WPF记录没有';即使使用INotifyPropertyChanged也无法更新

本文关键字:INotifyPropertyChanged 更新 记录 WPF | 更新日期: 2023-09-27 17:57:32

我在组合框中设置了一个患者集合,每当我运行和测试表单时,它似乎很好,但每当我更新记录或添加另一个记录(来自另一个表单)时,组合框都不会更新。我可以通过使用代码隐藏和IContent接口来解决这个问题,但我喜欢尽可能减少代码隐藏的使用。你能告诉我缺少什么标记或代码吗?

这是我的网格标记(我省略了RowDefinitions和ColumnDefinitions)

<UserControl.Resources>
        <common:ImageSourceConverter x:Key="ToImageSourceConverter" />
        <businessLogic:PatientMgr x:Key="PatientsViewModel" />
        <ObjectDataProvider x:Key="ItemsSource" ObjectInstance="{StaticResource PatientsViewModel}" />
</UserControl.Resources>
<Grid x:Name="DetailsGrid" DataContext="{StaticResource ItemsSource}">     
    <Border Grid.Row="1" Grid.RowSpan="8" Grid.Column="0" Grid.ColumnSpan="2" BorderBrush="Turquoise" BorderThickness="2">
        <Image x:Name="InsideButtonImage"
                   VerticalAlignment="Center" 
                   HorizontalAlignment="Center" 
                   StretchDirection="Both" Stretch="Uniform" 
                   Source="{Binding ElementName=FullNameComboBox, Path=SelectedItem.PictureId, Converter={StaticResource ToImageSourceConverter}, UpdateSourceTrigger=PropertyChanged}" 
                   />
    </Border>

    <TextBox x:Name="IdTextBox" Text="{Binding ElementName=FullNameComboBox, Path=SelectedItem.Id, UpdateSourceTrigger=PropertyChanged}" Grid.Row="0" Grid.Column="1" Visibility="Collapsed"/>
    <ComboBox x:Name="FullNameComboBox" Grid.Row="10"  Grid.Column="1" IsEditable="True"
              ItemsSource="{Binding Path=ComboBoxItemsCollection, UpdateSourceTrigger=PropertyChanged}"
              DisplayMemberPath = "FullName"
              SelectedIndex="0"
              SelectionChanged="FullNameComboBox_OnSelectionChanged"
              />
    <TextBox x:Name="GenderTextBox" Text="{Binding ElementName=FullNameComboBox, Path=SelectedItem.GenderName, UpdateSourceTrigger=PropertyChanged}" Grid.Row="11" Grid.Column="1" IsReadOnly="True" IsReadOnlyCaretVisible="True"/>
</Grid>

这是我的BusinessLogicLayer

public class PatientMgr :INotifyPropertyChanged
{
    #region Fields
    private readonly PatientDb _db;
    private Patient _entity;
    private List<Patient> _entityList;        
    private ObservableCollection<Patient> _comboBoxItemsCollection;
    private Patient _selectedItem;
    #endregion

    #region Properties
    public Patient Entity
    {
        get { return _entity; }
        set
        {
            if (Equals(value, _entity)) return;
            _entity = value;
            OnPropertyChanged();
        }
    }
    public List<Patient> EntityList
    {
        get { return _entityList; }
        set
        {
            if (Equals(value, _entityList)) return;
            _entityList = value;
            OnPropertyChanged();
        }
    }       
    public ObservableCollection<Patient> ComboBoxItemsCollection
    {
        get { return _comboBoxItemsCollection; }
        set
        {
            if (Equals(value, _comboBoxItemsCollection)) return;
            _comboBoxItemsCollection = value;
            OnPropertyChanged();
        }
    }
    public Patient SelectedItem
    {
        get { return _selectedItem; }
        set
        {
            if (Equals(value, _selectedItem)) return;
            _selectedItem = value;
            OnPropertyChanged();
        }
    }
    #endregion

    #region Constructor
    public PatientMgr()
    {
        _db = new PatientDb();
        Entity = new Patient();
        EntityList = new List<Patient>();
        Parameters = new Patient();
        ComboBoxItemsCollection = new ObservableCollection<Patient>(_db.RetrieveMany(Entity));
        SelectedItem = ComboBoxItemsCollection[0];
    }
    #endregion

