如何将视图模型的属性绑定到用作项目控件源的 collection 的项目属性

How can I bind properties of a view model to properties of items of a collection used as source for an items control

我想要实现的是将 ViewModel (mvvm light) object 的属性以分组方式绑定到一些自制的自定义控件。

所以我创建了一个 CustomControl1,它有一个标题 属性 和一个我自己的自定义类型 SomeDataControl 的 ItemsCollection。

     public class CustomControl1 : Control
    {
        static CustomControl1()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl1), new FrameworkPropertyMetadata(typeof(CustomControl1)));
        }

        public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
            "Text", typeof(string), typeof(CustomControl1), new PropertyMetadata(default(string)));

        public string Text
        {
            get { return (string) GetValue(TextProperty); }
            set { SetValue(TextProperty, value); }
        }



        public ObservableCollection<SomeDataControl> _ItemsCollection;
        public ObservableCollection<SomeDataControl> ItemsCollection {
            get
            {
                if(_ItemsCollection == null) _ItemsCollection = new ObservableCollection<SomeDataControl>();
                return _ItemsCollection;
            }
        }
    }

    public class SomeDataControl : DependencyObject
    {
        public static readonly DependencyProperty LAbelProperty = DependencyProperty.Register(
            "LAbel", typeof(string), typeof(SomeDataControl), new PropertyMetadata(default(string)));

        public string LAbel
        {
            get { return (string) GetValue(LAbelProperty); }
            set { SetValue(LAbelProperty, value); }
        }

        public static readonly DependencyProperty DValueProperty = DependencyProperty.Register(
            "DValue", typeof(double), typeof(SomeDataControl), new PropertyMetadata(default(double)));

        public double DValue
        {
            get { return (double) GetValue(DValueProperty); }
            set { SetValue(DValueProperty, value); }
        }
    }

我还添加了一个 stlye 来将我的控件的内容呈现到 ItemsControl 中,并将值绑定到适当的字段,如下所示:

<Style x:Key="ControlStyle" TargetType="local:CustomControl1">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate>
                    <StackPanel>
                        <Label Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Text}"></Label>
                        <ItemsControl ItemsSource="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=ItemsCollection}">
                            <ItemsControl.ItemTemplate>
                                <DataTemplate>
                                    <StackPanel Orientation="Horizontal">
                                        <Label Content="{Binding Path=LAbel}" />
                                        <Label Content="{Binding Path=DValue}" />
                                    </StackPanel>

                                </DataTemplate>
                            </ItemsControl.ItemTemplate>
                        </ItemsControl>
                    </StackPanel>

                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

我将所有这些都放在我的视图中,并将视图模型作为 DataContext,这样我就可以获取这些值。

<Window x:Class="BindingTest.MainWindow" x:Name="thisControl" DataContext="{Binding Source={StaticResource Locator}, Path=VM}">
...
<local:CustomControl1 Text="{Binding Path=DataContext.Text, ElementName=thisControl}" Style="{StaticResource ControlStyle}" >
            <local:CustomControl1.ItemsCollection>
                <local:SomeDataControl LAbel="Apple" DValue="{Binding Path=DataContext.DVal1, Mode=TwoWay, ElementName=thisControl}">
                <local:SomeDataControl LAbel="Peach" DValue="{Binding Path=DataContext.DVal2, Mode=TwoWay, ElementName=thisControl}">
                <local:SomeDataControl LAbel="Pear" DValue="{Binding Path=DataContext.DVal3, Mode=TwoWay, ElementName=thisControl}"></local:SomeDataControl>

            </local:CustomControl1.ItemsCollection>
        </local:CustomControl1>

一切顺利,直到我想将 DVal1、2 和 3 绑定到特定项目。它们都具有默认值。 我已经寻找答案 3 天了,但我找不到任何有用的东西。我也尝试过将 DependenyProperty 用于 collection,或将其类型更改为一个简单的列表,也是 Freezable,但没有任何帮助。

我真的很想在 XAML 中以这种方式声明我的组,而不是将所有内容放在我的 ViewModel 中以达到布局。

任何形式的帮助都会很棒。

提前谢谢你。

问题是你的绑定

<ItemsControl.ItemTemplate>
                            <DataTemplate>
                                <StackPanel Orientation="Horizontal">
                                    <Label Content="{Binding Path=LAbel}" />
                                    <Label Content="{Binding Path=DValue}" />
                                </StackPanel>

                            </DataTemplate>
                        </ItemsControl.ItemTemplate>``

当您正在构建 Itemsource 集合时无法实现

<local:CustomControl1.ItemsCollection>
            <local:SomeDataControl LAbel="Apple" DValue="{Binding Path=DataContext.DVal1, Mode=TwoWay, ElementName=thisControl}">
            <local:SomeDataControl LAbel="Peach" DValue="{Binding Path=DataContext.DVal2, Mode=TwoWay, ElementName=thisControl}">
            <local:SomeDataControl LAbel="Pear" DValue="{Binding Path=DataContext.DVal3, Mode=TwoWay, ElementName=thisControl}"></local:SomeDataControl>

        </local:CustomControl1.ItemsCollection>

当您首先创建 SomeDataControl 时,Style 需要一个 itemsource 但它在到达结束标记之前不可用。

因此,如果您不想在 Viewmodel 中使用,请在 window 的资源部分创建 itemsource 并将其绑定到您的 customControl。

 <Window.Resources>
    <x:Array  x:Key="Mycollection" Type="local:SomeDataControl">
        <local:SomeDataControl LAbel="Apple"
                               DValue="{Binding Path=DataContext.DVal1, Mode=TwoWay}"/>
            <local:SomeDataControl LAbel="Peach"
                                   DValue="{Binding Path=DataContext.DVal2, Mode=TwoWay}"/>
    </x:Array>
</Window.Resources>

并绑定到集合

<local:CustomControl1.ItemsCollection ItemsSource={StaticResource Mycollection}/>

实际上,我通过使用这两个提示和一些谷歌搜索找到了答案。 主要问题是,我的 SomeDataControl 项目不是可视化树的一部分,因此它们没有获得更高级别 FrameworkElement 的数据上下文。 所以我引入了一个绑定代理感谢这个 post:

Usage of Binding Proxy

所以我的 XAML 看起来像这样:

<Window.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Styles.xaml"></ResourceDictionary>
            </ResourceDictionary.MergedDictionaries>
            <local:BindingProxy x:Key="proxy" Data="{Binding}" />
        </ResourceDictionary>        
    </Window.Resources>

...

<local:SomeDataControl LAbel="Apple" DValue="{Binding Path=Data.DVal1, Source={StaticResource proxy}}"></local:SomeDataControl>

绑定代理的代码也很好很简单,值得re-share:

    public class BindingProxy : Freezable
    {
        #region Overrides of Freezable

        protected override Freezable CreateInstanceCore()
        {
            return new BindingProxy();
        }

        #endregion

        public object Data
        {
            get { return (object) GetValue(DataProperty); }
            set { SetValue(DataProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Data.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty DataProperty =
            DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
    }

链接博客中也有一些解释 post

The solution to our problem is actually quite simple, and takes advantage of the Freezable class. The primary purpose of this class is to define objects that have a modifiable and a read-only state, but the interesting feature in our case is that Freezable objects can inherit the DataContext even when they’re not in the visual or logical tree.

感谢大家的支持。