WPF 绑定未更新 XAML 但调用了 PropertyChanged
WPF Binding not updating XAML but PropertyChanged called
我正在尝试将我的 XAML 中的 WPF UI 元素的可见性绑定到我的视图模型 (MainViewModel.DisplayPopup
) 的 属性,该元素已更新来自另一个视图模型 (ContactViewModel
) 的 属性,它是此 class (MainViewModel
) 的 属性。 BaseViewModel
class 扩展 INotifyPropertyChanged
并使用 自动调用每个 属性 的 setter 内的 PropertyChanged 事件的 Nuget 包。 =46=]
这是我的视图模型代码:
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
具有相同的状态,删除它。
<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
的 属性 更改].因此,“文本”绑定永远不会更新它的值。
要解决此问题,请将 MainViewModel
中的 DisplayPopup
属性 设为简单的 get-set 属性。你不需要计算它。创建第二个 属性 以绑定 MainViewModel
中 ListBox
的 SelectedItem
。 This 属性 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
上的SelectedItem
绑定到SelectedTankItem
。
<ListBox Grid.Column="0" ItemsSource="{Binding TankItems}" SelectedItem="{Binding SelectedTankItem}">
<!-- ...other code. -->
</ListBox>
您可以通过删除 属性 更改的代码来简化您的基本视图模型。您不需要它,因为该属性会让 Fody 为您实现它。
[AddINotifyPropertyChangedInterfaceAttribute]
public class BaseViewModel : INotifyPropertyChanged
{
}
DisplayPopup
的绑定未更新的原因是 属性 是计算的 属性。计算出的 属性 缺少 setter,因此永远不会引发 INotifyPropertyChanged.PropertyChanged
。数据绑定侦听此事件。结果 DisplayPopup.Get
仅调用一次(绑定初始化时)。
要解决这个问题,您可以让 MainViewModel
监听 ContactViewModel
项目的 PropertyChanged
事件,或者因为您似乎对所选项目感兴趣,只需绑定 ListBox.SelectedItem
并在更改时更改 MainViewModel.DisplayPopup
。
为简单起见,我推荐第二种解决方案。
请注意,为了使 ListBox.IsSelected
绑定有效,您必须设置 ListBox.ItemContainerStyle
和目标 ListBoxItem
而不是 ListBox
:
MainViewModel.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()
};
}
}
MainWindow.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>
我正在尝试将我的 XAML 中的 WPF UI 元素的可见性绑定到我的视图模型 (MainViewModel.DisplayPopup
) 的 属性,该元素已更新来自另一个视图模型 (ContactViewModel
) 的 属性,它是此 class (MainViewModel
) 的 属性。 BaseViewModel
class 扩展 INotifyPropertyChanged
并使用 自动调用每个 属性 的 setter 内的 PropertyChanged 事件的 Nuget 包。 =46=]
这是我的视图模型代码:
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
具有相同的状态,删除它。
<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
的 属性 更改].因此,“文本”绑定永远不会更新它的值。
要解决此问题,请将 MainViewModel
中的 DisplayPopup
属性 设为简单的 get-set 属性。你不需要计算它。创建第二个 属性 以绑定 MainViewModel
中 ListBox
的 SelectedItem
。 This 属性 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
上的SelectedItem
绑定到SelectedTankItem
。
<ListBox Grid.Column="0" ItemsSource="{Binding TankItems}" SelectedItem="{Binding SelectedTankItem}">
<!-- ...other code. -->
</ListBox>
您可以通过删除 属性 更改的代码来简化您的基本视图模型。您不需要它,因为该属性会让 Fody 为您实现它。
[AddINotifyPropertyChangedInterfaceAttribute]
public class BaseViewModel : INotifyPropertyChanged
{
}
DisplayPopup
的绑定未更新的原因是 属性 是计算的 属性。计算出的 属性 缺少 setter,因此永远不会引发 INotifyPropertyChanged.PropertyChanged
。数据绑定侦听此事件。结果 DisplayPopup.Get
仅调用一次(绑定初始化时)。
要解决这个问题,您可以让 MainViewModel
监听 ContactViewModel
项目的 PropertyChanged
事件,或者因为您似乎对所选项目感兴趣,只需绑定 ListBox.SelectedItem
并在更改时更改 MainViewModel.DisplayPopup
。
为简单起见,我推荐第二种解决方案。
请注意,为了使 ListBox.IsSelected
绑定有效,您必须设置 ListBox.ItemContainerStyle
和目标 ListBoxItem
而不是 ListBox
:
MainViewModel.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()
};
}
}
MainWindow.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>