c# - WPF 双向绑定(bind)不起作用

标签 c# wpf xaml data-binding mvvm

我有一个数据上下文 (UserPreferences) 分配给我的主窗口,以及一个文本框,该文本框双向绑定(bind)到数据上下文的一个属性 (CollectionDevice) >) 在上下文中。

加载窗口时,文本框不会绑定(bind)到我的模型中的属性。我在调试器中验证数据上下文是否设置为模型对象并且模型的属性已正确分配。然而,我得到的只是一系列带有 0 的文本框。

当我将数据输入文本框时,数据会在模型中更新。当我加载数据并将其应用到数据上下文时,这个问题就发生了,文本框没有得到更新。

当我将模型保存到数据库时,将从文本框中保存正确的数据。当我从数据库中恢复模型时,会应用正确的数据。当模型应用于我的构造函数中的数据上下文时,文本框的数据上下文包含正确的数据,并且它的属性按应有的方式分配。问题是 UI 没有反射(reflect)这一点。

XAML

<Window.DataContext>
    <models:UserPreferences />
</Window.DataContext>

        <!-- Wrap pannel used to store the manual settings for a collection device. -->
        <StackPanel Name="OtherCollectionDevicePanel">
            <StackPanel Orientation="Horizontal">
                <TextBlock VerticalAlignment="Center" Margin="10, 10, 0, 0" Text="Baud Rate" />
                <TextBox Name="BaudRateTextBox" Text="{Binding Path=SelectedCollectionDevice.BaudRate, Mode=TwoWay}" Margin="10, 10, 0, 0" MinWidth="80" ></TextBox>
            </StackPanel>
            <WrapPanel>
                <TextBlock VerticalAlignment="Center" Margin="10, 10, 0, 0" Text="Com Port" />
                <TextBox Text="{Binding Path=SelectedCollectionDevice.ComPort, Mode=TwoWay}" Margin="10, 10, 0, 0" MinWidth="80" ></TextBox>
            </WrapPanel>
            <WrapPanel>
                <TextBlock VerticalAlignment="Center" Margin="10, 10, 0, 0" Text="Data Points" />
                <TextBox Text="{Binding Path=SelectedCollectionDevice.DataPoints, Mode=TwoWay}" Margin="10, 10, 0, 0" MinWidth="80" ></TextBox>
            </WrapPanel>
            <WrapPanel Orientation="Horizontal">
                <TextBlock VerticalAlignment="Center" Margin="10, 10, 0, 0" Text="WAAS" />
                <CheckBox IsChecked="{Binding Path=SelectedCollectionDevice.WAAS, Mode=TwoWay}" Content="Enabled" Margin="20, 0, 0, 0" VerticalAlignment="Bottom"></CheckBox>
            </WrapPanel>
        </StackPanel>

模型 <-- 数据上下文。

/// <summary>
/// Provides a series of user preferences.
/// </summary>
[Serializable]
public class UserPreferences : INotifyPropertyChanged
{
    private CollectionDevice selectedCollectionDevice;

    public UserPreferences()
    {
        this.AvailableCollectionDevices = new List<CollectionDevice>();

        var yuma1 = new CollectionDevice
        {
            BaudRate = 4800,
            ComPort = 31,
            DataPoints = 1,
            Name = "Trimble Yuma 1",
            WAAS = true
        };

        var yuma2 = new CollectionDevice
        {
            BaudRate = 4800,
            ComPort = 3,
            DataPoints = 1,
            Name = "Trimble Yuma 2",
            WAAS = true
        };

        var toughbook = new CollectionDevice
        {
            BaudRate = 4800,
            ComPort = 3,
            DataPoints = 1,
            Name = "Panasonic Toughbook",
            WAAS = true
        };


        var other = new CollectionDevice
        {
            BaudRate = 0,
            ComPort = 0,
            DataPoints = 0,
            Name = "Other",
            WAAS = false
        };

        this.AvailableCollectionDevices.Add(yuma1);
        this.AvailableCollectionDevices.Add(yuma2);
        this.AvailableCollectionDevices.Add(toughbook);
        this.AvailableCollectionDevices.Add(other);

        this.SelectedCollectionDevice = this.AvailableCollectionDevices.First();
    }