    public List<Patient> RetrieveMany(Patient parameters)
    {
        return _db.RetrieveMany(parameters);
    }

    public event PropertyChangedEventHandler PropertyChanged;
    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

WPF记录没有';即使使用INotifyPropertyChanged也无法更新

因为ItemsSource是静态使用的,所以当ComboBox的源更改时,它不会得到通知。尝试使用PatientMgr的实例作为UserControl.DataContext,如下所示:

public partial class YourUserControl: UserControl
{
    private PatientMgr PatientsViewModel;
    public YourUserControl()
    {
        InitializeComponent();
        PatientsViewModel = new PatientMgr();
        DataContext = PatientsViewModel;
    }
    public void AddComboItem(Patient item)
    {
        PatientsViewModel.ComboBoxItemsCollection.Add(item);
    }
    public void UpdateComboItem(Patient item)
    {
        //your update code
    }

删除静态绑定后,XAML应该如下所示:

<UserControl.Resources>
        <common:ImageSourceConverter x:Key="ToImageSourceConverter" />
</UserControl.Resources>
<Grid x:Name="DetailsGrid"      
    <Border Grid.Row="1" Grid.RowSpan="8" Grid.Column="0" Grid.ColumnSpan="2" BorderBrush="Turquoise" BorderThickness="2">
        <Image x:Name="InsideButtonImage" ...

由于ComboBoxItemsCollectionObservableCollection,它会在添加/删除时自动通知UI,但您必须编写适当的更新功能才能强制UI刷新。

编辑:

要利用ComboBoxEditable功能来实现更新方法,可以执行以下步骤:

  1. 定义了一个整数属性来跟踪组合框所选项的最后一个有效索引(index!=-1),并将其绑定到FullNameComboBox.SelectedIndex
  2. 定义了一个字符串属性,表示组合框所选项目的文本值,并将其绑定到FullNameComboBox.Text(绑定应在LostFocus上触发,因此只有当用户完成键入时才接受更改)
  3. 查找并删除ComboBoxItemsCollection.OldItem(按最后一个有效索引查找),并在FullNameComboBox.Text更改时插入ComboBoxItemsCollection.ModifiedItem。使用remove和insert而不是赋值(OldItem = ModifiedItem;)将强制UI更新

PatientMgr代码中:

public int LastValidIndex
{
    get { return _lastIndex; }
    set
    {
        if (value == -1) return;
        _lastIndex = value;
        OnPropertyChanged();
    }
}
public string CurrentFullName
{
    get
    {
        return SelectedItem.FullName;
    }
    set
    {
        var currentItem = SelectedItem;
        ComboBoxItemsCollection.RemoveAt(LastValidIndex);
        currentItem.FullName = value;
        ComboBoxItemsCollection.Insert(LastValidIndex, currentItem);
        SelectedItem = currentItem;
    }
}

UserControl.Xaml:中

<ComboBox x:Name="FullNameComboBox" Grid.Row="1" IsEditable="True"
      ItemsSource="{Binding Path=ComboBoxItemsCollection, 
      UpdateSourceTrigger=PropertyChanged}"
      SelectedItem="{Binding SelectedItem}"
      SelectedIndex="{Binding LastValidIndex}"
      IsTextSearchEnabled="False"
      Text="{Binding CurrentFullName, UpdateSourceTrigger=LostFocus}"
      DisplayMemberPath = "FullName"
      />

我不喜欢这个:

public ObservableCollection<Patient> ComboBoxItemsCollection
    {
        get { return _comboBoxItemsCollection; }
        set
        {
            if (Equals(value, _comboBoxItemsCollection)) return;
            _comboBoxItemsCollection = value;
            OnPropertyChanged();
        }
    }

试试这个:

OnPropertyChanged(“ComboBoxItemsCollection”);

而且,你确定这个平等问题已经解决了吗?

if (Equals(value, _comboBoxItemsCollection)) return;

试着调试它…

您的问题与组合框上设置的IsEditable="True"有关。你的装订对我来说很好。

这是我刚刚尝试的:

具有FullName属性的简单患者模型:

public class Patient : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged = delegate { };
    private string _FullName;
    public string FullName
    {
        get { return _FullName; }
        set
        {
            _FullName = value;
            PropertyChanged(this, new PropertyChangedEventArgs("FullName"));
        }
    }
}

