MVVM 中的 ListView SelectedItems DataBinding 实现
ListView SelectedItems DataBinding Implementation in MVVM
我在绑定 ListView 控件的 SelectedItems 时遇到一个问题,为了解决这个问题,我扩展了 ListView 控件,ListViewExtended
以使用此 post 中的引用公开 SelectedItemsList,但有两种绑定方式无法获取模型中的 SelectedItems。
请帮助指出此演示源中的问题。
自定义控件:
public sealed class ListViewExtended : ListView
{
public ListViewExtended()
{
this.SelectionChanged += ListViewExtended_SelectionChanged;
}
void ListViewExtended_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.SelectedItemsList = this.SelectedItems;
}
#region SelectedItemsList
public IEnumerable SelectedItemsList
{
get { return (IEnumerable)GetValue(SelectedItemsListProperty); }
set { SetValue(SelectedItemsListProperty, value); }
}
public static readonly DependencyProperty SelectedItemsListProperty =
DependencyProperty.Register("SelectedItemsList",
typeof(IEnumerable),
typeof(ListViewExtended),
new FrameworkPropertyMetadata(null));
#endregion
}
型号:
public class Member
{
public string Name { get; set; }
public int Age { get; set; }
}
public class Family : BindableBase
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; OnPropertyChanged("Name"); }
}
private IList<Member> _selectedMembers;
public IList<Member> SelectedMembers
{
get { return _selectedMembers; }
set { _selectedMembers = value; OnPropertyChanged("SelectedMembers"); }
}
public override string ToString()
{
return Name;
}
}
ViewModel:
public class ListViewSelectedItemsExtendedViewModel : BindableBase
{
Dictionary<string, IEnumerable> _repository = new Dictionary<string, IEnumerable>();
public ListViewSelectedItemsExtendedViewModel()
{
var families = new List<Family>();
families.Add(new Family() { Name = "family 1" });
families.Add(new Family() { Name = "family 2" });
Families = families;
var items = new List<Member>();
items.Add(new Member() { Name = "John", Age = 30 });
items.Add(new Member() { Name = "Chapel", Age = 50 });
items.Add(new Member() { Name = "Max", Age = 46 });
_repository.Add(families[0].Name, items);
items = new List<Member>();
items.Add(new Member() { Name = "Warner", Age = 28 });
items.Add(new Member() { Name = "Peter", Age = 36 });
items.Add(new Member() { Name = "Tom", Age = 5 });
_repository.Add(families[1].Name, items);
}
private IList<Family> _families;
public IList<Family> Families
{
get { return _families; }
set { _families = value; OnPropertyChanged("Families"); }
}
private Family _selectedFamily;
public Family SelectedFamily
{
get { return _selectedFamily; }
set { _selectedFamily = value; OnPropertyChanged("SelectedFamily"); }
}
private ICommand _familySelectionChangedCommand;
public ICommand FamilySelectionChangedCommand
{
get
{
return _familySelectionChangedCommand ?? (_familySelectionChangedCommand = new DelegateCommand(
() =>
{
Members = (IList<Member>)_repository[SelectedFamily.Name];
}
));
}
}
}
XAML:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="3*" />
</Grid.ColumnDefinitions>
<ListBox ItemsSource="{Binding Families}" SelectedItem="{Binding SelectedFamily}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding FamilySelectionChangedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<fsx:ListViewExtended Margin="10" Grid.Row="0"
ItemsSource="{Binding Members}"
SelectedItemsList="{Binding SelectedFamily.SelectedMembers, Mode=TwoWay}">
<ListView.View>
<GridView>
<GridViewColumn Header="Include">
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Path=IsSelected,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListViewItem}}}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Name" Width="120" DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="Age" Width="50" DisplayMemberBinding="{Binding Age}" />
</GridView>
</ListView.View>
<!--<i:Interaction.Behaviors>
<fsx:ListViewMultiSelectionBehavior
SelectedItems="{Binding SelectedFamily.SelectedMembers}" />
</i:Interaction.Behaviors>-->
</fsx:ListViewExtended>
<ListView Margin="10" Grid.Row="1"
ItemsSource="{Binding SelectedFamily.SelectedMembers}">
<ListView.View>
<GridView>
<GridViewColumn Header="Name" Width="120" DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="Age" Width="50" DisplayMemberBinding="{Binding Age}" />
</GridView>
</ListView.View>
</ListView>
</Grid>
</Grid>
除了上面的实现,我也尝试过使用这个中给出的方法,但没有得到 100% 的结果。
这里的问题是您无法将通用类型 IList 的源 属性 设置为非通用 IList,它是 SelectedItems 属性 的类型列表显示。如果您自己尝试这样做,您会发现它甚至无法编译:
IList<Member> members = listView1.SelectedItems; //WON'T COMPILE
通用 IList 接口不扩展 IList 接口,因此这是两种完全不同的类型。这就像试图将一个 int 属性 设置为一个字符串。就是不行。
如果您将 Family class 的 SelectedMembers 属性 的类型更改为非通用 IList,它将起作用:
public class Family : BindableBase
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; OnPropertyChanged("Name"); }
}
//change the type here:
private IList _selectedMembers;
public IList SelectedMembers
{
get { return _selectedMembers; }
set { _selectedMembers = value; OnPropertyChanged("SelectedMembers"); }
}
public override string ToString()
{
return Name;
}
}
there must be some way to generalize it like other built-in control's DependencyProperty work with collections
如前所述;为了能够将源 属性 设置为 ListView 的 SelectedItems 属性 的值,source 属性 的类型必须匹配 SelectedItems 属性 的类型,即源 属性 应该是非通用 IList。
您可能还想阅读这篇文章:https://blog.magnusmontin.net/2014/01/30/wpf-using-behaviours-to-bind-to-readonly-properties-in-mvvm/。它是关于如何使用行为绑定到只读属性。无论您选择采用何种解决方案,属性的类型仍必须匹配。
我在绑定 ListView 控件的 SelectedItems 时遇到一个问题,为了解决这个问题,我扩展了 ListView 控件,ListViewExtended
以使用此 post 中的引用公开 SelectedItemsList,但有两种绑定方式无法获取模型中的 SelectedItems。
请帮助指出此演示源中的问题。
自定义控件:
public sealed class ListViewExtended : ListView
{
public ListViewExtended()
{
this.SelectionChanged += ListViewExtended_SelectionChanged;
}
void ListViewExtended_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.SelectedItemsList = this.SelectedItems;
}
#region SelectedItemsList
public IEnumerable SelectedItemsList
{
get { return (IEnumerable)GetValue(SelectedItemsListProperty); }
set { SetValue(SelectedItemsListProperty, value); }
}
public static readonly DependencyProperty SelectedItemsListProperty =
DependencyProperty.Register("SelectedItemsList",
typeof(IEnumerable),
typeof(ListViewExtended),
new FrameworkPropertyMetadata(null));
#endregion
}
型号:
public class Member
{
public string Name { get; set; }
public int Age { get; set; }
}
public class Family : BindableBase
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; OnPropertyChanged("Name"); }
}
private IList<Member> _selectedMembers;
public IList<Member> SelectedMembers
{
get { return _selectedMembers; }
set { _selectedMembers = value; OnPropertyChanged("SelectedMembers"); }
}
public override string ToString()
{
return Name;
}
}
ViewModel:
public class ListViewSelectedItemsExtendedViewModel : BindableBase
{
Dictionary<string, IEnumerable> _repository = new Dictionary<string, IEnumerable>();
public ListViewSelectedItemsExtendedViewModel()
{
var families = new List<Family>();
families.Add(new Family() { Name = "family 1" });
families.Add(new Family() { Name = "family 2" });
Families = families;
var items = new List<Member>();
items.Add(new Member() { Name = "John", Age = 30 });
items.Add(new Member() { Name = "Chapel", Age = 50 });
items.Add(new Member() { Name = "Max", Age = 46 });
_repository.Add(families[0].Name, items);
items = new List<Member>();
items.Add(new Member() { Name = "Warner", Age = 28 });
items.Add(new Member() { Name = "Peter", Age = 36 });
items.Add(new Member() { Name = "Tom", Age = 5 });
_repository.Add(families[1].Name, items);
}
private IList<Family> _families;
public IList<Family> Families
{
get { return _families; }
set { _families = value; OnPropertyChanged("Families"); }
}
private Family _selectedFamily;
public Family SelectedFamily
{
get { return _selectedFamily; }
set { _selectedFamily = value; OnPropertyChanged("SelectedFamily"); }
}
private ICommand _familySelectionChangedCommand;
public ICommand FamilySelectionChangedCommand
{
get
{
return _familySelectionChangedCommand ?? (_familySelectionChangedCommand = new DelegateCommand(
() =>
{
Members = (IList<Member>)_repository[SelectedFamily.Name];
}
));
}
}
}
XAML:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="3*" />
</Grid.ColumnDefinitions>
<ListBox ItemsSource="{Binding Families}" SelectedItem="{Binding SelectedFamily}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding FamilySelectionChangedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<fsx:ListViewExtended Margin="10" Grid.Row="0"
ItemsSource="{Binding Members}"
SelectedItemsList="{Binding SelectedFamily.SelectedMembers, Mode=TwoWay}">
<ListView.View>
<GridView>
<GridViewColumn Header="Include">
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Path=IsSelected,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListViewItem}}}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Name" Width="120" DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="Age" Width="50" DisplayMemberBinding="{Binding Age}" />
</GridView>
</ListView.View>
<!--<i:Interaction.Behaviors>
<fsx:ListViewMultiSelectionBehavior
SelectedItems="{Binding SelectedFamily.SelectedMembers}" />
</i:Interaction.Behaviors>-->
</fsx:ListViewExtended>
<ListView Margin="10" Grid.Row="1"
ItemsSource="{Binding SelectedFamily.SelectedMembers}">
<ListView.View>
<GridView>
<GridViewColumn Header="Name" Width="120" DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="Age" Width="50" DisplayMemberBinding="{Binding Age}" />
</GridView>
</ListView.View>
</ListView>
</Grid>
</Grid>
除了上面的实现,我也尝试过使用这个
这里的问题是您无法将通用类型 IList
IList<Member> members = listView1.SelectedItems; //WON'T COMPILE
通用 IList
如果您将 Family class 的 SelectedMembers 属性 的类型更改为非通用 IList,它将起作用:
public class Family : BindableBase
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; OnPropertyChanged("Name"); }
}
//change the type here:
private IList _selectedMembers;
public IList SelectedMembers
{
get { return _selectedMembers; }
set { _selectedMembers = value; OnPropertyChanged("SelectedMembers"); }
}
public override string ToString()
{
return Name;
}
}
there must be some way to generalize it like other built-in control's DependencyProperty work with collections
如前所述;为了能够将源 属性 设置为 ListView 的 SelectedItems 属性 的值,source 属性 的类型必须匹配 SelectedItems 属性 的类型,即源 属性 应该是非通用 IList。
您可能还想阅读这篇文章:https://blog.magnusmontin.net/2014/01/30/wpf-using-behaviours-to-bind-to-readonly-properties-in-mvvm/。它是关于如何使用行为绑定到只读属性。无论您选择采用何种解决方案,属性的类型仍必须匹配。