WPF 中的双向绑定 - 缓存对象位于另一个绑定对象内

问题描述 投票:0回答:1

我无法让 WPF 中的双向绑定正常工作。

我得到的是一个名为 UserCache 的类,它有一个名为“Current”的属性。此类的对象放置在名为 ConnectedUserCache 的外观类中,该类还有一个名为“Current”的属性。

MainWindow.xaml

[...]
<Window.DataContext>
    <local:ConnectedUserCache/>
</Window.DataContext>
[...]
<Grid Grid.Row="1">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
        <RowDefinition />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition />
    </Grid.ColumnDefinitions>
    <Label Style="{StaticResource style1}" >Vorname:</Label>
    <TextBox Text="{Binding Path=Current.FirstName, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />  
    <Label Style="{StaticResource style1}" Grid.Row="1">Nachname:</Label>
    <TextBox Grid.Row="1" Text="{Binding Path=Current.LastName, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
    <Label Style="{StaticResource style1}" Grid.Row="2">Telefon:</Label>
    <TextBox Grid.Row="2" Text="{Binding Path=Current.PhoneNumber, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
</Grid>
[...]
<DataGrid ItemsSource="{Binding UsersShown}" Margin="0,30,0,-280" Grid.Row="3" Grid.RowSpan="1"
                 IsReadOnly="True"
                 Name="UserDataGrid"
                 SelectionMode="Single" SelectionUnit="FullRow"
                 AutoGeneratingColumn="UserDataGrid_AutoGeneratingColumn"
                 SelectedCellsChanged="UserDataGrid_SelectedCellsChanged"/>

