【问题标题】:WPF MVVM passing data between viewmodel but property not updatingWPF MVVM 在视图模型之间传递数据但属性未更新
【发布时间】:2019-08-31 02:46:43
【问题描述】:

我正在制作一个窗口来管理使用笔记本电脑的用户。我有一个名为“LaptopWindow”的窗口,其中包含一个文本框来显示使用它的用户的用户 ID。我制作了一个按钮来打开名为“FindEmployeeUC”的新用户控件,通过选择用户控件的 DataGrid 中的行并将其传递回“笔记本电脑窗口”中的文本框来查找“EmpID”。

我获得了 DataGrid 的选定行,并使用属性名称“SelectedUA”将其保存在视图模型“UserAccountViewModel”中。

当 OnPropertyChanged 事件触发时,我调用“LaptopManagementViewModel”实例(此视图模型与“LaptopWindow”绑定)并通过名为“ReceiverID”的属性将 EmpID 设置为“LaptopWindow”中的 TextBox

“ReceiverID”属性获得了值,但“LaptopWindow”的 UI 没有更新。

我尝试使用Delegate,Singleton模式,结果相同。

这里有一些代码来解释我所面临的更多问题

  • “LaptopWindow”xaml:
<StackPanel Grid.Row="2" Grid.Column="1" Style="{StaticResource inputControl}">
      <TextBlock Text="Người nhận"/>
      <TextBox Name="txtReceiver" Text="{Binding ReceiverID,Source={StaticResource vmLaptopManagement}}" Margin="0,0,30,0"/>   
</StackPanel>

<!--Button open FindEmpUC -->
<Button Grid.Row="2" Grid.Column="1"  Width="30" Height="29" HorizontalAlignment="Right" VerticalAlignment="Bottom" Background="Transparent" Margin="0,4,4,4" Command="{Binding CmdFindEmp}">
      <Image Source="/imgs/find-48.png" Stretch="Uniform"  />
</Button>
  • “LaptopManagementViewModel”:
//the userAccountVM
      UserAccountViewModel userAccountVM;

//the constructor
  public LaptopManagementViewModel(UserAccountViewModel userAccountVM)
        {
            LstDVUS = LaptopManagementBLL.Instance.GetDVUsageStatuses();           
            LstLaptop = LaptopManagementBLL.Instance.GetLaptopsInfo();

            this.userAccountVM = userAccountVM;
            ReceiverID = this.userAccountVM.SelectedUA.EmpID;
        }
//the ReceiverID property
        string receiverID;

        public string ReceiverID
        {
            get { return receiverID; }
            set
            {
                receiverID = value;
                OnPropertyChanged("ReceiverID");
            }
        }
//function open FindEmployeeUC
 private void FindEmployee(object obj)
        {
            //show findEmployee UC
            Window wd = new Window()
            {
                Content = new FindEmployeeUC(),                
            };            
            wd.ShowDialog();

        }
  • “FindEmployeeUC”xaml:
 <DataGrid Grid.Row="1" ItemsSource="{Binding LstUA}" CanUserAddRows="False" SelectedItem="{Binding SelectedUA,Mode=TwoWay}" AutoGenerateColumns="False" ColumnWidth="*" IsReadOnly="True">
            <DataGrid.Columns>
                <DataGridTextColumn Header="ID" Binding="{Binding ID}"></DataGridTextColumn>
                <DataGridTextColumn Header="EmpID" Binding="{Binding EmpID}"></DataGridTextColumn>
                <DataGridTextColumn Header="EmpName" Binding="{Binding EmpName}"></DataGridTextColumn>
                <DataGridTextColumn Header="Position" Binding="{Binding Position}"></DataGridTextColumn>
                <DataGridTextColumn Header="LineGroup" Binding="{Binding LineGroup}"></DataGridTextColumn>
            </DataGrid.Columns>
        </DataGrid>
  • “UserAccountViewModel”:
//The property "SelectedUA"
        UserAccountModel selectedUA;

        public UserAccountModel SelectedUA
        {
            get { return selectedUA; }
            set
            {
                if(selectedUA!=value)
                {
                    selectedUA = value;
                    LaptopManagementViewModel laptopVM = new LaptopManagementViewModel(this);

                    OnPropertyChanged("SelectedUA");

                }

            }
        }

我希望在“LaptopWindow”中获得 TextBox 的 EmpID。我附上一张图片以获取更多详细信息: 提前致谢!

