我有一个控件
类DragGrid:网格 { ... }
继承自原始网格,可以拖动和调整其子元素的大小。
我需要将名为 WorkItemsProperty
的自定义DP绑定到类型为 WorkItem
的可观察集合(实现INotifyPropertyChanged
)。网格中的每个元素都绑定到一个集合项。
每当用户在运行时动态添加新项目(无法在XAML中声明项目!),或从该集合中删除项目时,应更新DragGrid上的WorkItems
DP,并更新子项网格(每个子代表一个WorkItem
集合项)。
我的问题是DP如何通知控件网格中的哪个子元素必须删除,已更改('更改'表示用户拖动元素,或者用鼠标调整大小)或添加,我如何识别哪一个现有子项是需要删除或更改的子项。
我知道这是DependencyPropertyChangedCallback的用武之地。但是只有在重新设置DP属性时才会调用它,而不是在集合中的某些内容发生更改时(如add,remove item)。那么最后,DragGrid
控件是否需要订阅CollectionChanged事件?我将在什么时候连接事件处理程序?
*编辑:: 首先使用网格的原因是因为我希望能够在用户拖动或调整网格中的控件时保持最小增量。控件表示时间跨度,每个网格列表示15分钟(这是最小值)。使用Thumbs在Canvas中执行此操作非常困难且错误。实现DragGrid解决了我的用户交互问题。此外,Canvas不可扩展,因此时间跨度必须始终重新计算。使用网格,我没有问题,因为列无论大小都告诉我时间。**
答案 0 :(得分:17)
回答你的实际问题:
如您所述,您应该添加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 Content Model了解详情。
答案 1 :(得分:1)
抱歉,我没有解决您的具体自定义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
和{ {1}}这样的属性:
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>
的{{1}}属性来使用此项目样式:
ItemContainerStyle
我不知道如何实现拖放操作,因为我从未这样做过,但显然你已经为自定义ListBox
完成了它,所以在使用它时不应该是一个大问题一个ItemContainerStyle="{StaticResource WorkItemStyle}"
。但是,如果更新Grid
的属性,则应自动重新定位元素。此外,如果您在集合中添加/删除项目(ListBox
),则会自动添加/删除项目,因为WorkItem
与数据绑定到集合。
您可能需要根据您的情况更改WorkItemsProperty
。例如,如果在运行时调整ListBox的大小,则可能必须使位置相对于容器的(Canvas')大小。因此,您需要ListBox
而不是简单WorkItemStyle
。但那是另一个故事......
现在,您决定是否仍然可以采用这种方法,或者您的MultiBinding
是否已经完成并且您不愿意改变。我知道这很难,但在我看来,上述方法更清洁(也更简单!)!