英文:
Why doesn't changing the bound property update the checkbox's visual state during runtime?
问题
我有一个简单的WPF应用程序,其中包含一个下拉框和一个复选框:
XAML如下所示:
<StackPanel>
<ComboBox ItemsSource="{Binding FileTypes}" SelectedItem="{Binding SelectedFileType, Mode=TwoWay}"/>
<CheckBox Content="test" IsChecked="{Binding IsChecked}"/>
</StackPanel>
我想要在从下拉框中选择“Word”时强制复选框取消选中状态。我尝试通过在我的数据上下文中实现INotifyPropertyChanged
,然后订阅表示下拉框中选定项的属性SelectedFileType
的PropertyChanged
事件来实现这一点。以下是我到目前为止编写的代码:
public class MainViewModel : INotifyPropertyChanged
{
private string m_selectedFileType;
public List<string> FileTypes { get; set; }
public bool IsChecked { get; set; }
public string SelectedFileType
{
get => m_selectedFileType;
set
{
m_selectedFileType = value;
PropertyChanged(this, new PropertyChangedEventArgs((nameof(SelectedFileType))));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public MainViewModel()
{
FileTypes = new List<string>();
FileTypes.Add("Excel");
FileTypes.Add("Word");
PropertyChanged += FileTypeChanged;
SelectedFileType = "Excel";
IsChecked = true;
}
private void FileTypeChanged(object sender, PropertyChangedEventArgs e)
{
if (SelectedFileType == "Word")
{
IsChecked = false;
}
}
}
不幸的是,当我运行应用程序并从下拉框中切换从“Excel”到“Word”时,复选框保持不变,所以我觉得我漏掉了什么。
我已经尝试过切换到TwoWay
绑定并使用UpdateSourceTrigger
,但这些方法都没有奏效。
为什么在运行时修改SelectedFileType
属性不会影响复选框的可视状态,即使我在XAML中明确将其设置为双向绑定呢?
编辑:相关的文档提到了以下内容:
“要检测源更改(适用于OneWay和TwoWay绑定),源必须实现适当的属性更改通知机制,例如INotifyPropertyChanged。”
我开始觉得也许TwoWay
绑定和INotifyPropertyChanged
有一定的关联。
英文:
I have a simple WPF application with a drop down and a checkbox:
The XAML looks like the following:
<StackPanel>
<ComboBox ItemsSource="{Binding FileTypes}" SelectedItem="{Binding SelectedFileType, Mode=TwoWay}"/>
<CheckBox Content="test" IsChecked="{Binding IsChecked}"/>
</StackPanel>
I would like to force the checkbox to go unchecked when I select "Word" from the drop down. I've tried to achieve this by implementing INotifyPropertyChanged
in my data context and then subscribing to the PropertyChanged
event of the property which represents the selected item from the drop down, SelectedFileType
. Here's the code I have so far:
public class MainViewModel : INotifyPropertyChanged
{
private string m_selectedFileType;
public List<string> FileTypes { get; set; }
public bool IsChecked { get; set; }
public string SelectedFileType
{
get => m_selectedFileType;
set
{
m_selectedFileType = value;
PropertyChanged(this, new PropertyChangedEventArgs((nameof(SelectedFileType))));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public MainViewModel()
{
FileTypes = new List<string>();
FileTypes.Add("Excel");
FileTypes.Add("Word");
PropertyChanged += FileTypeChanged;
SelectedFileType = "Excel";
IsChecked = true;
}
private void FileTypeChanged(object sender, PropertyChangedEventArgs e)
{
if (SelectedFileType == "Word")
{
IsChecked = false;
}
}
}
Unfortunately, when I run the app and switch from "Excel" to "Word" in the drop down, the check box remains unchanged, so I feel that I am missing something.
I have already tried switching to TwoWay
binding and using UpdateSourceTrigger
but none of those things have worked.
Why does modifying the SelectedFileType
property during runtime not affect the visual state of the checkbox, even though I have clearly set it as a two-way binding in XAML?
EDIT
The relevant documentation does mention the following:
> To detect source changes (applicable to OneWay and TwoWay bindings), the source must implement a suitable property change notification mechanism such as INotifyPropertyChanged.
I'm starting to think that perhaps TwoWay
binding and INotifyPropertyChanged
sort of go hand in hand...
答案1
得分: 1
您的 IsChecked
属性未引发 INotifyPropertyChanged.PropertyChanged
事件。
每当您希望从源属性(例如 IsChecked
)发送数据到目标属性(例如 CheckBox.IsChecked
)时,必须引发 INotifyPropertyChanged.PropertyChanged
事件。否则,绑定将不知道它必须更新目标。
BindingMode.TwoWay
仅表示数据可以回传到源。此模式还包括 BindingMode.OneWay
行为以使其双向。换句话说,当绑定目标(例如 CheckBox
)更改双向绑定的值时,目标必须引发更改通知(通过依赖属性)以通知绑定它必须更新绑定源。仍然,源必须引发更改通知,以便在更新值时触发绑定(更准确地说是 BindingExpression
)。
绑定是被动的,不会主动在源和目标之间传递数据。发送者(单向绑定中的绑定源和双向绑定中的绑定源和绑定目标,或单向到源模式中的绑定目标)必须始终通过引发更改通知来显式触发绑定以传输数据(通过实现 INotifyPropertyChanged
并引发 PropertyChanged
,或通过将属性实现为依赖属性)。
请参阅Microsoft Docs:数据绑定概述(WPF .NET)。
此外,您当前的代码将在任何属性更改时都更新 IsChecked
属性。您必须在 PropertyChanged
事件处理程序中筛选正确的属性:
private void FileTypeChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case nameof(this.IsChecked):
if (this.SelectedFileType == "Word")
{
this.IsChecked = false;
}
break;
}
}
但因为您是这些属性的所有者,所以不必处理 PropertyChanged
事件。而是从属性设置器中调用例如 OnIsChekedChanged
方法。您可以在.NET文档中找到更好和推荐的 INotifyPropertyChanged
实现:PropertyChanged
示例。
改进和修复版本的代码可能如下所示:
class MainViewModel : INotifyPropertyChanged
{
private bool isChecked;
public bool IsChecked
{
get => this.isChecked;
set
{
this.isChecked = value;
OnPropertyChanged();
OnIsCheckedChanged();
}
}
private string selectedFileType;
public string SelectedFileType
{
get => this.selectedFileType;
set
{
bool oldValue = this.IsChecked;
this.selectedFileType = value;
OnPropertyChanged();
OnIsCheckedChanged(oldValue, value);
}
}
protected virtual void OnIsCheckedChanged(bool oldValue, bool newValue)
{
if (this.SelectedFileType.Equals("Word", StringComparison.InvariantCultureIgnoreCase))
{
this.IsChecked = false;
}
}
}
英文:
Your IsChecked
property doesn't raise the INotifyPropertyChanged.PropertyChanged
event.
Whenever you want to send data from the source property (e.g. IsChecked
) to a target property (e.g. CheckBox.IsChecked
), you must raise the INotifyPropertyChanged.PropertyChanged
. Otherwise the Binding
won't know that it has to update the target.
BindingMode.TwoWay
only means that data can be send back to the source. This mode also includes BindingMode.OneWay
behavior to make it two way. In other words, when the the binding target (e.g. CheckBox
) changes the value of a two way binding the target has to raise a change notification (via dependency property) to notify the Binding
that it has to update the binding source.
Still the source has to raise a change notification in case it updates the value to trigger the Binding
(it's actually the BindingExpression
to be more precise).
The binding is passive in that it doesn't actively send data between source and target. The sender (binding source in OneWay
and binding source and binding target in TwoWay
or binding target in OneWyToSource mode) must always explicitly trigger the binding to transfer data by raising a change notification (by implementing
INotifyPropertyChangedand raise
PropertyChanged` or by implementing the property as dependency property).
See Microsoft Docs: Data binding overview (WPF .NET).
Furthermore, your current code will update the IsChecked
property every time that any property changes. You must filter for the correct property in your PropertyChanegd
event handler:
private void FileTypeChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case nameof(this.IsChecked):
if (this.SelectedFileType == "Word")
{
this.IsChecked = false;
}
break;
}
}
But because you are the the owner of those properties, you don't have to handle the PropertyChanged
event. Instead invoke a e.g. OnIsChekedChanged
method from the property setter.
You can find a better and recommended INotifyPropertyChanged
implementation in the .NET docs: PropertyChanged
example.
The improved and fixed version of your code could look as follows:
class MainViewModel : INotifyProeprtyChanged
{
private bool isChecked;
public bool IsChecked
{
get => this.isChecked;
set
{
this.isChecked = value;
OnPropertyChanged();
OnIsCheckedChanged();
}
}
private string selectedFileType;
public string SelectedFileType
{
get => this.selectedFileType;
set
{
bool oldValue = this.IsChecked;
this.selectedFileType = value;
OnPropertyChanged();
OnIsCheckedChanged(oldValue, value);
}
}
protected virtual void OnIsCheckedChanged(bool oldValue, bool newValue)
{
if (this.SelectedFileType.Equals("Word", StringComparison.InvariantCultureIgnoreCase)
{
this.IsChecked = false;
}
}
}
答案2
得分: 0
你忘了通知视图 IsChecked
属性已更改。
我稍微修改了您的代码,这应该可以工作:
这是XAML代码:
<StackPanel>
<ComboBox ItemsSource="{Binding FileTypes}" SelectedItem="{Binding SelectedFileType}"/>
<CheckBox Content="test" IsChecked="{Binding IsChecked}"/>
</StackPanel>
这是更改将发生的地方:
public class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
SelectedFileType = "Excel";
IsChecked = true;
}
public event PropertyChangedEventHandler PropertyChanged;
// 实现一个方法来引发属性更改事件
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
// 创建一个新的私有字段,并使用属性公开它,以保持代码一致性
// 还使用了ObservableCollection,因为它在向集合添加项时会通知视图
private ObservableCollection<string> file_types = new ObservableCollection<string>() { "Word", "Excel" };
public ObservableCollection<string> FileTypes
{
get => file_types;
private set => file_types = value; // 通常不需要重新创建ObservableCollection,只需要修改它
}
private bool is_checked = false;
public bool IsChecked
{
get => is_checked;
set
{
is_checked = value;
// 通知视图更改
OnPropertyChanged(nameof(IsChecked));
}
}
private string m_selectedFileType;
public string SelectedFileType
{
get => m_selectedFileType;
set
{
m_selectedFileType = value;
OnPropertyChanged(nameof(SelectedFileType));
// 使用现有属性的绑定,而不是事件
if (SelectedFileType == "Word")
{
// 这将触发属性设置器,从而触发OnPropertyChanged方法
IsChecked = false;
}
}
}
}
编辑:我认为您可能误解了Binding Mode
的实际工作原理。我发现这篇文章很容易理解。
简而言之;
将任何Mode
添加到绑定中要求元素具有共同的绑定属性。例如,一个Slider
和一个TextBox
。Slider
的值绑定到一个名为SliderValue
(示例)的属性,而TextBox
的Text
属性绑定到相同的SliderValue
属性。Binding Mode
只是让我们控制哪个控件可以更改另一个控件的视图值。请注意,它们都具有它们绑定到的共同属性,而在您的情况下并非如此。
考虑到这个代码作为我们的上下文:
<Slider Name="Slider1" Value="0" SmallChange="1" Maximum="100"></Slider>
<TextBox Name="txtValue" Text="{Binding Mode=TwoWay, ElementName=Slider1,Path=Value, UpdateSourceTrigger=PropertyChanged}"></TextBox>
OneWay
- 这将允许滑块更改文本框的值,但反之亦然
OneWayToSource
- 这将允许文本框更改滑块的值,但反之亦然
TwoWay
- 这将允许两个组件互相更新
您的Binding Mode
实现不符合这些规则。
英文:
You forgot to notify the view that the IsChecked
property has changed.
I took the freedom to modify your code a bit, this should work:
This would be the XAML
<StackPanel>
<ComboBox ItemsSource="{Binding FileTypes}" SelectedItem="{Binding SelectedFileType}"/>
<CheckBox Content="test" IsChecked="{Binding IsChecked}"/>
</StackPanel>
and this is where the change will happen:
public class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
SelectedFileType = "Excel";
IsChecked = true;
}
public event PropertyChangedEventHandler PropertyChanged;
// Implement a method to raise property changed calls
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
// Created a new private field and exposed it using a property for code consistency
// Also used ObservableCollection because it notifies the view when an item is added to the collection
private ObservableCollection<string> file_types = new ObservableCollection<string>() { "Word", "Excel" };
public ObservableCollection<string> FileTypes
{
get => file_types;
private set => file_types = value; // You usually don't need to recreate an ObservableCollection, only modify it
}
private bool is_checked = false;
public bool IsChecked
{
get => is_checked;
set
{
is_checked = value;
// Notifiy the view of the change
OnPropertyChanged(nameof(IsChecked));
}
}
private string m_selectedFileType;
public string SelectedFileType
{
get => m_selectedFileType;
set
{
m_selectedFileType = value;
OnPropertyChanged(nameof(SelectedFileType));
// Use the existing binding to the property instead of an event
if (SelectedFileType == "Word")
{
// this will trigger the property setter thus triggering the OnPropertyChanged method
IsChecked = false;
}
}
}
}
Edit: I think you have misunderstood how Binding Mode
actually works. I found this article to be pretty easy to understand.
TLDR;
Adding any Mode
to a binding requires that the elements have a common binding property. For example a Slider
and a TextBox
. The Slider
has its value bound to a property called SliderValue
(example) and the TextBox
has its Text
property bound to the same SliderValue
property. The Binding Mode
just gives us control over which of these control can change the view value of the other. Notice that they both have a common property they are bound to, which in your case is not true.
Considering this code as our context:
<Slider Name="Slider1" Value="0" SmallChange="1" Maximum="100" ></Slider>
<TextBox Name="txtValue" Text="{Binding Mode=TwoWay, ElementName=Slider1,Path=Value, UpdateSourceTrigger=PropertyChanged}"></TextBox>
OneWay
- this will allow the slider to change the value of the text box but not the other way around
OneWayToSource
- this will allow the text box to change the value of the slider but not the other way around
Two way
- this will allow both components to update each other
Your implementation of a Binding Mode
follows none of this.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论