C# 绑定到UserControl内部ListView的ItemsSource和SelectedValue

C# 绑定到UserControl内部ListView的ItemsSource和SelectedValue,c#,wpf,listview,user-controls,itemssource,C#,Wpf,Listview,User Controls,Itemssource,我的目标是重用ListView和我设计的UserControl中的几个其他控件 为了简洁起见,假设我有一个像这样的Person类,以及它的实例列表 public class Person { public string Name { get; set; } public string City { get; set; } } 我的主窗口: <Window x:Class="ReusableListView.MainWindow" ... W

我的目标是重用
ListView
和我设计的
UserControl
中的几个其他控件

为了简洁起见,假设我有一个像这样的
Person
类,以及它的实例列表

public class Person
{
    public string Name { get; set; }
    public string City { get; set; }
}
我的
主窗口

<Window x:Class="ReusableListView.MainWindow"
        ...
        WindowStartupLocation="CenterScreen"
        Title="MainWindow" Height="600" Width="600">
    <Grid>        
        <local:UCListView Margin="8"
                          ItemsSource="{Binding PersonList, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
    </Grid>
</Window>

public partial class MainWindow : Window, INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    private ObservableCollection<Person> _personList = null;
    public ObservableCollection<Person> PersonList
    {
        get { return _personList; }
        set { _personList = value; OnPropertyChanged("PersonList"); }
    }

    private Person _selectedPerson = null;
    public Person SelectedPerson
    {
        get { return _selectedPerson; }
        set { _selectedPerson = value; OnPropertyChanged("SelectedPerson"); }
    }

    public MainWindow()
    {
        InitializeComponent();
        PersonList = GetPeople();
    }

    private ObservableCollection<Person> GetPeople()
    {
        var list = new ObservableCollection<Person>
        {
            new Person() { Name = "Jane", City = "NYC" },
            new Person() { Name = "John", City = "LA" }
        };
        return list;
    }
}
以及
UserControl
代码隐藏:

public partial class UCListView : UserControl
{
    public UCListView()
    {
        InitializeComponent();
    }

