WPF双向绑定无法正常工作

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

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

当我将数据输入文本框时,数据将在模型中更新。 当我加载数据并将其应用于数据上下文时,问题就会发生,文本框不会更新。

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

XAML

                       

Model < – Datacontext。

 ///  /// Provides a series of user preferences. ///  [Serializable] public class UserPreferences : INotifyPropertyChanged { private CollectionDevice selectedCollectionDevice; public UserPreferences() { this.AvailableCollectionDevices = new List(); 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(); } ///  /// Gets or sets the GPS collection device. ///  public CollectionDevice SelectedCollectionDevice { get { return selectedCollectionDevice; } set { selectedCollectionDevice = value; this.OnPropertyChanged("SelectedCollectionDevice"); } } ///  /// Gets or sets a collection of devices that can be used for collecting GPS data. ///  [Ignore] [XmlIgnore] public List AvailableCollectionDevices { get; set; } public event PropertyChangedEventHandler PropertyChanged; ///  /// Notifies objects registered to receive this event that a property value has changed. ///  /// The name of the property that was changed. protected virtual void OnPropertyChanged(string propertyName) { if (this.PropertyChanged != null) { this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } 

CollectionDevice < – 文本框绑定的位置。

 ///  /// CollectionDevice model ///  [Serializable] public class CollectionDevice : INotifyPropertyChanged { ///  /// Gets or sets the COM port. ///  private int comPort; ///  /// Gets or sets a value indicating whether [waas]. ///  private bool waas; ///  /// Gets or sets the data points. ///  private int dataPoints; ///  /// Gets or sets the baud rate. ///  private int baudRate; ///  /// Gets or sets the name. ///  public string Name { get; set; } ///  /// Gets or sets the COM port. ///  public int ComPort { get { return this.comPort; } set { this.comPort= value; this.OnPropertyChanged("ComPort"); } } ///  /// Gets or sets the COM port. ///  public bool WAAS { get { return this.waas; } set { this.waas = value; this.OnPropertyChanged("WAAS"); } } ///  /// Gets or sets the COM port. ///  public int DataPoints { get { return this.dataPoints; } set { this.dataPoints = value; this.OnPropertyChanged("DataPoints"); } } ///  /// Gets or sets the COM port. ///  public int BaudRate { get { return this.baudRate; } set { this.baudRate = value; this.OnPropertyChanged("BaudRate"); } } public event PropertyChangedEventHandler PropertyChanged; ///  /// Notifies objects registered to receive this event that a property value has changed. ///  /// The name of the property that was changed. protected virtual void OnPropertyChanged(string propertyName) { if (this.PropertyChanged != null) { this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } public override string ToString() { return this.Name; } } 

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

更新1

我试图强制更新文本框数据绑定,但这不起作用。

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

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

  

更新2

我试图跟随微软的一些文档 ,它似乎没有解决问题。 窗口加载时,值仍为0。 从数据库恢复对象的状态后,绑定未更新。 绑定是连接的,因为当我输入数据时,数据上下文会更新。 出于某种原因,当我将它设置为双向时,它就像单向一样。

更新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 – 发现问题

我已经确定了问题,但仍然需要一个解决方案。 数据绑定的全部意义在于我不必执行此手动分配。 我的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属性仅在对焦点丢失时才更新。 这是默认行为。 你用DataContextvalidation了吗?

如果要覆盖此行为,则必须以这种方式包含属性UpdateSourceTrigger

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

UpdateSourceTrigger的值设置为PropertyChanged ,一旦文本更改,更改将在更改绑定属性的值时反映在TextBox中。

这里有一个关于UpdateSourceTrigger属性用法的有用教程。

好吧,我能够确定问题并解决问题。 事实certificate这是导致这种情况的汇编。

首先,我的模型。

