WPF -ItemsSource的属性到依赖项属性

blmhpbnm  于 2022-11-30  发布在  其他
关注(0)|答案(2)|浏览(258)

背景
我正在制作一个具有多个列表框的自定义控件。我希望使此控件与MVVM兼容,所以我保持任何XAML和代码与任何ViewModel无关。一个ListBox只是一个TextBox列表,而另一个将以画布作为宿主以图形方式显示数据。这两个ListBox都是此自定义控件的子控件。自订控件样板的虚拟范例:

<CustomControl>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
    <ListBox1 Grid.Column="0"/>
    <ListBox2 Grid.Column="1"/>
</CustomControl>

这个custrom控件的代码将有一个dependency属性,该属性将用作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);
}

我被困在那里
因为两个ListBox使用相同的数据源,但只是显示不同的数据,所以我希望将ItemsSource定义为父视图的依赖属性之一,作为两个子视图的ItemsSource。从ViewModel端来看,此项目源可以是某种ObservableCollection<ChildViewModels>或IEnumerable,或者它希望成为的任何类型。

如何将ItemsSource的ViewModel中的属性指向子视图的依赖项属性?

我希望得到一些类似于如何在自定义视图之外完成的东西:
父视图模型示例(省略批次,假设所有功能):

public class ParentViewModel
{
    public ObservableCollection<ChildViewModel> ChildViewModels;
}

示例ViewModel(省略INotifyPropertyChanged和相关逻辑):

public class ChildViewModel
{
    public string Name {get; set;}
    public string ID {get; set;}
    public string Description {get; set;}   
}

示例控件(省略设置DataContext,假设设置正确):

<ListBox ItemsSource="{Binding ChildViewModels}">
    <ListBox.ItemsTemplate>
        <StackPanel>
            <TextBlock Text="{Binding Name}"/>
            <TextBlock Text ="{Binding Description}"/>
        </StackPanel>
    </ListBox.ItemsTemplate>
</ListBox>

如何执行类似的操作,将属性从ItemsSource传递到自定义控件上的子视图?
多谢

hgqdbh6s

hgqdbh6s1#

如果我正确理解了你的需求,那么这里有一个例子。
1.为列表中的元素模板添加属性,并为“画布”添加样式。

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

在主题(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 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>

km0tfn4u

km0tfn4u2#

您必须使用所有参与控件的ItemsSource属性。其想法是将来源集合从父控件委派给子控件,最后委派给ListBox。ItemsSource属性应该是IList型别的相依性属性,而不是IEnumerable型别的相依性属性。如此一来,您可以胁迫系结来源为IList型别,以改善系结效能。
要允许自定义实际显示的项目,您必须
a)为每个控件花费DataTemplate类型的ItemTemplate属性,并将其委托给最内部的ListBox.ItemTemplate(类似于ItemsSource属性),或者
b)将模板定义为资源(隐式模板,其是无关键字DataTemplate)。
该示例实现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>

在子控件(ListBox1ListBox2)内部:

<UserControl>
  <ListBox ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ItemsSource}"
           ItemTemplate="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ItemTemplate}" />
</UserControl>

相关问题