【发布时间】:2014-04-10 18:49:44
【问题描述】:
我有一个分配给我的主窗口的数据上下文 (UserPreferences),以及一个文本框,它双向绑定到上下文中数据上下文属性 (CollectionDevice) 中的一个属性。
当窗口加载时,文本框不会绑定到我模型中的属性。我在调试器中验证数据上下文是否设置为模型对象,并且模型的属性已正确分配。然而,我得到的只是一系列带有 0 的文本框。
当我在文本框中输入数据时,模型中的数据会更新。问题只是在我加载数据并将其应用于数据上下文时发生,文本框没有得到更新。
当我将模型保存到数据库时,会从文本框中保存正确的数据。当我从数据库中恢复模型时,会应用正确的数据。当模型应用于我的构造函数中的数据上下文时,文本框的数据上下文包含正确的数据,并且它的属性按应有的方式分配。问题是 UI 没有反映这一点。
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
/// <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 中的绑定;我找不到它。我需要它是双向绑定的,因为数据可以在模型内的应用程序生命周期内随时更改(数据库通过同步更新)并且 UI 需要反映这些更改,但用户可以通过以下方式将更改应用于模型用户界面。
更新 1
我尝试强制更新文本框数据绑定,但效果不佳。
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。从数据库中恢复对象的状态后,绑定没有更新。绑定是连线的,因为当我输入数据时,数据上下文会更新。出于某种原因,当我将其设置为双向时,它的行为就像单向一样。
更新 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;
}
【问题讨论】:
-
我要做的第一件事是将
List<CollectionDevice>更改为ObservableCollection<CollectionDevice> -
我什至还有一个自定义类支持多线程更新:code.google.com/p/mutinyirc/source/browse/MvvmFoundation.Wpf/… 以备不时之需。
-
感谢@Ekoostik,我之所以没有将其作为可观察的,是因为它不需要被观察。集合是在对象构造函数中设置的,它永远不会改变。它充满了预先确定的收集设备。最后一个条目是名称设置为“其他”的设备,然后允许用户输入自定义数据。这就是为什么观察到 SelectedCollectionDevice 而不是观察到 AvailableCollectionDevice。
-
我复制/粘贴了您的代码,没有任何更改,一切正常。唯一的区别......我只是在后面的代码中手动分配了 Window 构造函数中的数据上下文。我什至添加了一个更新 SelectedCollectionDevice 上的属性的按钮,并且视图反映了更改。我不知道问题是什么,但希望这可以帮助您调试它。
-
嗨@Kevin 感谢您的提示。我尝试向 UI 添加一个按钮,并让它为
SelectedCollectionDevice中的一个属性分配一个值,并在 UI 上正确更新。我还将数据上下文从 XAML 移到构造函数中。最终结果是在 Window 加载时对象仍未正确绑定。文本框是空的。加载窗口后的任何更新都会立即显示在文本框上,而不是在加载期间。还有其他想法吗?
标签: c# wpf xaml data-binding mvvm