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是错误的。您必须绑定 ListBoxItemIsSelected 属性,而不是 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。上面的项目容器样式将设置 ContactViewModelIsSelected 属性,但它不会自动触发 MainViewModelDisplayPopup 的 属性 更改].因此,“文本”绑定永远不会更新它的值。

要解决此问题,请将 MainViewModel 中的 DisplayPopup 属性 设为简单的 get-set 属性。你不需要计算它。创建第二个 属性 以绑定 MainViewModelListBoxSelectedItem。 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>