【问题标题】:Bind to ItemsSource and SelectedValue of a ListView inside of a UserControl绑定到 UserControl 内 ListView 的 ItemsSource 和 SelectedValue
【发布时间】:2019-05-09 21:34:44
【问题描述】:

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

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

public class Person
{
    public string Name { get; set; }
    public string City { get; set; }
}

我的MainWindow

<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;
    }
}

我想将PersonName 属性显示为ListViewUserControl 内的单个项目,然后在它的右侧,我想显示所选人员的City 属性。所以我的UserControl 看起来像这样:

<UserControl x:Class="ReusableListView.UCListView"
             ...
             x:Name="MyListViewUC"
             d:DesignHeight="500" d:DesignWidth="580">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <ListView Grid.Column="0" MinWidth="256" Margin="8"
                  DataContext="{Binding ElementName=MyListViewUC}"
                  ItemsSource="{Binding ItemsSource}">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <TextBlock HorizontalAlignment="Left" VerticalAlignment="Center"
                               Width="Auto" Margin="8" Background="Pink"
                               Text="{Binding Name}"/>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
        <TextBox Grid.Column="1" Margin="8" Background="PaleGreen"/>
    </Grid>
</UserControl>

以及后面的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。以下是我的问题和疑问。

  1. 当我运行它时,UserControl 列表中没有显示任何内容。似乎是什么问题?
  2. 如何将SelectedPerson 属性绑定到UserContro.,以便它知道如何根据选择显示正确的City

【问题讨论】:

  • 在我看来,您不会将 MainWindow 的 DataContext 分配到任何地方。
  • @jsanalytics,在我的实际程序中,我确实使用了 ViewModel。这只是为了简单地发布一个更简单的 MCVE。
  • 永远不要依赖ListViewComboBox 选择的项目,永远选择绑定属性!这种行为驱动WinForms。干得好,你没有在这里使用 MvvM 标签,因为没有。

标签: c# wpf listview user-controls itemssource


【解决方案1】:

除了你错过了像这样设置窗口的DataContext

DataContext = this;

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

与 UserControl 的区别在于 XAML 在 ResourceDictionary Themes/Generic.xaml 中采用默认样式,这是在您将自定义控件添加到 WPF 项目时自动生成的。

控件的代码,将基类从 Control 更改为 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>

您可以像使用任何其他 ListBox 一样使用您的 MyListBox:

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

如果您不打算在派生的 ListBox 中包含其他属性,您也可以根本不派生控件,而只需在声明时将 ControlTemplate 分配给 ListBox:

<Window.Resources>
    <ControlTemplate x:Key="MyListBoxTemplate">
        ...
    </ControlTemplate>
</Window.Resources>
...

<ListBox Template="{StaticResource MyListBoxTemplate}"
         ItemsSource="{Binding PersonList}"
         SelectedItem="{Binding SelectedPerson}"
         DisplayMemberPath="Name"
         SelectedValuePath="City">

【讨论】:

  • 这是一个很好的解决方案,谢谢!我想使用ListView,因为这是一个简化的 MCVE,但实际上我需要在一行中显示很多东西,ListView 更合适。那么这是否意味着我们不能在UserControl 中完全使用ListViewListBox 之类的东西?
  • 当然可以,但除非您不想将内部元素的属性直接绑定到视图模型(在继承自 UserControl 父元素的 DataContext 中),否则您必须复制所有必要的 ListView 属性,例如 ItemsSource、SelectedItem、SelectedValue、SelectedValuePath、DisplayMemberPath、ItemTemplate 等等。
  • 你能不能也给我看一个例子?
  • 您在另一个答案中已经有了。但是,你不应该设置 ListView 的 DataContext,而只写ItemsSource="{Binding ItemsSource, RelativeSource={RelativeSource AncestorType=UserControl}}"。而且也没有必要使用IEnumerable&lt;Person&gt; 作为属性类型。 IEnumerable 就足够了。我已经相应地编辑了答案。通常,重用 ItemsControl/ListBox 中的属性类型。
【解决方案2】:

所以这个引起了我的兴趣。我稍微弄乱了代码,我发现为了完成这项工作,我必须按照 Mark 的建议设置 MainWindow 的 DataContext。所以在 MainWindow 构造函数中,你可以只放

DataContext = this;

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

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。我希望这会有所帮助!

【讨论】:

  • 谢谢。是的,我不设置数据源很傻。但是,这并不能回答我的第二个问题,即如何设置所选项目。不过,谢谢。
猜你喜欢
  • 1970-01-01
  • 2011-01-14
  • 2018-08-02
  • 1970-01-01
  • 2017-10-26
  • 2012-01-30
  • 2012-01-08
  • 1970-01-01
  • 2014-12-08
相关资源
最近更新 更多