Joh*_*ger 12 c# data-binding wpf xaml mvvm
我有一个数据上下文(UserPreferences
)分配给我的主窗口,以及一个文本框,它双向绑定到上下文中的一个数据上下文属性(CollectionDevice
)中的属性.
加载窗口时,文本框不会绑定到模型中的属性.我在调试器中验证数据上下文是否设置为模型对象,并且模型的属性已正确分配.然而,我得到的是一系列带有0的文本框.
当我将数据输入文本框时,数据将在模型中更新.当我加载数据并将其应用于数据上下文时,问题才会发生,文本框不会更新.
当我将模型保存到数据库时,将从文本框中保存正确的数据.当我从数据库恢复模型时,将应用正确的数据.当模型应用于构造函数中的数据上下文时,文本框的datacontext包含正确的数据,并且它的属性按原样分配.问题是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>
Run Code Online (Sandbox Code Playgroud)
Model < - Datacontext.
/// <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));
}
}
Run Code Online (Sandbox Code Playgroud)
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;
}
}
Run Code Online (Sandbox Code Playgroud)
有人能指出我正确的方向吗?我认为这个问题是我在XAML中的约束力; 我找不到它.我需要它是双向绑定的,因为数据可以在模型中的应用程序生命周期内随时更改(数据库通过同步更新)并且UI需要反映这些更改,但用户可以通过以下方式将更改应用于模型用户界面.
更新1
我试图强制更新文本框数据绑定,但这不起作用.
BindingExpression be = this.BaudRateTextBox.GetBindingExpression(TextBox.TextProperty);
be.UpdateSource();
Run Code Online (Sandbox Code Playgroud)
我也尝试设置UpdateSourceTrigger
到PropertyChanged
和似乎没有要么解决问题.
<TextBox Name="BaudRateTextBox" Text="{Binding Path=SelectedCollectionDevice.BaudRate, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="10, 10, 0, 0" MinWidth="80" ></TextBox>
Run Code Online (Sandbox Code Playgroud)
更新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;
}
Run Code Online (Sandbox Code Playgroud)
更新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;
}
Run Code Online (Sandbox Code Playgroud)
Alb*_*ano 50
默认情况下,Text
TextBox 的属性仅在对焦点丢失时才更新.您是否使用DataContext验证了它?
如果要覆盖此行为,则必须以UpdateSourceTrigger
这种方式包含该属性:
Text="{Binding Path=SelectedCollectionDevice.BaudRate, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}
Run Code Online (Sandbox Code Playgroud)
将UpdateSourceTrigger
值设置为PropertyChanged
,当您更改绑定属性的值时,更改将反映在TextBox中,只要文本更改.
有关使用一个有用的教程UpdateSourceTrigger
属性是在这里.
好吧,我能够确定问题并解决问题.事实证明这是导致这种情况的汇编.
首先,我的模型.
UserPreferences < - MainWindow是绑定到此的数据.
[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;
if (selectedCollectionDevice.Name == "Other")
{
this.AvailableCollectionDevices[3] = 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));
}
}
}
Run Code Online (Sandbox Code Playgroud)
在SelectedCollectionDevice
我的设置器中我不想看看所选设备是否是其他设备.所有其他设备(yuma1,panasonic等)都具有永不改变的预定属性值.当用户选择"其他"时,将显示文本框,并且他们可以手动输入数据.问题是当在窗口加载期间从数据库恢复手动输入的数据时,我没有将自定义数据分配给SelectedCollectionDevice
集合中的相应对象.
在窗口加载期间,将Combobox.SelectedItem
其设置为索引SelectedCollectionDevice
.将Combobox.ItemsSource
被设置为AvailableCollectionDevices
收集.
this.CollectionDevice.SelectedIndex =
viewModel.AvailableCollectionDevices.IndexOf(
viewModel.AvailableCollectionDevices.FirstOrDefault(
acd => acd.Name == viewModel.SelectedCollectionDevice.Name));
Run Code Online (Sandbox Code Playgroud)
执行上面的代码时,组合框从其数据源中提取默认对象,该数据源的所有值都设置为零.在组合框的SelectionChanged
事件中,我将数据上下文分配SelectedCollectionDevice
给与组合框关联的零输出项.
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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
长话短说,我在setter中添加了上面的代码,SelectedCollectionDevice
以便将值应用于AvailableCollectionDevices
List <>.这样,当组合框选择了"其他"值时,它会使用正确的数据从集合中提取值.在反序列化期间,我只是反序列化SelectedCollectionDevice
而不是List <>这就是为什么数据在窗口第一次加载时总是被覆盖的原因.
这也解释了为什么重新分配数据上下文SelectedCollectionDevice
属性与本地viewModel.SelectedCollectionDevice
工作.我正在替换与组合框关联的零对象,该组合框在SelectionChanged
事件期间设置了数据上下文.我无法在XAML中设置DataContext并删除手动分配.
感谢所有帮助,它帮助我缩小调试范围,直到我最终解决了问题.非常感激!