【问题讨论】:

    标签: wpf mvvm


    【解决方案1】:

    在您的OnPropertyChanged 事件调用器中,您总是在创建UserAccountViewModel 的新实例。此实例从未在您的 XAML 代码中引用,因此您的视图看不到此新实​​例。
    由于视图模型具有状态,因此您通常使用单个实例作为绑定目标。

    我删除了参数化构造函数以启用 XAML 中的实例化(实例从 XAML 分配给 UserAccountVM 属性),并且还删除了从 UserAccountViewModel 中对 LaptopManagementViewModel 的引用。我创建了视图模型实例并将它们添加到 App.xaml 的ResourceDictionary。 我还在LaptopManagementViewModel 中添加了一个PropertyChanged 事件处理程序来监听UserAccountViewModel.SelectedUA 的变化。

    强烈建议避免使用字符串文字。而不是调用OnPropertyChanged("MyProperty"),您应该通过应用nameof()OnPropertyChanged(nameof(MyClass.MyProperty)) 来使用免费的编译器支持。我替换了相应的代码。您现在可以摆脱拼写错误并获得对编译器检查和重构工具(例如重命名)的全面支持。

    也远离单身人士。它们闻起来很香。

    最后的抱怨:使字段始终为private(或protected),尤其是当它们是属性支持字段时。如果您不使用任何访问修饰符,则 internal 将隐式应用。这相当于共享程序集中的public,并且永远不应该公开字段。

    Microsoft Docs recommends

    通常,您应该只将字段用于具有私有或受保护可访问性的变量。您的类向客户端代码公开的数据应通过方法、属性和索引器提供。通过使用这些结构来间接访问内部字段,您可以防止无效的输入值。存储由公共属性公开的数据的私有字段称为后备存储或后备字段。

    App.xaml

    <Application x:class="App">
      <Application.Resources>
        <ResourceDictionary>
    
          <UserAccountViewModel x:Key="UserAccountViewModel" />
    
          <LaptopManagementViewModel x:Key="LaptopManagementViewModel">
            <LaptopManagementViewModel.UserAccountVM>
              <StaticResource ResourceKey="UserAccountViewModel" />
            </LaptopManagementViewModel.UserAccountVM>
          </LaptopManagementViewModel>
    
      </Application.Resources>
    </Application>
    

    LaptopWindow.xaml

    <Window x:class="LaptopWindow">
      <Window.DataContext>
        <StaticResource ResourceKey="LaptopManagementViewModel" />
      </Window.DataContext>
    
      ...
    </Window>
    

    FindEmployeeUC.xaml

    <Window x:class="FindEmployeeUC">
      <Window.DataContext>
        <StaticResource ResourceKey="UserAccountViewModel" />
      </Window.DataContext>
    
      <DataGrid 
        ...
      </DataGrid>
    </Window>
    

    LaptopManagementViewModel.cs

    public class LaptopManagementViewModel
    {
      private UserAccountViewModel userAccountVM;
      public UserAccountViewModel UserAccountVM
      {
        get => userAccountVM; 
        set
        {
          userAccountVM = value;
          OnPropertyChanged(nameof(UserAccountVM));
    
          if (userAccountVM != null)
          {
            // Always clean up event handlers to avoid memory leaks
            userAccountVM.PropertyChanged -= UpdateReceiverIdOnPropertyChanged;
          }
    
          userAccountVM.PropertyChanged += UpdateReceiverIdOnPropertyChanged;
        }
      }
    
      // The constructor is now parameterless for the use in XAML
      public LaptopManagementViewModel()
      {
        LstDVUS = LaptopManagementBLL.Instance.GetDVUsageStatuses();
        LstLaptop = LaptopManagementBLL.Instance.GetLaptopsInfo();
      }
    
      // UserAccountVM.PropertyChanged event handler
      private void UpdateReceiverIdOnPropertyChanged(object sender, PropertyChangedEventArgs e)
      {
        if (e.PropertyName.Equals(nameof(UserAccountViewModel.SelectedUA), StringComparison.OrdinalIgnoreCase))
        {
          ReceiverID = UserAccountVM.SelectedUA.EmpID;    
        }
      }
    
      private string receiverID;
      public string ReceiverID
      {
        get { return receiverID; }
        set
        {
          receiverID = value;
          OnPropertyChanged(nameof(ReceiverID));
        }
      }
    }
    

    UserAccountViewModel.cs

    public class UserAccountViewModel
    {
      private UserAccountModel selectedUA;
      public UserAccountModel SelectedUA
      { 
        get => selectedUA;
        set
        {
          if(selectedUA!=value)
          {
            // Removed wrong creation of LaptopManagementViewModel instances
            selectedUA = value;
            OnPropertyChanged(nameof(SelectedUA));
          }
        } 
      }
    }
    

    【讨论】:

    • 感谢您的帮助。它工作得很好。我对“私有”和“受保护”访问修饰符有更多的了解。我还学习了一种在 ViewModel 之间传输数据的新方法。我搜索了一些解决方案和论坛,以找出我的问题的解决方案。他们使用 PRISM 的 EventAggregator 将数据从这个 ViewModel 传输到另一个 ViewModel 但对我来说太复杂了。
    猜你喜欢
    • 1970-01-01
    • 2011-09-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多