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