WPF - привязка свойства зависимости ObservableCollection в UserControl - PullRequest
7 голосов
/ 05 мая 2010

У меня есть контроль

класс DragGrid: Grid { ... }

, который наследует от исходной сетки и позволяет перетаскивать и изменять ее размер дочерних элементов. Мне нужно привязать пользовательский DP с именем WorkItemsProperty к наблюдаемой коллекции типа WorkItem (реализует INotifyPropertyChanged). Каждый элемент в сетке привязан к элементу коллекции.

Всякий раз, когда пользователь динамически добавляет новый элемент во время выполнения (элементы не могут быть объявлены в XAML!) Или удаляет элемент из этой коллекции, WorkItems DP в DragGrid должен обновляться, а дочерние элементы в сетке (где каждый дочерний элемент представляет элемент коллекции WorkItem).

У меня вопрос, как DP уведомляет элемент управления о том, какой дочерний элемент в сетке должен быть удален , изменен («изменить» означает, что пользователь перетянул элемент или изменил его размер с помощью мыши) или добавлено , и как мне определить, какой из существующих потомков является тем, который необходимо удалить или изменить. Я понимаю, что именно здесь вступает DependencyPropertyChangedCallback. Но он вызывается только тогда, когда свойство DP устанавливается заново, а не когда что-то внутри коллекции изменяется (например, добавление, удаление элемента). Итак, в конце концов, элемент управления DragGrid должен каким-то образом подписываться на событие CollectionChanged? В какой момент я подключу обработчик событий для этого?

* EDIT :: Причина использования Grid, в первую очередь, заключается в том, что я хочу поддерживать минимальную дельту, когда пользователь перетаскивает или изменяет размер элемента управления в Grid. Элемент управления представляет промежуток времени, и каждый столбец сетки представляет 15 минут (что является минимальным значением). Делать это в Canvas with Thumbs было сложно и глючно. Реализация DragGrid решила мои проблемы с взаимодействием с пользователем. Кроме того, Canvas не масштабируется, поэтому временные интервалы придется пересчитывать все время. С сеткой у меня нет проблем, потому что столбцы показывают время независимо от размера. **

Ответы [ 2 ]

17 голосов
/ 05 мая 2010

В ответ на ваш актуальный вопрос:

Вы должны добавить обработчик DepencyPropertyChanged, как вы упомянули. В этом обработчике вы должны добавить обработчик события в свойство CollectionChanged в новой коллекции и удалить обработчик из старой коллекции, например:

    public ObservableCollection<WorkItem> WorkItems
    {
        get { return (ObservableCollection<WorkItem>)GetValue(WorkItemsProperty); }
        set { SetValue(WorkItemsProperty, value); }
    }

    // Using a DependencyProperty as the backing store for WorkItems.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty WorkItemsProperty =
        DependencyProperty.Register("WorkItems", typeof(ObservableCollection<WorkItem>), typeof(DragGrid), new FrameworkPropertyMetadata(null, OnWorkItemsChanged));

    private static void OnWorkItemsChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        DragGrid me = sender as DragGrid;

        var old = e.OldValue as ObservableCollection<WorkItem>;

        if (old != null)
            old.CollectionChanged -= me.OnWorkCollectionChanged;

        var n = e.NewValue as ObservableCollection<WorkItem>;

        if (n != null)
            n.CollectionChanged += me.OnWorkCollectionChanged;
    }

    private void OnWorkCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (e.Action == NotifyCollectionChangedAction.Reset)
        {
            // Clear and update entire collection
        }

        if (e.NewItems != null)
        {
            foreach (WorkItem item in e.NewItems)
            {
                // Subscribe for changes on item
                item.PropertyChanged += OnWorkItemChanged;

                // Add item to internal collection
            }
        }

        if (e.OldItems != null)
        {
            foreach (WorkItem item in e.OldItems)
            {
                // Unsubscribe for changes on item
                item.PropertyChanged -= OnWorkItemChanged;

                // Remove item from internal collection
            }
        }
    }

    private void OnWorkItemChanged(object sender, PropertyChangedEventArgs e)
    {
        // Modify existing item in internal collection
    }

Как объяснил gehho, похоже, что вы не используете класс Grid, как предполагалось изначально, хотя вы, возможно, слишком далеко в разработке, чтобы начать все сначала. Классы, полученные из Panel, на самом деле предназначены только для того, чтобы визуально нарисовать / расположить своих детей, а не манипулировать ими и улучшать их. Проверьте ItemsControl и Модель содержимого WPF , чтобы узнать больше.

1 голос
/ 05 мая 2010

Извините, у меня нет решения вашей конкретной пользовательской проблемы Grid, но у меня есть только предложение, как вы могли бы сделать это проще (и, я полагаю, как это понимается дизайнерами WPF). На самом деле, Grid - это не элемент управления для размещения предметов . Это Panel, который устраивает Controls. Итак, я полагаю, что это одна из причин, по которой у вас возникли проблемы с вашим решением.

Вместо этого я бы использовал ItemsControl (например, ListBox) с Canvas как ItemsPanel.

<ListBox ItemsSource="{Binding WorkItemsProperty}">
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <Canvas IsItemsHost="True"/>
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
</ListBox>

Теперь вы определяете соответствующие свойства в своем классе WorkItem (или WorkItemViewModel), например XPos и YPos, которые будут привязаны к свойствам Canvas.Left и Canvas.Top, например:

<Style x:Key="WorkItemStyle" TargetType="{x:Type ListBoxItem}">
    <Setter Property="Canvas.Left" Value="{Binding XPos, Mode=TwoWay}"/>
    <Setter Property="Canvas.Top" Value="{Binding YPos, Mode=TwoWay}"/>
</Style>

Затем вы можете использовать этот стиль элемента, назначив ItemContainerStyle свойство ListBox:

ItemContainerStyle="{StaticResource WorkItemStyle}"

Я не знаю, как реализовать перетаскивание, потому что я никогда не делал этого, но, очевидно, вы уже сделали это для своего пользовательского Grid, поэтому не должно быть большой проблемой использовать его в ListBox. Однако если вы обновите свойства вашего WorkItem, он должен автоматически переместить элемент. Кроме того, если вы добавляете / удаляете элемент в / из вашей коллекции (WorkItemsProperty), он будет автоматически добавлен / удален, поскольку ListBox привязан к данным для коллекции.

Возможно, вам придется изменить WorkItemStyle в зависимости от вашего сценария. Например, если размер ListBox изменяется во время выполнения, вам может потребоваться сделать позиции относительно размера контейнера (Canvas '). Следовательно, вам понадобится MultiBinding вместо простого Binding. Но это другая история ...

Теперь вы сами решаете, можете ли вы все-таки перейти на этот подход, или ваш Grid почти закончен, и вы не готовы измениться. Я знаю, что это сложно, но на мой взгляд, вышеупомянутый подход является более чистым (и более легким!)!

...