UserPreferences < - MainWindow是绑定到此的数据。

 [Serializable] public class UserPreferences : INotifyPropertyChanged { private CollectionDevice selectedCollectionDevice; public UserPreferences() { this.AvailableCollectionDevices = new List(); 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(); } ///  /// Gets or sets the GPS collection device. ///  public CollectionDevice SelectedCollectionDevice { get { return selectedCollectionDevice; } set { selectedCollectionDevice = value; if (selectedCollectionDevice.Name == "Other") { this.AvailableCollectionDevices[3] = value; } this.OnPropertyChanged("SelectedCollectionDevice"); } } ///  /// Gets or sets a collection of devices that can be used for collecting GPS data. ///  [Ignore] [XmlIgnore] public List AvailableCollectionDevices { get; set; } public event PropertyChangedEventHandler PropertyChanged; ///  /// Notifies objects registered to receive this event that a property value has changed. ///  /// The name of the property that was changed. protected virtual void OnPropertyChanged(string propertyName) { if (this.PropertyChanged != null) { this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } } 

SelectedCollectionDevice的setter中,我不想查看所选设备是否是其他设备。 所有其他设备(yuma1,panasonic等)都具有永不改变的预定属性值。 当用户选择“其他”时,将显示文本框,并且他们可以手动输入数据。 问题是当在窗口加载期间从数据库恢复手动输入的数据时,我没有将SelectedCollectionDevice的自定义数据分配给集合中的相应对象。

在窗口加载期间, Combobox.SelectedItem被设置为SelectedCollectionDevice的索引。 Combobox.ItemsSource设置为AvailableCollectionDevices集合。

 this.CollectionDevice.SelectedIndex = viewModel.AvailableCollectionDevices.IndexOf( viewModel.AvailableCollectionDevices.FirstOrDefault( acd => acd.Name == viewModel.SelectedCollectionDevice.Name)); 

执行上面的代码时,combobox从其数据源中提取默认对象,该数据源的所有值都设置为零。 在combobox的SelectionChanged事件中,我将Data Context SelectedCollectionDevice分配给与combobox关联的零输出项。

 private void CollectionDeviceSelected(object sender, SelectionChangedEventArgs e) { if (e.AddedItems.Count > 0 && e.AddedItems[0] is CollectionDevice) { // Assign the view models SelectedCollectionDevice to the device selected in the combo box. var device = e.AddedItems[0] as CollectionDevice; ((Models.UserPreferences)this.DataContext).SelectedCollectionDevice = device; // Check if Other is selected. If so, we have to present additional options. if (device.Name == "Other") { OtherCollectionDevicePanel.Visibility = Visibility.Visible; } else if (OtherCollectionDevicePanel.Visibility == Visibility.Visible) { OtherCollectionDevicePanel.Visibility = Visibility.Collapsed; } } } 

长话短说,我在上面的代码中为SelectedCollectionDevice添加了代码,将值应用于AvailableCollectionDevices List <>。 这样,当组合框选择了“其他”值时,它会使用正确的数据从集合中提取值。 在反序列化期间,我只是反序列化SelectedCollectionDevice而不是List <>,这就是为什么在首次加载窗口时数据总是被覆盖的原因。

这也解释了为什么使用本地viewModel.SelectedCollectionDevice重新分配数据上下文SelectedCollectionDevice属性是有效的。 我正在替换与combobox关联的零对象,该combobox在SelectionChanged事件期间设置了数据上下文。 我无法在XAML中设置DataContext并删除手动分配。

感谢所有帮助,它帮助我缩小调试范围,直到我最终解决了问题。 非常感激!

不是答案,但想发布在我的机器上工作的代码来帮助OP ……

完整的xaml页面……

                        

完整的代码背后……

 using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Windows; using System.Xml.Serialization; namespace WpfApplication1 { ///  /// Interaction logic for Window1.xaml ///  public partial class Window1 : Window { public Window1() { InitializeComponent(); DataContext = new UserPreferences(); } private void ButtonBase_OnClick(object sender, RoutedEventArgs e) { ((UserPreferences) DataContext).SelectedCollectionDevice.ComPort = 11; } } ///  /// Provides a series of user preferences. ///  [Serializable] public class UserPreferences : INotifyPropertyChanged { private CollectionDevice selectedCollectionDevice; public UserPreferences() { this.AvailableCollectionDevices = new List(); 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(); } ///  /// Gets or sets the GPS collection device. ///  public CollectionDevice SelectedCollectionDevice { get { return selectedCollectionDevice; } set { selectedCollectionDevice = value; this.OnPropertyChanged("SelectedCollectionDevice"); } } ///  /// Gets or sets a collection of devices that can be used for collecting GPS data. ///  [XmlIgnore] public List AvailableCollectionDevices { get; set; } public event PropertyChangedEventHandler PropertyChanged; ///  /// Notifies objects registered to receive this event that a property value has changed. ///  /// The name of the property that was changed. protected virtual void OnPropertyChanged(string propertyName) { if (this.PropertyChanged != null) { this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } } ///  /// CollectionDevice model ///  [Serializable] public class CollectionDevice : INotifyPropertyChanged { ///  /// Gets or sets the COM port. ///  private int comPort; ///  /// Gets or sets a value indicating whether [waas]. ///  private bool waas; ///  /// Gets or sets the data points. ///  private int dataPoints; ///  /// Gets or sets the baud rate. ///  private int baudRate; ///  /// Gets or sets the name. ///  public string Name { get; set; } ///  /// Gets or sets the COM port. ///  public int ComPort { get { return this.comPort; } set { this.comPort = value; this.OnPropertyChanged("ComPort"); } } ///  /// Gets or sets the COM port. ///  public bool WAAS { get { return this.waas; } set { this.waas = value; this.OnPropertyChanged("WAAS"); } } ///  /// Gets or sets the COM port. ///  public int DataPoints { get { return this.dataPoints; } set { this.dataPoints = value; this.OnPropertyChanged("DataPoints"); } } ///  /// Gets or sets the COM port. ///  public int BaudRate { get { return this.baudRate; } set { this.baudRate = value; this.OnPropertyChanged("BaudRate"); } } public event PropertyChangedEventHandler PropertyChanged; ///  /// Notifies objects registered to receive this event that a property value has changed. ///  /// The name of the property that was changed. protected virtual void OnPropertyChanged(string propertyName) { if (this.PropertyChanged != null) { this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } public override string ToString() { return this.Name; } } } 

我遇到过同样的问题。 我的问题是绑定属性名称是错误的。 如果查看输出窗口,可以在运行时看到所有绑定错误。

System.Windows.Data错误:40:BindingExpression路径错误:’object”’ProtectedWebsitesViewModel’(HashCode = 32764015)’上找不到’SelectedProtectedWebsiteTemplate’属性。 BindingExpression:路径= SelectedProtectedWebsiteTemplate.Name; DataItem =’ProtectedWebsitesViewModel’(HashCode = 32764015); target元素是’TextBox’(Name =”); target属性是’Text’(类型’String’)