简体   繁体   English

WPF - ItemsSource 的属性到依赖属性

[英]WPF - Properties of ItemsSource to Dependency Properties

Background背景

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

Where I am stuck我被困在哪里

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.如果我正确理解你需要什么,那么这里有一个例子。

  1. Add properties for element templates in both lists and style for Canvas.在 Canvas 的列表和样式中添加元素模板的属性。
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 ):在子控件( ListBox1ListBox2 )内:

<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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM