c# - 嵌套的 UserControls 通信

标签 c# wpf xaml mvvm user-controls

简而言之:我有一个 ListView,当我选择 ListView 的一个项目时,该项目应该在详细的 UserControl 中显示和编辑。
我有一个窗口 (ViewMain) 和一个用户控件 (UserControlEmployees),它有一个 ListView 和另一个用户控件 (UserControlEmployeeDetails)。 ListView 的项目由第三个 UserControl (UserControlEmployee) 显示。 UserControlEmployees 有两个依赖属性:一个 ObservableCollection (Employees) 和一个单个 Employee (SelectedEmployee)。 ViewModel 将 ObservableCollection 传递给 UserControlEmployees。 UserControlEmployees 然后将Employees 传递给ListView。 ListView 的 SelectedItem 绑定(bind)到 SelectedEmployee。
像这样的东西:
enter image description here
SelectedEmployee 也应该绑定(bind)到 UserControlEmployeeDetails。所以我尝试将 ViewModelEmployeeDetail 和 ListView 的 SelectedItem 绑定(bind)到同一个依赖属性。
我猜这个问题在 UserControlEmployees 中:
我的想法是 control.ControlEmployeesListView.SelectedItem = e.NewValue as Employee;将 SelectedItem 绑定(bind)到 SelectedEmployee。但这不起作用,我不知道我还能如何绑定(bind)它。通常我会在 XAML 中做类似的事情,但在这种情况下我无权访问它。
编辑
我注意到我忘记将 ListView SelectedItem 设置为 Binding。

        <ListView
            x:Name="ControlEmployeesListView"
            Grid.Row="0"
            SelectedItem="{Binding Mode=TwoWay}">
我解决了这个问题,但现在我得到了这个异常:
System.Windows.Markup.XamlParseException: ''在 'System.Windows.Data.Binding' 上提供值引发了异常。'行号“26”和行位置“17”。
内部异常
InvalidOperationException:双向绑定(bind)需要 Path 或 XPath。
/编辑
UserControlEmployees.xaml
<UserControl
x:Class="TestNestedUserControls.View.UserControls.UserControlEmployees"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:uc="clr-namespace:TestNestedUserControls.View.UserControls"
d:DesignHeight="25"
d:DesignWidth="300"
mc:Ignorable="d">
<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition />
    </Grid.ColumnDefinitions>

    <Grid.RowDefinitions>
        <RowDefinition Height="*" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>

    <!--  ListView  -->
    <ListView Grid.Row="0">
        <ListView x:Name="ControlEmployeesListView" Grid.Row="0">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <uc:UserControlEmployeeListItem EmployeeListItem="{Binding}" />
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </ListView>

    <!--  Details  -->
    <uc:UserControlEmployeeDetails x:Name="ControlUserControlEmployeeDetails" Grid.Row="1" />
    <!--  SelectedEmployee="{Binding}"  -->
</Grid>
</UserControl>
这就是它的 UserControlEmployees.xaml.cs 中的代码
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using TestNestedUserControls.Model;

namespace TestNestedUserControls.View.UserControls
{
/// <summary>
/// Interaction logic for UserControlEmployees.xaml
/// </summary>
public partial class UserControlEmployees : UserControl, INotifyPropertyChanged
{
    public UserControlEmployees()
    {
        InitializeComponent();
    }

    // List Items
    public ObservableCollection<Employee> Employees
    {
        get { return (ObservableCollection<Employee>)GetValue(EmployeesProperty); }
        set
        {
            SetValue(EmployeesProperty, value);
            NotifyPropertyChanged();
        }
    }

    public static readonly DependencyProperty EmployeesProperty =
        DependencyProperty.Register(nameof(Employees), typeof(ObservableCollection<Employee>), typeof(UserControlEmployees), new PropertyMetadata(default, SetNew));

    private static void SetNew(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var control = d as UserControlEmployees;
        if (control != null)
        {
            control.ControlEmployeesListView.ItemsSource = e.NewValue as ObservableCollection<Employee>;
        }
    }

    //Selected Item
    public Employee SelectedEmployee
    {
        get { return (Employee)GetValue(EmployeeProperty); }
        set
        {
            SetValue(EmployeeProperty, value);
            NotifyPropertyChanged();
        }
    }

    public static readonly DependencyProperty EmployeeProperty =
        DependencyProperty.Register(nameof(SelectedEmployee), typeof(Employee),       typeof(UserControlEmployees), new PropertyMetadata(default, SetNewSelected));

    private static void SetNewSelected(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var control = d as UserControlEmployees;
        if (control != null)
        {
            control.ControlUserControlEmployeeDetails.EmployeeDetail = e.NewValue as Employee;
            control.ControlEmployeesListView.SelectedItem = e.NewValue as Employee;
        }
    }

    #region INotifyPropertyChanged ⬇️
    public event PropertyChangedEventHandler PropertyChanged;

    private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
    #endregion ⬆️
}
}

最佳答案

修复绑定(bind)错误:错误信息提供了错误解释和解决方法。简单设置Binding.Path .

<ListView SelectedItem="{Binding Path=., Mode=TwoWay}">
请注意 Selector.SelectedItem绑定(bind) TwoWay默认情况下。所以写下就足够了:
<ListView SelectedItem="{Binding}">

从绑定(bind)来看,它看起来像您的 DataContext是错的。由于所有用户控件都使用相同的数据进行操作,例如员工的集合和选定的员工,所有用户控件应共享相同的 DataContext这是保存源集合的 View 模型。
这个 View 模型还应该定义一个 SelectedEmployee ControlEmployeesListView 的属性(property)( ListView )和 UserControlEmployeeDetails都可以绑定(bind)到。
由于UserControlEmployees内部不对员工集合进行操作,不需要专门的EmployeeSelectedEmployee属性(property)。仅当用户控件旨在可重用时,它才能或应该具有这些属性。但是当它只在这个特定的上下文中使用时,你知道DataContext您可以提前避开它们并直接绑定(bind)到 UserControl.DataContext .Control , UserControlDependencyObject一般不应该实现INotifyPropertyChanged但将它们的属性实现为 DependecyProperty . setget DependencyProperty 的方法只是 DependencyObject.SetValue 的包装和 DependencyObject.GetValue .这些包装器仅由您的自定义代码调用,但从不由框架调用。
由于DependencyProperty提供自己的通知机制,包装器只是设置它们关联的 DependencyProperty ,将自动发出更改通知。因此调用NotifyPropertyChanged()在每个 setter 中都是多余的。
还有一点是你的SetNew...属性更改回调。他们只是将新值委托(delegate)给控件。这应该在数据绑定(bind)的帮助下完成。
我也想知道这是什么嵌套的<ListView><ListView /></ListView>是关于。也删除它(这甚至可以编译吗?)。DependencyProperty字段应与注册属性同名:SelectedEmployeeProperty而不是 EmployeeProperty .
以下示例显示了如何正确连接数据。它基于您的代码并使用 Emloyees 的专用属性和 SelectedEmployee .在您的场景中,删除这些属性并直接绑定(bind)到 DataContext 似乎很合理。 (这是 View 模型)。但这取决于用户控制的目的。但这也会简化代码。
MainViewModel.cs
class MainViewModel : INotifyPropertyChanged
{
  public ObservableCollection<Employee> Employees { get; set; }
    
  private Employee selectedEmployee;
  public Employee SelectedEmployee
  {
    get => this.selectedEmployee;
    set
    {
      this.selectedEmployee = value;
      OnPropertyChanged();
    }
  }
}
UserControlEmployees.xaml.cs
public partial class UserControlEmployees : UserControl
{
  public UserControlEmployees()
  {
    InitializeComponent();
  }

  public IEnumerable<Employee> Employees
  {
    get => (IEnumerable<Employee>) GetValue(EmployeesProperty); 
    set => SetValue(EmployeesProperty, value);
  }

  public static readonly DependencyProperty EmployeesProperty = DependencyProperty.Register(
    nameof(Employees), 
    typeof(IEnumerable<Employee>), 
    typeof(UserControlEmployees), 
    new PropertyMetadata(default));
  }

  public Employee SelectedEmployee
  {
    get => (Employee) GetValue(SelectedEmployeeProperty); 
    set => SetValue(SelectedEmployeeProperty, value);
  }

  // Configure to bind TwoWay by default
  public static readonly DependencyProperty SelectedEmployeeProperty = DependencyProperty.Register(
    nameof(SelectedEmployee), 
    typeof(Employee), 
    typeof(UserControlEmployees), 
    new FrameworkPropertyMetadata(
      default, 
      FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
}
UserControlEmployees.xaml
<UserControl>
  <Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition />
    </Grid.ColumnDefinitions>

    <Grid.RowDefinitions>
        <RowDefinition Height="*" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>

    <!--  ListView  -->
    <ListView Grid.Row="0" 
              ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=Employees}"
              SelectedItem="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=SelectedEmployee}">
      <ListView.ItemTemplate>
        <DataTemplate DataType="{x:Type local:Employee}">
          <uc:UserControlEmployeeListItem EmployeeListItem="{Binding}" />
        </DataTemplate>
      </ListView.ItemTemplate>
    </ListView>
  
    <!--  Details  -->
    <uc:UserControlEmployeeDetails Grid.Row="1"
                                   SelectedEmployee="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=SelectedEmployee}" />
  </Grid>
</UserControl>
MainWndow.xaml
<Window>
  <Window.DataContext>
    <MainViewModel />
  </Window.DataContext>

  <UserControlEmployees Employees="{Binding Employees}" 
                        SelectedEmployee="{Binding SelectedEmployee}" />
</Window>

关于c# - 嵌套的 UserControls 通信,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63810851/

相关文章:

wpf - 找不到x :Shared in wpf

c# - 带空格的 WPF/XAML ResourceDictionary

c# - 如何在 TextBox Windows Phone 8.1 中更改文本的垂直对齐方式

c# - Unity 配置和嵌套泛型类型

c# - 仅刷新 MVC4 中的部分 View

c# - UpdatePanel 破坏 JQuery 脚本

wpf - 确定 xamDataGrid 上的筛选器状态

c# - WPF 中的简单数据绑定(bind)

c# - 如何在 xaml 中的硬编码值上使用转换器

c# - uwp InkCanvas 将笔画保存为 svg