这是XAML:

<Window x:Class="PatientsStack.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:PatientsStack"
    Title="MainWindow" Height="350" Width="525">
<Window.Resources>
    <local:PatientsMgr x:Key="PatientsViewModel"/>
    <ObjectDataProvider x:Key="ItemsSource" ObjectInstance="{StaticResource PatientsViewModel}" />
</Window.Resources>
<Grid DataContext="{StaticResource ItemsSource}">
    <StackPanel>
        <ComboBox x:Name="FullNameComboBox" IsEditable="True"
                  ItemsSource="{Binding Path=ComboBoxItemsCollection, UpdateSourceTrigger=PropertyChanged}"
                  TextSearch.TextPath="FullName"
                  DisplayMemberPath="FullName"
                  SelectedItem="{Binding SelectedPatient}" 
                  Text="{Binding SelectedPatient.FullName, Mode=TwoWay}"
                  VerticalAlignment="Top"
                  IsTextSearchEnabled="False"
                  SelectedIndex="0">
        </ComboBox>
        <Button Content="Change name" Command="{Binding ChangeNameCommand}" CommandParameter="{Binding ElementName=FullNameComboBox, Path=SelectedItem}"/>
        <Button Content="Add patient" Command="{Binding AddPatientCommand}" CommandParameter="{Binding ElementName=FullNameComboBox, Path=SelectedItem}"/>
    </StackPanel>
</Grid>

这两块就完成了任务:

SelectedItem="{Binding SelectedFilter}" 
Text="{Binding SelectedPatient.FullName, Mode=TwoWay}"

如果没有文本绑定,更新后的内容会起作用,但除非单击下拉菜单,否则您不会看到它。

如您所见,我有两个命令来更改一个现有患者的全名或添加一个新的全名。两者都如预期的那样工作。

这是这个的ViewModel:

public class PatientsMgr :  INotifyPropertyChanged
{
    private ObservableCollection<Patient> _ComboBoxItemsCollection;
    public ObservableCollection<Patient> ComboBoxItemsCollection
    {
        get
        {
            return _ComboBoxItemsCollection;
        }
        set
        {
            _ComboBoxItemsCollection = value;
            PropertyChanged(this, new PropertyChangedEventArgs("ComboBoxItemsCollection"));
        }
    }
    private Patient _SelectedPatient;
    public Patient SelectedPatient
    {
        get { return _SelectedPatient; }
        set
        {
            _SelectedPatient = value;
            PropertyChanged(this, new PropertyChangedEventArgs("SelectedPatient"));
        }
    }

    public ICommand ChangeNameCommand { get; set; }
    public ICommand AddPatientCommand { get; set; }
    public event PropertyChangedEventHandler PropertyChanged = delegate { };
    public PatientsMgr()
    {
        ComboBoxItemsCollection = new ObservableCollection<Patient>();
        ComboBoxItemsCollection.Add(new Patient() { FullName = "Patient1" });
        ComboBoxItemsCollection.Add(new Patient() { FullName = "Patient2" });
        ComboBoxItemsCollection.Add(new Patient() { FullName = "Patient3" });
        ChangeNameCommand = new RelayCommand<Patient>(ChangePatientName);
        AddPatientCommand = new RelayCommand<Patient>(AddPatient);
    }

    public void ChangePatientName(Patient patient)
    {
        patient.FullName = "changed at request";
    }
    public void AddPatient(Patient p)
    {
        ComboBoxItemsCollection.Add(new Patient() { FullName = "patient added" });
    }
}

我也在发布我的RelayCommand,但我相信你已经为你的行为定义了它:

public class RelayCommand<T> : ICommand
{
    public Action<T> _TargetExecuteMethod;
    public Func<T, bool> _TargetCanExecuteMethod;
    public RelayCommand(Action<T> executeMethod)
    {
        _TargetExecuteMethod = executeMethod;
    }
    public bool CanExecute(object parameter)
    {
        if (_TargetExecuteMethod != null)
            return true;
        return false;
    }
    public event EventHandler CanExecuteChanged;
    public void Execute(object parameter)
    {
        T tParam = (T)parameter;
        if (_TargetExecuteMethod != null)
            _TargetExecuteMethod(tParam);
    }
}