Как связать свойства модели представления со свойствами элементов коллекции, используемых в качестве источника для элемента управления - PullRequest
0 голосов
/ 13 июня 2019

То, чего я пытаюсь добиться, - это привязать свойства объекта ViewModel (mvvm light) к некоторому самодельному настраиваемому элементу управления групповым способом.

Итак, я создал CustomControl1 со свойством Title иItemsCollection моего собственного пользовательского типа SomeDataControl.

     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, чтобы достичь макета.

Любая помощь будет отличной.

Заранее спасибо.

Ответы [ 2 ]

0 голосов
/ 14 июня 2019

На самом деле я нашел ответ, используя оба подсказки и немного погуглив. Основная проблема заключалась в том, что мои элементы SomeDataControl не были частью визуального дерева, поэтому они не получили текст данных верхнего уровня FrameworkElement. Итак, я представил 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, даже если они не находятся в визуальном или логическом дереве.

Спасибо всем за поддержку.

0 голосов
/ 13 июня 2019

Проблема в том, что ваши привязки

<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 ожидает источник элементов, но он недоступенпока не будет достигнут закрывающий тег.

Итак, если вы не хотите в Viewmodel, создайте источник элементов в разделе ресурсов вашего окна и привяжите его к вашему 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}/>
...