繁体   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 )。 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));
        }
    }
}

输出

在坦克物品中绑定选定的 State

您的代码中有多个问题。 让我们从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 是错误的。 您必须绑定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的属性更改。 因此,“Text”绑定永远不会更新它的值。

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

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