[英]WPF - Properties of ItemsSource to Dependency Properties
I am making a custom control that has multiple ListBox's.我正在制作一个具有多个 ListBox 的自定义控件。 I want to make this control MVVM compliant, so I am keeping any XAML and the code behind agnostic with respect to any ViewModel.
我想让这个控件符合 MVVM,所以我保留了任何 XAML 以及与任何 ViewModel 无关的代码。 One ListBox is simply going to be a list of TextBox's while the other is going to have a canvas as the host to display the data graphically.
一个 ListBox 只是一个 TextBox 的列表,而另一个将有一个 canvas 作为主机以图形方式显示数据。 Both of these ListBox's are children of this custom control.
这两个 ListBox 都是此自定义控件的子项。 Pseudo example for the custom control template:
自定义控件模板的伪示例:
<CustomControl>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox1 Grid.Column="0"/>
<ListBox2 Grid.Column="1"/>
</CustomControl>
The code behind for this custrom control would have a dependency property that will serve as the ItemsSource, fairly standard stuff:这个自定义控件背后的代码将有一个依赖属性,它将作为 ItemsSource,相当标准的东西:
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(UserControl1), new PropertyMetadata(new PropertyChangedCallback(OnItemsSourcePropertyChanged)));
private static void OnItemsSourcePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var control = sender as UserControl1;
if (control != null)
control.OnItemsSourceChanged((IEnumerable)e.OldValue, (IEnumerable)e.NewValue);
}
Because the two ListBox's are using the same data source but just display the data differently, I want the ItemsSource defined as one of the the parent view's dependency properties to be the ItemsSource for the two children.因为两个 ListBox 使用相同的数据源,只是显示数据不同,所以我希望将 ItemsSource 定义为父视图的依赖属性之一,作为两个子视图的 ItemsSource。 From the ViewModel side, this items source can be some sort of
ObservableCollection<ChildViewModels>
, or IEnumerable, or whatever it wants to be.从 ViewModel 方面来看,这个项目源可以是某种
ObservableCollection<ChildViewModels>
或 IEnumerable,或者任何它想要的。
How can I point to properties from the ItemsSource's ViewModel to dependency properties of the child views?如何将 ItemsSource 的 ViewModel 的属性指向子视图的依赖属性?
I was hoping to get something similar to how it could be done outside of a custom view:我希望得到类似于在自定义视图之外如何完成的东西:
Example Parent ViewModel(omitting a lot, assume all functioning):父 ViewModel 示例(省略很多,假设所有功能正常):
public class ParentViewModel
{
public ObservableCollection<ChildViewModel> ChildViewModels;
}
Example ViewModel (omitting INotifyPropertyChanged
and associated logic):示例 ViewModel(省略
INotifyPropertyChanged
和相关逻辑):
public class ChildViewModel
{
public string Name {get; set;}
public string ID {get; set;}
public string Description {get; set;}
}
Example control (ommitting setting the DataContext, assume set properly):示例控件(省略设置 DataContext,假设设置正确):
<ListBox ItemsSource="{Binding ChildViewModels}">
<ListBox.ItemsTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text ="{Binding Description}"/>
</StackPanel>
</ListBox.ItemsTemplate>
</ListBox>
How can I do something similar where I can pass the properties from the ItemsSource to the child views on a custom control?我怎样才能做类似的事情,我可以将属性从 ItemsSource 传递到自定义控件的子视图?
Many thanks非常感谢
If I understand correctly what you need, then here is an example.如果我正确理解你需要什么,那么这里有一个例子。
using System.Collections;
using System.Windows;
using System.Windows.Controls;
namespace Core2022.SO.jgrmn
{
public class TwoListControl : Control
{
static TwoListControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(TwoListControl), new FrameworkPropertyMetadata(typeof(TwoListControl)));
}
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register(
nameof(ItemsSource),
typeof(IEnumerable),
typeof(TwoListControl),
new PropertyMetadata((d, e) => ((TwoListControl)d).OnItemsSourceChanged((IEnumerable)e.OldValue, (IEnumerable)e.NewValue)));
private void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
//throw new NotImplementedException();
}
public DataTemplate TemplateForStack
{
get { return (DataTemplate)GetValue(TemplateForStackProperty); }
set { SetValue(TemplateForStackProperty, value); }
}
public static readonly DependencyProperty TemplateForStackProperty =
DependencyProperty.Register(
nameof(TemplateForStack),
typeof(DataTemplate),
typeof(TwoListControl),
new PropertyMetadata(null));
public DataTemplate TemplateForCanvas
{
get { return (DataTemplate)GetValue(TemplateForCanvasProperty); }
set { SetValue(TemplateForCanvasProperty, value); }
}
public static readonly DependencyProperty TemplateForCanvasProperty =
DependencyProperty.Register(
nameof(TemplateForCanvas),
typeof(DataTemplate),
typeof(TwoListControl),
new PropertyMetadata(null));
public Style StyleForCanvas
{
get { return (Style)GetValue(StyleForCanvasProperty); }
set { SetValue(StyleForCanvasProperty, value); }
}
public static readonly DependencyProperty StyleForCanvasProperty =
DependencyProperty.Register(
nameof(StyleForCanvas),
typeof(Style),
typeof(TwoListControl),
new PropertyMetadata(null));
}
}
In the theme (Themes/Generic.xaml), set bindings to these properties:在主题 (Themes/Generic.xaml) 中,设置对这些属性的绑定:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:jgrmn="clr-namespace:Core2022.SO.jgrmn">
<Style TargetType="{x:Type jgrmn:TwoListControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type jgrmn:TwoListControl}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox Grid.Column="0"
ItemsSource="{TemplateBinding ItemsSource}"
ItemTemplate="{TemplateBinding TemplateForStack}"/>
<ListBox Grid.Column="1"
ItemsSource="{TemplateBinding ItemsSource}"
ItemTemplate="{TemplateBinding TemplateForCanvas}"
ItemContainerStyle="{TemplateBinding StyleForCanvas}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ListBox>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Window with an example of use: Window 使用示例:
<Window x:Class="Core2022.SO.jgrmn.TwoListWindow"
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:local="clr-namespace:Core2022.SO.jgrmn"
mc:Ignorable="d"
Title="TwoListWindow" Height="250" Width="400">
<FrameworkElement.DataContext>
<CompositeCollection>
<Point>15 50</Point>
<Point>50 150</Point>
<Point>150 50</Point>
<Point>150 150</Point>
</CompositeCollection>
</FrameworkElement.DataContext>
<Grid>
<local:TwoListControl ItemsSource="{Binding}">
<local:TwoListControl.TemplateForStack>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}Point ({0} {1})">
<Binding Path="X"/>
<Binding Path="Y"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</local:TwoListControl.TemplateForStack>
<local:TwoListControl.TemplateForCanvas>
<DataTemplate>
<Ellipse Width="10" Height="10" Fill="Red"/>
</DataTemplate>
</local:TwoListControl.TemplateForCanvas>
<local:TwoListControl.StyleForCanvas>
<Style TargetType="ListBoxItem">
<Setter Property="Canvas.Left" Value="{Binding X}"/>
<Setter Property="Canvas.Top" Value="{Binding Y}"/>
</Style>
</local:TwoListControl.StyleForCanvas>
</local:TwoListControl>
</Grid>
</Window>
You must spend all participating controls a ItemsSource
property.您必须为所有参与的控件使用一个
ItemsSource
属性。 The idea is to delegate the source collection from the parent to the child controls and finally to the ListBox
.这个想法是将源集合从父级委托给子控件,最后委托给
ListBox
。 The ItemsSource properties should be a dependency property of type IList
and not IEnumerable
. ItemsSource 属性应该是
IList
而不是IEnumerable
类型的依赖属性。 This way you force the binding source to be of type IList
which improves the binding performance.通过这种方式,您可以强制绑定源为
IList
类型,从而提高绑定性能。
To allow customization of the actual displayed items, you must either要允许自定义实际显示的项目,您必须
a) spend every control a ItemTemplate
property of type DataTemplate
and delegate it to the inner most ListBox.ItemTemplate
(similar to the ItemsSource
property) or a) 花费每个控件一个
DataTemplate
类型的ItemTemplate
属性并将其委托给最里面的ListBox.ItemTemplate
(类似于ItemsSource
属性)或
b) define the template as a resource (implicit template, which is a key less DataTemplate
). b) 将模板定义为资源(隐式模板,这是一个无键的
DataTemplate
)。
The example implements a):该示例实现了 a):
<Window>
<Window.DataContext>
<ParentViewModel />
</Window.DataCOntext>
<CustomControl ItemsSource="{Binding ChildViewModels}">
<CustomControl.ItemsTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text ="{Binding Description}"/>
</StackPanel>
</CustomControl.ItemsTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox1 Grid.Column="0"
ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ItemsSource}"
ItemTemplate="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ItemTemplate}" />
<ListBox2 Grid.Column="1"
ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ItemsSource}"
ItemTemplate="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ItemTemplate}" />
</CustomControl>
</Window>
Inside the child controls ( ListBox1
and ListBox2
):在子控件(
ListBox1
和ListBox2
)内:
<UserControl>
<ListBox ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ItemsSource}"
ItemTemplate="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ItemTemplate}" />
</UserControl>
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.