WPF 双向绑定不适用于 CheckBox 和 List<string>
WPF two way binding not working on CheckBox and List<string>
即使在审查了大量的解决方案建议后,我也无法在 xaml 中使用简单的双向绑定。我有一个 Window、一个 dataContext 和一个 App。问题是:
a) 当 App 构造函数运行时,Window(在同一个构造函数中初始化和 .Show
-ed)出现,但根本没有更新,即使我切换了复选框我的 C# 代码中的值几次;
b) 当 App 构造函数完成时,Window 只更新一次;我已将其设置为如果我单击 Window 中的复选框,App 中的事件处理程序(绑定到 DataContext 属性 更改通知)应该增加字符串列表的大小,即也显示出来。列表的增加在代码中正确发生,但未反映在 Window.
中
总结:
- Window 中的用户输入可以正常访问应用程序的 C# 代码:我可以对复选框更改等进行操作
- 相反的方向不起作用:每当通过代码更改 dataContext 中的项目时,Window 不会自动更新,即使已实现和执行 iNotifyProperty。
我希望的是:
a) 当 App 构造函数运行并切换 CheckBox 值时,Window 应通过设置/清除复选框上的勾号来反映更改;
b) App 构造函数完成后,每当我将 CheckBox 从 FALSE 切换为 TRUE 时,NameList
都会附加一个新字符串。我希望 Window 中的列表会相应增加并自动显示完整的附加 NameList
内容。
观察结果:
- 我尝试确保在 Window 上调用
InitializeComponent
之前设置 Window 上的 DataContext。不幸的是并没有真正改变...
- 我在 MainWindow.xaml 文件 的 VS 中得到一条线索:CheckBox 路径和 ListBox 绑定
NameList
注释为
Cannot resolve symbol due to unknown DataContext
但是,当 App 构造函数终止时 Window 被更新,当我单击 CheckBox 时,将触发正确的 NotifyProperty 事件。这告诉我运行时绑定实际上应该工作......显然只是单向的,而不是双向的。
MainWindow.xaml:
<Window x:Class="StatisticsEvaluation.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<StackPanel Orientation="Vertical">
<CheckBox IsChecked="{Binding Path=IsChecked, Mode=TwoWay}" Content="CheckBox" />
<ListBox ItemsSource="{Binding NameList, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBlock FontSize="18" FontFamily="Arial" Foreground="Black" Text="TextBlock" Visibility="Visible" />
</StackPanel>
</Grid>
MainWindow.xaml.cs:
namespace StatisticsEvaluation
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
}
}
}
应用程序和 DataContext:
namespace StatisticsEvaluation
{
public class DataContextClass : INotifyPropertyChanged
{
private bool isChecked;
public bool IsChecked
{
get
{
return isChecked;
}
set
{
isChecked = value;
OnPropertyChanged("IsChecked");
}
}
private List<string> nameList;
public List<string> NameList
{
get
{
return nameList;
}
set
{
nameList = value;
OnPropertyChanged("NameList");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
private MainWindow MyWindow { get; set; }
private DataContextClass MyDataContext{ get; set; }
private void HandleDataContextPropertyChange(object sender, PropertyChangedEventArgs e)
{
// If the CheckBox was just toggled to TRUE, increase the NameList
// with an additional name and call OnPropertyChanged on it ...
// hoping that this would trigger a Window UI update - but no luck !
if ((e.PropertyName == "IsChecked") && MyDataContext.IsChecked)
{
var randomProvider = new Random();
MyDataContext.NameList.Add(randomProvider.Next().ToString());
MyDataContext.OnPropertyChanged("NameList");
}
}
public App()
{
MyDataContext = new DataContextClass();
MyDataContext.PropertyChanged += HandleDataContextPropertyChange;
MyWindow = new MainWindow {DataContext = MyDataContext};
MyWindow.InitializeComponent();
MyWindow.Show();
MyDataContext.NameList = new List<string>();
MyDataContext.NameList.Add("FirstName");
MyDataContext.NameList.Add("SecondName");
MyDataContext.NameList.Add("ThirdName");
MyDataContext.IsChecked = true;
Thread.Sleep(3000);
MyDataContext.IsChecked = false;
Thread.Sleep(3000);
MyDataContext.IsChecked = true;
}
}
}
当我启动应用程序时,出现以下 Window,一旦应用程序构造函数点击 .Show
:
App 构造器完成后,Window 会更新一次,但之后不会再更新,无论向 NameList
添加了多少字符串:
任何想法,为什么我的双向绑定只能在一个方向上工作?
如果绑定集合未实现 INotifyCollectionChanged
(例如 ObservableCollection<T>
),您将在尝试更新视图时遇到不一致或不存在的行为。我注意到在将检查状态切换为 true 后轻弹鼠标滚轮时列表确实会更新。另外,正如@Clemens 所说,您的 ItemsSource
绑定应该是 Mode=TwoWay
因为这是唯一有意义的模式。
顺便说一句,无论如何你都应该使用符合 INotifyCollectionChanged
的集合,因为如果你在完成后不清除绑定,你可能 运行 进入泄漏[1] .这在您的单一 window 应用程序中不是问题,但现在值得一提。
至于 IsChecked
在睡眠之间切换,我有根据的猜测是 Thread.Sleep
发生在 UI 线程上(因此将其捆绑),所以你有6 秒的停滞时间,其中 PropertyChanged
是无用的。我能够通过以下方法解决这个问题(假设使用了正确的集合类型):
private async void Toggle()
{
MyDataContext.IsChecked = true;
await Task.Delay(3000);
MyDataContext.IsChecked = false;
await Task.Delay(3000);
MyDataContext.IsChecked = true;
}
并在 App
构造函数的末尾调用 Toggle()
。不幸的是,这导致应用程序尝试从另一个不起作用的线程修改集合。然后你可以用一些荒谬的东西来解决that:
...
Toggle(Application.Current.Dispatcher);
}
private async void Toggle(System.Windows.Threading.Dispatcher d)
{
d.Invoke(() => { MyDataContext.IsChecked = true; });
await Task.Delay(3000);
d.Invoke(() => { MyDataContext.IsChecked = false; });
await Task.Delay(3000);
d.Invoke(() => { MyDataContext.IsChecked = true; });
}
但这只是强化了你程序的糟糕结构。
编辑:我忘了提到使用 async/await 有释放 UI 线程的额外好处;它不再在检查状态之间锁定您的整个 window。
我建议您将代码分离到适当的文件中,然后将逻辑分离到适当的位置。您的 HandleDataContextPropertyChange
可以在 IsChecked
的 setter 内进行,减去 Notify 调用。
[1] https://blog.jetbrains.com/dotnet/2014/09/04/fighting-common-wpf-memory-leaks-with-dotmemory/
即使在审查了大量的解决方案建议后,我也无法在 xaml 中使用简单的双向绑定。我有一个 Window、一个 dataContext 和一个 App。问题是:
a) 当 App 构造函数运行时,Window(在同一个构造函数中初始化和 .Show
-ed)出现,但根本没有更新,即使我切换了复选框我的 C# 代码中的值几次;
b) 当 App 构造函数完成时,Window 只更新一次;我已将其设置为如果我单击 Window 中的复选框,App 中的事件处理程序(绑定到 DataContext 属性 更改通知)应该增加字符串列表的大小,即也显示出来。列表的增加在代码中正确发生,但未反映在 Window.
中总结:
- Window 中的用户输入可以正常访问应用程序的 C# 代码:我可以对复选框更改等进行操作
- 相反的方向不起作用:每当通过代码更改 dataContext 中的项目时,Window 不会自动更新,即使已实现和执行 iNotifyProperty。
我希望的是:
a) 当 App 构造函数运行并切换 CheckBox 值时,Window 应通过设置/清除复选框上的勾号来反映更改;
b) App 构造函数完成后,每当我将 CheckBox 从 FALSE 切换为 TRUE 时,NameList
都会附加一个新字符串。我希望 Window 中的列表会相应增加并自动显示完整的附加 NameList
内容。
观察结果:
- 我尝试确保在 Window 上调用
InitializeComponent
之前设置 Window 上的 DataContext。不幸的是并没有真正改变... - 我在 MainWindow.xaml 文件 的 VS 中得到一条线索:CheckBox 路径和 ListBox 绑定
NameList
注释为Cannot resolve symbol due to unknown DataContext
但是,当 App 构造函数终止时 Window 被更新,当我单击 CheckBox 时,将触发正确的 NotifyProperty 事件。这告诉我运行时绑定实际上应该工作......显然只是单向的,而不是双向的。
MainWindow.xaml:
<Window x:Class="StatisticsEvaluation.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<StackPanel Orientation="Vertical">
<CheckBox IsChecked="{Binding Path=IsChecked, Mode=TwoWay}" Content="CheckBox" />
<ListBox ItemsSource="{Binding NameList, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBlock FontSize="18" FontFamily="Arial" Foreground="Black" Text="TextBlock" Visibility="Visible" />
</StackPanel>
</Grid>
MainWindow.xaml.cs:
namespace StatisticsEvaluation
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
}
}
}
应用程序和 DataContext:
namespace StatisticsEvaluation
{
public class DataContextClass : INotifyPropertyChanged
{
private bool isChecked;
public bool IsChecked
{
get
{
return isChecked;
}
set
{
isChecked = value;
OnPropertyChanged("IsChecked");
}
}
private List<string> nameList;
public List<string> NameList
{
get
{
return nameList;
}
set
{
nameList = value;
OnPropertyChanged("NameList");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
private MainWindow MyWindow { get; set; }
private DataContextClass MyDataContext{ get; set; }
private void HandleDataContextPropertyChange(object sender, PropertyChangedEventArgs e)
{
// If the CheckBox was just toggled to TRUE, increase the NameList
// with an additional name and call OnPropertyChanged on it ...
// hoping that this would trigger a Window UI update - but no luck !
if ((e.PropertyName == "IsChecked") && MyDataContext.IsChecked)
{
var randomProvider = new Random();
MyDataContext.NameList.Add(randomProvider.Next().ToString());
MyDataContext.OnPropertyChanged("NameList");
}
}
public App()
{
MyDataContext = new DataContextClass();
MyDataContext.PropertyChanged += HandleDataContextPropertyChange;
MyWindow = new MainWindow {DataContext = MyDataContext};
MyWindow.InitializeComponent();
MyWindow.Show();
MyDataContext.NameList = new List<string>();
MyDataContext.NameList.Add("FirstName");
MyDataContext.NameList.Add("SecondName");
MyDataContext.NameList.Add("ThirdName");
MyDataContext.IsChecked = true;
Thread.Sleep(3000);
MyDataContext.IsChecked = false;
Thread.Sleep(3000);
MyDataContext.IsChecked = true;
}
}
}
当我启动应用程序时,出现以下 Window,一旦应用程序构造函数点击 .Show
:
App 构造器完成后,Window 会更新一次,但之后不会再更新,无论向 NameList
添加了多少字符串:
任何想法,为什么我的双向绑定只能在一个方向上工作?
如果绑定集合未实现 INotifyCollectionChanged
(例如 ObservableCollection<T>
),您将在尝试更新视图时遇到不一致或不存在的行为。我注意到在将检查状态切换为 true 后轻弹鼠标滚轮时列表确实会更新。另外,正如@Clemens 所说,您的 ItemsSource
绑定应该是 Mode=TwoWay
因为这是唯一有意义的模式。
顺便说一句,无论如何你都应该使用符合 INotifyCollectionChanged
的集合,因为如果你在完成后不清除绑定,你可能 运行 进入泄漏[1] .这在您的单一 window 应用程序中不是问题,但现在值得一提。
至于 IsChecked
在睡眠之间切换,我有根据的猜测是 Thread.Sleep
发生在 UI 线程上(因此将其捆绑),所以你有6 秒的停滞时间,其中 PropertyChanged
是无用的。我能够通过以下方法解决这个问题(假设使用了正确的集合类型):
private async void Toggle()
{
MyDataContext.IsChecked = true;
await Task.Delay(3000);
MyDataContext.IsChecked = false;
await Task.Delay(3000);
MyDataContext.IsChecked = true;
}
并在 App
构造函数的末尾调用 Toggle()
。不幸的是,这导致应用程序尝试从另一个不起作用的线程修改集合。然后你可以用一些荒谬的东西来解决that:
...
Toggle(Application.Current.Dispatcher);
}
private async void Toggle(System.Windows.Threading.Dispatcher d)
{
d.Invoke(() => { MyDataContext.IsChecked = true; });
await Task.Delay(3000);
d.Invoke(() => { MyDataContext.IsChecked = false; });
await Task.Delay(3000);
d.Invoke(() => { MyDataContext.IsChecked = true; });
}
但这只是强化了你程序的糟糕结构。 编辑:我忘了提到使用 async/await 有释放 UI 线程的额外好处;它不再在检查状态之间锁定您的整个 window。
我建议您将代码分离到适当的文件中,然后将逻辑分离到适当的位置。您的 HandleDataContextPropertyChange
可以在 IsChecked
的 setter 内进行,减去 Notify 调用。
[1] https://blog.jetbrains.com/dotnet/2014/09/04/fighting-common-wpf-memory-leaks-with-dotmemory/