    /// <summary>
    /// Gets or sets the GPS collection device.
    /// </summary>
    public CollectionDevice SelectedCollectionDevice
    {
        get
        {
            return selectedCollectionDevice;
        }
        set
        {
            selectedCollectionDevice = value;
            this.OnPropertyChanged("SelectedCollectionDevice");
        }
    }

    /// <summary>
    /// Gets or sets a collection of devices that can be used for collecting GPS data.
    /// </summary>
    [Ignore]
    [XmlIgnore]
    public List<CollectionDevice> AvailableCollectionDevices { get; set; }

    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    /// Notifies objects registered to receive this event that a property value has changed.
    /// </summary>
    /// <param name="propertyName">The name of the property that was changed.</param>
    protected virtual void OnPropertyChanged(string propertyName)
    {
        if (this.PropertyChanged != null)
        {
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

CollectionDevice <-- 文本框绑定(bind)到的位置。

/// <summary>
/// CollectionDevice model
/// </summary>
[Serializable]
public class CollectionDevice : INotifyPropertyChanged
{
    /// <summary>
    /// Gets or sets the COM port.
    /// </summary>
    private int comPort;

    /// <summary>
    /// Gets or sets a value indicating whether [waas].
    /// </summary>
    private bool waas;

    /// <summary>
    /// Gets or sets the data points.
    /// </summary>
    private int dataPoints;

    /// <summary>
    /// Gets or sets the baud rate.
    /// </summary>
    private int baudRate;

    /// <summary>
    /// Gets or sets the name.
    /// </summary>
    public string Name { get; set; }

    /// <summary>
    /// Gets or sets the COM port.
    /// </summary>
    public int ComPort
    {
        get
        {
            return this.comPort;
        }

        set
        {
            this.comPort= value;
            this.OnPropertyChanged("ComPort");
        }
    }

    /// <summary>
    /// Gets or sets the COM port.
    /// </summary>
    public bool WAAS
    {
        get
        {
            return this.waas;
        }

        set
        {
            this.waas = value;
            this.OnPropertyChanged("WAAS");
        }
    }

    /// <summary>
    /// Gets or sets the COM port.
    /// </summary>
    public int DataPoints
    {
        get
        {
            return this.dataPoints;
        }

        set
        {
            this.dataPoints = value;
            this.OnPropertyChanged("DataPoints");
        }
    }

    /// <summary>
    /// Gets or sets the COM port.
    /// </summary>
    public int BaudRate
    {
        get
        {
            return this.baudRate;
        }

        set
        {
            this.baudRate = value;
            this.OnPropertyChanged("BaudRate");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    /// Notifies objects registered to receive this event that a property value has changed.
    /// </summary>
    /// <param name="propertyName">The name of the property that was changed.</param>
    protected virtual void OnPropertyChanged(string propertyName)
    {
        if (this.PropertyChanged != null)
        {
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public override string ToString()
    {
        return this.Name;
    }
}

有人能指出我正确的方向吗?我认为问题是我在 XAML 中的绑定(bind);我找不到它。我需要它是双向绑定(bind)的,因为数据可以在模型内的应用程序生命周期内随时更改(数据库通过同步更新)并且 UI 需要反射(reflect)这些更改,但用户可以通过以下方式将更改应用于模型用户界面。

更新 1

我试图强制更新文本框数据绑定(bind),但效果不佳。

BindingExpression be = this.BaudRateTextBox.GetBindingExpression(TextBox.TextProperty);
be.UpdateSource();

我还尝试将 UpdateSourceTrigger 设置为 PropertyChanged,但似乎也没有解决问题。

<TextBox Name="BaudRateTextBox" Text="{Binding Path=SelectedCollectionDevice.BaudRate, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="10, 10, 0, 0" MinWidth="80" ></TextBox>

更新 2

我试着跟随一些 documentation from Microsoft它似乎并没有解决这个问题。当窗口加载时,这些值仍然为 0。在我从数据库中恢复对象的状态后,绑定(bind)没有被更新。绑定(bind)是有线的,因为当我输入数据时,数据上下文会更新。出于某种原因,当我将其设置为双向时,它的行为就像单向。

更新 3

我试图将代码移入窗口加载事件并移出构​​造函数,但这似乎没有帮助。我发现有趣的是 PropertyChanged 事件在反序列化过程中不会被触发。我认为在这种情况下这并不重要,因为对象已正确完全恢复,然后我只是将它分配给数据上下文。我将数据上下文移出 XAML 并移入 WindowLoaded 以测试 XAML 是否是问题所在。结果是一样的。

private void WindowLoaded(object sender, RoutedEventArgs e)
{
    // Restore our preferences state.
    var preferences = new UserPreferenceCommands();
    Models.UserPreferences viewModel = new Models.UserPreferences();

    // Set up the event handler before we deserialize.
    viewModel.PropertyChanged += viewModel_PropertyChanged;
    preferences.LoadPreferencesCommand.Execute(viewModel);

    // At this point, viewModel is a valid object. All properties are set correctly.
    viewModel = preferences.Results;

    // After this step, the UI still shows 0's in all of the text boxs. Even though the values are not zero.
    this.DataContext = viewModel;
}

// NEVER gets fired from within the WindowLoaded event.
void viewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
    MessageBox.Show("Property changed!");
}

// This changes the model properties and is immediately reflected in the UI. Why does this not happen within the WindowLoaded event?
private void TestButtonClickEvent(object sender, RoutedEventArgs e)
{
    var context = this.DataContext as Models.UserPreferences;
    context.SelectedCollectionDevice.ComPort = 1536;
}

更新 4 - 已识别的问题

我已确定问题所在,但仍需要解决方案。数据绑定(bind)的全部意义在于我不必执行此手动分配。我的 INotify 实现有问题吗?

private void WindowLoaded(object sender, RoutedEventArgs e)
{
    // Restore our preferences state.
    var preferences = new UserPreferenceCommands();
    Models.UserPreferences viewModel = new Models.UserPreferences();

    // Set up the event handler before we deserialize.
    viewModel.PropertyChanged += viewModel_PropertyChanged;
    preferences.LoadPreferencesCommand.Execute(viewModel);

    // At this point, viewModel is a valid object. All properties are set correctly.
    viewModel = preferences.Results;

    // After this step, the UI still shows 0's in all of the text boxs. Even though the values are not zero.
    this.DataContext = viewModel;

    // SOLUTION: - Setting the actual property causes the UI to be reflected when the window is initialized; setting the actual data context does not. Why? Also note that I set this property and my PropertyChanged event handler still does not fire.
    ((Models.UserPreferences) DataContext).SelectedCollectionDevice = viewModel.SelectedCollectionDevice;

}

最佳答案

默认情况下,TextBox 的 Text 属性仅在失去焦点时更新。 您是否使用 DataContext 对其进行了验证?

如果你想覆盖这个行为,你必须包含 UpdateSourceTrigger这样的属性:

Text="{Binding Path=SelectedCollectionDevice.BaudRate, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}

UpdateSourceTrigger 的值设置为 PropertyChanged,当您更改绑定(bind)属性的值时,只要文本更改,更改就会反射(reflect)在 TextBox 中。

有关 UpdateSourceTrigger 属性用法的有用教程是 here .

关于c# - WPF 双向绑定(bind)不起作用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22253211/

相关文章:

java - JNA通过java找不到dll文件中指定的程序

wpf - Viewmodel 的多个实例

Silverlight:ItemsControl 中的最后一项

performance - 包含在 ScrollViewer 中的 LongListSelector 性能非常差

c# - 如何测试需要协调 UserControls 的创建和托管的 ViewModel?

c# - IL Asm 函数名称中的 Unicode?

c# - Dictionary.ContainsKey 行为不端 c#

WPF 从 ListBox 拖放选择模式 Multiple

c# - 从 PRISM 中的区域获取 HostControl

wpf - ItemsControl 与 SharedSizeGroup 和拉伸(stretch)