MainWindow.xaml.cs

   public partial class MainWindow : Window
   {
       private ConnectedUserCache _cache = new ConnectedUserCache();

       public MainWindow()
       {
           InitializeComponent();
           DataContext = _cache;

            // Otherwise DataGrid would not be filled at the beginning
            _cache.RefreshAsync();
            ReloadDataGrid(_cache);
       }

        private void ReloadDataGrid(ConnectedUserCache? cache)
        {
            UserDataGrid.ItemsSource = null;
            UserDataGrid.ItemsSource = cache.UsersShown;
        }

       private void Button_SeachUserInCache_Click(object sender, RoutedEventArgs e)
       {
          var cache = DataContext as ConnectedUserCache;
          if (cache == null) return; 

          cache.SearchCurrentUser();

          // Reload datasource of datagrid
         UserDataGrid.ItemsSource = null;
         UserDataGrid.ItemsSource = cache.UsersShown;
      }
[...]

ConnectedUserCache.cs

using System.Collections.ObjectModel;
using System.ComponentModel;
using UserWpfClient.Http;
using UserWpfClient.LocalData;

namespace UserWpfClient
{
    internal class ConnectedUserCache : INotifyPropertyChanged
    {
        private UserCache _userCache = new UserCache();
        private HttpUserClient _userClient = new HttpUserClient();

        public ConnectedUserCache() {}
        protected virtual void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
[...]

        public event PropertyChangedEventHandler? PropertyChanged;

        public UserModelView Current
        {
           get { return _userCache.Current; }
           set 
           { 
              _userCache.Current = value; 
              OnPropertyChanged(nameof(Current));
           }
        }
[...]

        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
[...]

UserCache.cs

public class UserCache : INotifyPropertyChanged
{
    private Collection<UserModelView> _allUsers = new Collection<UserModelView>();
    private UserModelView _current =  new UserModelView();

    public event PropertyChangedEventHandler? PropertyChanged;

    public ObservableCollection<UserModelView> UsersShown { get; set; } = [];

    public UserModelView Current 
    { 
        get => _current; 
        set
        {
            _current = value;
            OnPropertyChanged(nameof(Current));
        } 
    }

    protected virtual void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    private bool GetSomeUsersByFirstName()
    {
        var result = new List<UserModelView>();
        foreach (var item in _allUsers)
        {
            if (item.FirstName == Current.FirstName) result.Add(item);
        }
        UsersShown = new ObservableCollection<UserModelView>(result);

        // Feedback to the user, for what we have been looking for
        Current = new UserModelView()
        { 
            FirstName = Current.FirstName,
            LastName = string.Empty, 
            PhoneNumber = string.Empty
        };

        return UsersShown.Any();
    }

    public bool Refresh()
    {
       UsersShown = new ObservableCollection<UserModelView>(_allUsers);
       Current = new UserModelView();
       return UsersShown.Any();
    }

[...]

UserViewModel.cs

using System.ComponentModel;
using UserWpfClient.Attributes;

namespace UserWpfClient.LocalData
{
    public class UserModelView : INotifyPropertyChanged
    {
        private string _firstName = string.Empty;
        private string _lastName = string.Empty;
        private string _phoneNumber = string.Empty;

        public event PropertyChangedEventHandler? PropertyChanged;

        [ColumnName("Vorname")]
        public string FirstName
        {
            get => _firstName ?? string.Empty;

            set
            {
                _firstName = value;
                OnPropertyChanged(nameof(FirstName));
            }
        }

        [ColumnName("Nachname")]
        public string LastName 
        { 
            get => _lastName ?? string.Empty; 
            set 
            { 
                _lastName = value; 
                OnPropertyChanged(nameof(LastName));
            }
        }

        [ColumnName("Telefon")]
        public string PhoneNumber 
        { 
            get => _phoneNumber ?? string.Empty; 
            set
            {
                _phoneNumber = value;
                OnPropertyChanged(nameof(PhoneNumber));
            } 
        }

        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

我发现了以下内容:WPF 中的双向绑定Wpf 列表框中的双向绑定。 正如您所看到的,我相应地更改了这些类(UserCache、ConnectedUserCache 和 UserModelView)。

接下来我将遵循以下步骤:https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/generators/observableproperty但是由于它减少了样板文件的数量,我想我仍然需要理解我的问题的根本原因,这意味着更好地理解 WPF 及其事件。

主窗口.xaml 在那里,我用

Mode=TwoWay
和/或
UpdateSourceTrigger=PropertyChanged
进行了多次尝试。

UserCache.cs 我在那里尝试了使用和不使用 INotifyPropertyChanged 的情况。

期望和实际发生的事情: 我希望如果这些文本框有内容,那么相关属性“Current”也有这样的内容,属性“Current”也有这样的内容 - 并且所有这些也都是相反的。

但是发生的情况是,如果我在运行时作为用户向其中一个文本框写入一些内容,例如开始在数据网格上过滤用户(绑定到后面的缓存),它工作正常。然后,在再次显示所有用户(效果很好)并输入文本框后仍然显示,当我再次开始过滤时,它应该像以前一样工作 - 因为它使用相同的代码。

但它不会再次过滤。我必须再次重写到文本框中。因此,边界似乎需要更新触发,或者换句话说,边界似乎在这个特定时刻丢失了。

问题: 所以,当我写下这些文字时,我想知道我是否找错了方向。这是绑定问题吗? 或者问题出在其他地方?在我的示例中哪些类应该属于 INotifyChanged?

我的下一步: 如果您给我一点时间,在接下来的几天里,我将公开我的 git-repositiories(WPF-Client 和 Backend-Sercice)。但是,请不要期待完美的东西。这只是为了学习而学习。

c# wpf mvvm data-binding
1个回答
0
投票

这是一个绑定问题吗?

是的,是的。因为,绑定属性

Current
不会被更新。

还是问题出在其他地方?

是的,在某种程度上确实如此。

  1. 如果您预计其他地方的代码会出现问题,为什么不对这些代码区域进行单元测试。
  2. 这个问题的根源在于对MVVM概念的误解。理解对了,这段代码可以合理测试。

在我的示例中哪些类应该属于 INotifyChanged?

我想,这里DataContext也应该考虑一下。 ICommand 或其替代品之一(如 RelaisCommand/DelegateCommand)也可以考虑在内。 有关此问题的直接答案,请参阅下文。

根本原因: 给定的问题代码可以理解为这样的层:

  • Class
    ConnectedUserCache
    用作视图模型,因为它绑定到视图。而且它还向底层类添加了 http 连接,称为
    UserCache
  • UserCache
    包含业务逻辑,应放置在视图模型中。

但应该是这样的:

  • 属性
    Current
    (或其属性)和
    UsersShown
    之间的业务逻辑应放置在同一层上,作为视图模型工作,两者都绑定到视图(
    MainWindow
    )。因此,对于两个
    DataContext
    来说,视图模型都是相同的。
  • 与数据库的http连接以及某种缓存应该放在下面。

INotifyChanged: 如果图层按照应有的方式放置(如上所述),那么定义模型视图的类将是

INotifyChanged
,如果属性
FirstName
LastName
PhoneNumber
将直接放置在那里。如果
UsersShown
类型的属性
ObservableCollection
将与其方法一起使用,则不需要另一个
INotifyChanged
。如果类
UserViewModel
(如问题中所命名)的属性不直接在模型视图上使用,则需要为
INotifyChanged

© www.soinside.com 2019 - 2024. All rights reserved.