简体   繁体   English

WPF 绑定不更新 XAML 但调用了 PropertyChanged

[英]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 ). 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 )。 The BaseViewModel class extends INotifyPropertyChanged and uses a Nuget package that automatically calls the PropertyChanged event inside of each property's setter. BaseViewModel class 扩展了INotifyPropertyChanged并使用 Nuget package自动调用每个属性设置器内部的 PropertyChanged 事件。

This is my view model code:这是我的看法 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;            
}

This is my XAML:这是我的 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>

What I expect to happen is when I click one of the ListBox items, DisplayPopup becomes true , but it stays the same.我期望发生的是当我单击ListBox项目之一时, DisplayPopup变为true ,但它保持不变。 HOWEVER, if I log the value of ((MainViewModel)DataContext).DisplayPopup) I get the correct value - false in the beginning then true when the selection is changed.但是,如果我记录((MainViewModel)DataContext).DisplayPopup)的值,我会得到正确的值 - 开始时为false ,然后在更改选择时为true

Why is the binding value not updating?为什么绑定值不更新?

Update更新

Here is the BaseViewModel , which uses Fody PropertyChanged这是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));
        }
    }
}

输出

Binding Selected State in Tank Items在坦克物品中绑定选定的 State

There are multiple issues in your code.您的代码中有多个问题。 Let's start with the ContactViewModel .让我们从ContactViewModel开始。

public bool IsSelected {get => _isSelected; set { _isSelected = value; DisplayPopup = value;   

The DisplayPopup property is redundant, as it is has the same state as IsSelected , remove it. DisplayPopup属性是多余的,因为它具有与 IsSelected 相同的IsSelected ,请将其删除。

<ListBox.Style>
   <Style TargetType="ListBox">
      <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
   </Style>
</ListBox.Style>

This XAML is wrong.这个 XAML 是错误的。 You have to bind the IsSelected property of a ListBoxItem , not the ListBox .您必须绑定ListBoxItemIsSelected属性,而不是ListBox Plus you bind to an IsSelected property on MainViewModel , as this is the DataContext here.另外,您绑定到MainViewModel上的IsSelected属性,因为这是此处的DataContext This property does not exist, so the binding will not work.该属性不存在,因此绑定将不起作用。 Instead use an ItemContainerStyle .而是使用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>

Updating the Display Popup Property更新显示弹出属性

You want to set DisplayPopup on MainViewModel to true , if any item is selected.如果选择了任何项目,您希望将MainViewModel上的DisplayPopup设置为true The item container style above will set the IsSelected property of your ContactViewModel , but it does not automatically trigger a property change of DisplayPopup in MainViewModel .上面的项目容器样式将设置您的ContactViewModelIsSelected属性,但它不会自动触发MainViewModelDisplayPopup的属性更改。 Hence, the "Text" binding will never update its value.因此,“Text”绑定永远不会更新它的值。

To solve this, make the DisplayPopup property in MainViewModel a simple get-set property.要解决此问题,请将MainViewModel中的DisplayPopup属性设为简单的 get-set 属性。 You do not need to compute it.你不需要计算它。 Create a second property to bind the SelectedItem of the ListBox in MainViewModel .创建第二个属性以绑定MainViewModelListBoxSelectedItem This property will get set, when the selection changes. This property will get set, when the selection changes.

public bool DisplayPopup { get; set; } 
public ContactViewModel SelectedTankItem { get; set; }

Additionally, create a method called OnSelectedTankItemChanged where you set the DisplayPopup property depending on SelectedTankItem .此外,创建一个名为OnSelectedTankItemChanged的方法,您可以在其中根据SelectedTankItem设置DisplayPopup属性。 This method will automatically be called by the Fody framework, when SelectedTankItem changes.SelectedTankItem更改时,Fody 框架将自动调用此方法。

public void OnSelectedTankItemChanged()
{
   DisplayPopup = SelectedTankItem != null;
}

Then bind the SelectedItem on your ListBox to SelectedTankItem .然后将ListBox上的SelectedTankItem SelectedItem

<ListBox Grid.Column="0" ItemsSource="{Binding TankItems}" SelectedItem="{Binding SelectedTankItem}">
   <!-- ...other code. -->
</ListBox>

You can simplify your base view model by removing the property changed code.您可以通过删除属性更改代码来简化基本视图 model。 You do not need it, since the attribute will cause Fody to implement it for you.您不需要它,因为该属性将导致 Fody 为您实现它。

[AddINotifyPropertyChangedInterfaceAttribute]
public class BaseViewModel : INotifyPropertyChanged
{
}

The reason why the binding to DisplayPopup does not update is that the property is a computed property.DisplayPopup的绑定不更新的原因是该属性是计算属性。 A computed property lacks a setter and therefore never raises INotifyPropertyChanged.PropertyChanged .计算属性缺少设置器,因此永远不会引发INotifyPropertyChanged.PropertyChanged The data binding listens to this event.数据绑定侦听此事件。 As the result DisplayPopup.Get is only called once (the moment the binding is initialized).结果DisplayPopup.Get只被调用一次(绑定初始化的那一刻)。

To solve this you can either let the MainViewModel listen to PropertyChanged events of the ContactViewModel items or as it seems that you are interested in selected items simply bind the ListBox.SelectedItem and change MainViewModel.DisplayPopup on changes.要解决此问题,您可以让MainViewModel监听ContactViewModel项目的PropertyChanged事件,或者您似乎对选定项目感兴趣,只需绑定ListBox.SelectedItem并在更改时更改MainViewModel.DisplayPopup

For simplicity I recommend the second solution.为简单起见,我推荐第二种解决方案。

Note that in order to make the ListBox.IsSelected binding work you must set the ListBox.ItemContainerStyle and target ListBoxItem instead of ListBox :请注意,为了使ListBox.IsSelected绑定起作用,您必须设置ListBox.ItemContainerStyle和目标ListBoxItem而不是ListBox

MainViewModel.cs主视图模型.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主窗口.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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM