绑定到 UserControl 内的 ListView 的 ItemsSource 和 SelectedValue
Bind to ItemsSource and SelectedValue of a ListView inside of a UserControl
我的目标是重用 ListView
和我设计的 UserControl
中的其他几个控件。
为简洁起见,假设我有一个这样的 Person
class 及其实例列表。
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;
}
}
我想把Person
的Name
属性作为单独的项目显示在我的ListView
里面UserControl
,然后在UserControl
的右边它,我想显示选中的人的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。这是我的问题。
- 当我运行这个时,
UserControl
列表中没有任何显示。好像是什么问题?
- 如何将
SelectedPerson
属性 绑定到 UserContro.
以便它知道如何根据选择显示正确的 City
?
所以这个让我感兴趣。我稍微弄乱了代码,发现为了完成这项工作,我必须按照 Mark 的建议为 MainWindow 设置 DataContext。所以在 MainWindow 构造函数中,你可以只输入
DataContext = this;
我还发现您设置依赖项 属性 的方式存在问题。你把它设置为一个对象。如果将其设置为 IEnumerable,它将起作用。我确信有一种更通用的方法可以做到这一点,但是,这应该会让您走上正确的道路。问题是 ItemsSource 无法使用对象。它需要 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。希望对您有所帮助!
除此之外,您还没有像
这样设置 Window 的 DataContext
DataContext = this;
您应该考虑直接从 ListView 或更简单的 ListBox 派生您的控件,因为这样您就可以直接访问其所有有用的属性。
与 UserControl 的区别在于 XAML 在 ResourceDictionary Themes/Generic.xaml
中的默认样式中,它是在您将自定义控件添加到 WPF 项目时自动生成的。
控件的代码,将基础 class 从 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
和我设计的 UserControl
中的其他几个控件。
为简洁起见,假设我有一个这样的 Person
class 及其实例列表。
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;
}
}
我想把Person
的Name
属性作为单独的项目显示在我的ListView
里面UserControl
,然后在UserControl
的右边它,我想显示选中的人的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。这是我的问题。
- 当我运行这个时,
UserControl
列表中没有任何显示。好像是什么问题? - 如何将
SelectedPerson
属性 绑定到UserContro.
以便它知道如何根据选择显示正确的City
?
所以这个让我感兴趣。我稍微弄乱了代码,发现为了完成这项工作,我必须按照 Mark 的建议为 MainWindow 设置 DataContext。所以在 MainWindow 构造函数中,你可以只输入
DataContext = this;
我还发现您设置依赖项 属性 的方式存在问题。你把它设置为一个对象。如果将其设置为 IEnumerable,它将起作用。我确信有一种更通用的方法可以做到这一点,但是,这应该会让您走上正确的道路。问题是 ItemsSource 无法使用对象。它需要 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。希望对您有所帮助!
除此之外,您还没有像
这样设置 Window 的 DataContextDataContext = this;
您应该考虑直接从 ListView 或更简单的 ListBox 派生您的控件,因为这样您就可以直接访问其所有有用的属性。
与 UserControl 的区别在于 XAML 在 ResourceDictionary Themes/Generic.xaml
中的默认样式中,它是在您将自定义控件添加到 WPF 项目时自动生成的。
控件的代码,将基础 class 从 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">