WPF-привязка свойства зависимости ObservableCollection в UserControl

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

класс DragGrid: сетка { ... }

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

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

мой вопрос в том, как DP уведомляет элемент управления о том, какой дочерний элемент в сетке должен быть удалены, изменить ("изменить" означает, что пользователь перетащил элемент или изменил его размер с помощью мыши) или добавил, и как бы я идентифицировал какой из существующих детей является тем, который необходимо удалить или изменить. Я понимаю,что именно здесь возникает DependencyPropertyChangedCallback. Но это вызывается только тогда, когда свойство DP установлено заново, а не когда что-то внутри коллекции изменяется (например, add, remove item). Так, в конце концов, делает DragGrid control как-то нужно подписаться на событие CollectionChanged? В какой момент я подключу обработчик событий для это?

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

2 ответов


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

вы должны добавить обработчик 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 чтобы узнать больше.


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

вместо этого я бы использовалItemsControl (например, a ListBox) С Canvas as 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 почти закончено, и вы не хотите меняться. Я знаю, что это трудно, но в моих глазах вышеуказанный подход является более чистым (и проще!) один!