[英]WPF Binding not updating XAML but PropertyChanged called
I am trying to bind the visibility of a WPF UI element in my XAML to a property of my view model ( MainViewModel.DisplayPopup
), which is updated from the property of another view model ( ContactViewModel
) which is a property of this class ( MainViewModel
)。 BaseViewModel
class 扩展了INotifyPropertyChanged
并使用 Nuget package自动调用每个属性设置器内部的 PropertyChanged 事件。
这是我的看法 model 代码:
public class MainViewModel : BaseViewModel
{
public ObservableCollection<ContactViewModel> TankItems {get; set; }
public bool DisplayPopup
{
get => TankItems.Any(contact => contact.DisplayPopup);
}
public MainViewModel() : base()
{
TankItems = new ObservableCollection<ContactViewModel>();
TankItems.Add(new ContactViewModel());
TankItems.Add(new ContactViewModel());
}
}
public class ContactViewModel : BaseViewModel
{
private bool _isSelected = false;
public bool DisplayPopup {get; set; } = false;
public bool IsSelected {get => _isSelected; set { _isSelected = value; DisplayPopup = value;
}
这是我的 XAML:
<ListBox Grid.Column="0" ItemsSource="{Binding TankItems}" SelectionChanged="List_SelectionChanged">
<ListBox.Style>
<Style TargetType="ListBox">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</ListBox.Style>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="Test" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Border BorderBrush="Orange" BorderThickness="3" Grid.Column="1">
<TextBlock Text="{Binding DisplayPopup}" /> <!-- Stays as false -->
</Border>
我期望发生的是当我单击ListBox
项目之一时, DisplayPopup
变为true
,但它保持不变。 但是,如果我记录((MainViewModel)DataContext).DisplayPopup)
的值,我会得到正确的值 - 开始时为false
,然后在更改选择时为true
。
为什么绑定值不更新?
更新
这是BaseViewModel
,它使用Fody PropertyChanged
[AddINotifyPropertyChangedInterfaceAttribute]
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = (sender, e) => { };
public void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
您的代码中有多个问题。 让我们从ContactViewModel
开始。
public bool IsSelected {get => _isSelected; set { _isSelected = value; DisplayPopup = value;
DisplayPopup
属性是多余的,因为它具有与 IsSelected 相同的IsSelected
,请将其删除。
<ListBox.Style>
<Style TargetType="ListBox">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</ListBox.Style>
这个 XAML 是错误的。 您必须绑定ListBoxItem
的IsSelected
属性,而不是ListBox
。 另外,您绑定到MainViewModel
上的IsSelected
属性,因为这是此处的DataContext
。 该属性不存在,因此绑定将不起作用。 而是使用ItemContainerStyle
。
<ListBox.ItemContainerStyle>SelectedTankItem
<Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource {x:Type ListBoxItem}}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
</Style>
</ListBox.ItemContainerStyle>
如果选择了任何项目,您希望将MainViewModel
上的DisplayPopup
设置为true
。 上面的项目容器样式将设置您的ContactViewModel
的IsSelected
属性,但它不会自动触发MainViewModel
中DisplayPopup
的属性更改。 因此,“Text”绑定永远不会更新它的值。
要解决此问题,请将MainViewModel
中的DisplayPopup
属性设为简单的 get-set 属性。 你不需要计算它。 创建第二个属性以绑定MainViewModel
中ListBox
的SelectedItem
。 This property will get set, when the selection changes.
public bool DisplayPopup { get; set; }
public ContactViewModel SelectedTankItem { get; set; }
此外,创建一个名为OnSelectedTankItemChanged
的方法,您可以在其中根据SelectedTankItem
设置DisplayPopup
属性。 当SelectedTankItem
更改时,Fody 框架将自动调用此方法。
public void OnSelectedTankItemChanged()
{
DisplayPopup = SelectedTankItem != null;
}
然后将ListBox
上的SelectedTankItem
SelectedItem
<ListBox Grid.Column="0" ItemsSource="{Binding TankItems}" SelectedItem="{Binding SelectedTankItem}">
<!-- ...other code. -->
</ListBox>
您可以通过删除属性更改代码来简化基本视图 model。 您不需要它,因为该属性将导致 Fody 为您实现它。
[AddINotifyPropertyChangedInterfaceAttribute]
public class BaseViewModel : INotifyPropertyChanged
{
}
与DisplayPopup
的绑定不更新的原因是该属性是计算属性。 计算属性缺少设置器,因此永远不会引发INotifyPropertyChanged.PropertyChanged
。 数据绑定侦听此事件。 结果DisplayPopup.Get
只被调用一次(绑定初始化的那一刻)。
要解决此问题,您可以让MainViewModel
监听ContactViewModel
项目的PropertyChanged
事件,或者您似乎对选定项目感兴趣,只需绑定ListBox.SelectedItem
并在更改时更改MainViewModel.DisplayPopup
。
为简单起见,我推荐第二种解决方案。
请注意,为了使ListBox.IsSelected
绑定起作用,您必须设置ListBox.ItemContainerStyle
和目标ListBoxItem
而不是ListBox
:
主视图模型.cs
public class MainViewModel : BaseViewModel
{
public ObservableCollection<ContactViewModel> TankItems { get; set; }
private ContactViewModel selectedTankItem;
public ContactViewModel SelectedTankItem
{
get => this.selectedTankItem;
set
{
this.selectedTankItem = value;
OnPropertyChanged(nameof(this.SelectedTankItem));
this.DisplayPopup = this.SelectedTankItem != null;
}
// Raises INotifyPropertyChanged.PropertyChanged
public bool DisplayPopup { get; set; }
public MainViewModel() : base()
{
TankItems = new ObservableCollection<ContactViewModel>()
{
new ContactViewModel(),
new ContactViewModel()
};
}
}
主窗口.xaml
<ListBox ItemsSource="{Binding TankItems}"
SelectedItem="{Binding SelectedTankItem}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected"
Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type ContactViewModel}">
<StackPanel>
<TextBlock Text="Test" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Border>
<TextBlock Text="{Binding DisplayPopup}" />
</Border>
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.