【问题标题】:How can I bind properties of a view model to properties of items of a collection used as source for an items control如何将视图模型的属性绑定到用作项控件源的集合项的属性
【发布时间】:2019-06-13 13:33:52
【问题描述】:

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

所以我创建了一个 CustomControl1,它有一个 Title 属性和一个我自己的自定义类型 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); }
        }
    }

我还添加了一个样式来将控件的内容呈现到 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,或者将其类型更改为一个简单的列表,也是 Freezable,但没有任何帮助。

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

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

提前谢谢你。

【问题讨论】:

  • 在调试模式下运行它并观察输出窗口是否有绑定错误
  • 感谢您的提示,实际上它帮助了很多。

标签: c# wpf binding viewmodel itemscontrol


【解决方案1】:

问题是你的绑定

<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 中使用,请在窗口的 Resources 部分创建 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}/>

【讨论】:

  • 您好,感谢您的详细解答。我开始做这一切,因为我想要一些易于阅读的东西,而且我可以节省大量的复制粘贴。正如您所建议的,我需要提前将我的数组定义为分组属性绑定,这样我就可以将它们与 ItemsControl 一起使用。不幸的是,这只是易于阅读的编码。有没有其他方法可以在不将数据源分隔为数组或复制和粘贴的情况下达到我的目标,例如布局项目?
【解决方案2】:

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

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>

而且绑定代理的代码也很好很简单,值得再分享一下:

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

链接的博文中也有一些解释

我们的问题的解决方案实际上非常简单,并且利用了 Freezable 类。这个类的主要目的是定义具有可修改和只读状态的对象,但在我们的例子中有趣的特性是 Freezable 对象可以继承 DataContext,即使它们不在可视树或逻辑树中。

感谢大家的支持。

【讨论】:

    猜你喜欢
    • 2013-04-28
    • 1970-01-01
    • 2010-12-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-13
    相关资源
    最近更新 更多