    public object ItemsSource
    {
        get { return GetValue(ItemsSourceProperty); }
        set { SetValue(ItemsSourceProperty, value); }
    }
    public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(object), typeof(UCListView), new PropertyMetadata(null));
}
上面的代码是从我在网上看到的大多数示例(包括SO)中拼凑而成的。以下是我的问题和疑问

  • 运行此操作时,
    UserControl
    列表中不显示任何内容。有什么问题吗
  • 如何将
    SelectedPerson
    属性绑定到
    usercontrol.
    以便它知道如何根据选择显示正确的
    City

  • 所以这个让我感兴趣。我把代码弄得有点乱,我发现为了让它工作,我必须按照马克的建议为MainWindow设置DataContext。因此,在主窗口构造函数中,您可以

    DataContext = this;
    
    我还发现依赖项属性设置的方式存在问题。您已将其设置为对象。如果将其设置为IEnumerable,它将工作。我相信有一种更通用的方法可以做到这一点,然而,这应该会让你走上正确的道路。问题是ItemsSource无法使用对象。它需要数不清

    public IEnumerable ItemsSource
    {
        get { return (IEnumerable)GetValue(ItemsSourceProperty); }
        set { SetValue(ItemsSourceProperty, value); }
    }
    
    public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register(
        nameof(ItemsSource), typeof(IEnumerable), typeof(UCListView));
    
    最后需要做的一件事是创建一个依赖项对象以通过DisplayMemberPath,或者在用户控件中静态设置它。我只是静态地设置它,但是您可能需要创建一个依赖属性来传递它,这样它就可以是动态的

    <ListView Grid.Column="0" MinWidth="256" Margin="8"
              x:Name="listView"
              DataContext="{Binding ElementName=MyListViewUC}"
              DisplayMemberPath="Name"
              ItemsSource="{Binding ItemsSource}"/>
    
    
    

    您必须删除ItemTemplate。我希望这是有帮助的

    所以这个让我感兴趣。我把代码弄得有点乱,我发现为了让它工作,我必须按照马克的建议为MainWindow设置DataContext。因此,在主窗口构造函数中,您可以

    DataContext = this;
    
    我还发现依赖项属性设置的方式存在问题。您已将其设置为对象。如果将其设置为IEnumerable,它将工作。我相信有一种更通用的方法可以做到这一点,然而,这应该会让你走上正确的道路。问题是ItemsSource无法使用对象。它需要数不清

    public IEnumerable ItemsSource
    {
        get { return (IEnumerable)GetValue(ItemsSourceProperty); }
        set { SetValue(ItemsSourceProperty, value); }
    }
    
    public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register(
        nameof(ItemsSource), typeof(IEnumerable), typeof(UCListView));
    
    最后需要做的一件事是创建一个依赖项对象以通过DisplayMemberPath,或者在用户控件中静态设置它。我只是静态地设置它,但是您可能需要创建一个依赖属性来传递它,这样它就可以是动态的

    <ListView Grid.Column="0" MinWidth="256" Margin="8"
              x:Name="listView"
              DataContext="{Binding ElementName=MyListViewUC}"
              DisplayMemberPath="Name"
              ItemsSource="{Binding ItemsSource}"/>
    
    
    

    您必须删除ItemTemplate。我希望这是有帮助的

    此外,您没有设置窗口的DataContext,例如

    DataContext = this;
    

    您应该考虑直接从ListVIEW或更简单的ListBox派生控件,因为这样您就可以直接访问其所有有用的属性。

    与UserControl不同的是,XAML在ResourceDictionary
    Themes/Generic.XAML
    中是默认样式,当您将自定义控件添加到WPF项目时会自动生成该样式

    控件的代码,将基类从控件更改为ListBox:

    public class MyListBox : ListBox
    {
        static MyListBox()
        {
            DefaultStyleKeyProperty.OverrideMetadata(
                typeof(MyListBox),
                new FrameworkPropertyMetadata(typeof(MyListBox)));
        }
    }
    
    Generic.xaml:

    <ResourceDictionary
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:...">
    
        <Style TargetType="local:MyListBox">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="local:MyListBox">
                        <Border Background="{TemplateBinding Background}"
                                BorderBrush="{TemplateBinding BorderBrush}"
                                BorderThickness="{TemplateBinding BorderThickness}">
                            <Grid>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition/>
                                    <ColumnDefinition/>
                                </Grid.ColumnDefinitions>
    
                                <ScrollViewer Grid.Column="0">
                                    <ItemsPresenter/>
                                </ScrollViewer>
    
                                <TextBlock Grid.Column="1"
                                           Text="{TemplateBinding SelectedValue}"/>
                            </Grid>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </ResourceDictionary>
    
    
    
    您可以像使用其他列表框一样使用MyListBox:

    <local:MyListBox ItemsSource="{Binding PersonList}"
                     SelectedItem="{Binding SelectedPerson}"
                     DisplayMemberPath="Name"
                     SelectedValuePath="City">
    
    
    

    如果您不希望在派生的ListBox中具有其他属性,那么您也可以根本不派生控件,而在声明控件模板时只将其分配给ListBox:

    <Window.Resources>
        <ControlTemplate x:Key="MyListBoxTemplate">
            ...
        </ControlTemplate>
    </Window.Resources>
    ...
    
    <ListBox Template="{StaticResource MyListBoxTemplate}"
             ItemsSource="{Binding PersonList}"
             SelectedItem="{Binding SelectedPerson}"
             DisplayMemberPath="Name"
             SelectedValuePath="City">
    
    
    ...
    ...
    
    此外,您没有设置窗口的DataContext,如

    DataContext = this;
    

    您应该考虑直接从ListVIEW或更简单的ListBox派生控件,因为这样您就可以直接访问其所有有用的属性。

    与UserControl不同的是,XAML在ResourceDictionary
    Themes/Generic.XAML
    中是默认样式,当您将自定义控件添加到WPF项目时会自动生成该样式

    控件的代码,将基类从控件更改为ListBox:

    public class MyListBox : ListBox
    {
        static MyListBox()
        {
            DefaultStyleKeyProperty.OverrideMetadata(
                typeof(MyListBox),
                new FrameworkPropertyMetadata(typeof(MyListBox)));
        }
    }
    
    Generic.xaml:

    <ResourceDictionary
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:...">
    
        <Style TargetType="local:MyListBox">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="local:MyListBox">
                        <Border Background="{TemplateBinding Background}"
                                BorderBrush="{TemplateBinding BorderBrush}"
                                BorderThickness="{TemplateBinding BorderThickness}">
                            <Grid>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition/>
                                    <ColumnDefinition/>
                                </Grid.ColumnDefinitions>
    
                                <ScrollViewer Grid.Column="0">
                                    <ItemsPresenter/>
                                </ScrollViewer>
    
                                <TextBlock Grid.Column="1"
                                           Text="{TemplateBinding SelectedValue}"/>
                            </Grid>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </ResourceDictionary>
    
    
    
    您可以像使用其他列表框一样使用MyListBox:

    <local:MyListBox ItemsSource="{Binding PersonList}"
                     SelectedItem="{Binding SelectedPerson}"
                     DisplayMemberPath="Name"
                     SelectedValuePath="City">
    
    
    

    如果您不希望在派生的ListBox中具有其他属性,那么您也可以根本不派生控件,而在声明控件模板时只将其分配给ListBox:

    <Window.Resources>
        <ControlTemplate x:Key="MyListBoxTemplate">
            ...
        </ControlTemplate>
    </Window.Resources>
    ...
    
    <ListBox Template="{StaticResource MyListBoxTemplate}"
             ItemsSource="{Binding PersonList}"
             SelectedItem="{Binding SelectedPerson}"
             DisplayMemberPath="Name"
             SelectedValuePath="City">
    
    
    ...
    ...
    
    在我看来,您并没有将主窗口的
    DataContext
    分配到任何地方。@jsanalytics,在我的实际程序中,我确实使用了ViewModel。这只是为了简单地发布一个更简单的MCVE。永远不要依赖于
    列表视图
    组合框
    选择的项目始终选择绑定属性!这种行为驱动着WinForms。很好,您没有在这里使用MvvM标记,因为这里没有。在我看来,您不会在任何地方分配主窗口的
    DataContext
    。@jsanalytics,在我的实际程序中