使用 DragDropTarget 将数据双向绑定到 Silverlight TreeView 上的 ObservableCollection
这是最基本的问题:我如何监听通过 DragDropTarget 修改的 TreeView 控件中的更改内容的更新?
所以这是我的交易:我有一个包含议程项目的 TreeView。所有数据都具有相同的数据类型 (WCFAgendaItem),并加载到层次结构中,其中子项表示为属性 ChildItems。整个事情被包装在 ObservableCollection 中,并使用 MVVM Light 绑定到 TreeView。效果很好观看。我还希望用户能够使用拖放功能对来自各种其他来源的新项目进行重新排序、重新组织和添加到此议程(一个示例是图像幻灯片的 ListView)。为了保持一致性和易于序列化,所有新项目也将具有相同的 WCFAgendaItem 数据类型。
我的问题是:使用 Toolkit 的拖放功能在 UI 上进行拖放操作效果非常好。但我不知道如何让 ViewModel 了解 TreeView 内容的更改。
视图中的代码 (Agenda.xaml):(
顶部)
<UserControl.Resources>
<AHHSTeam_SLClassroomManagerMVVM_Helpers_Converters:BooleanVisibilityConverter x:Key="BooleanVisibilityConverter"/>
<sdk:HierarchicalDataTemplate x:Key="hdtAgenda" ItemsSource="{Binding ChildItems, Mode=TwoWay}" >
<Grid HorizontalAlignment="Left">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding ImageThumbnailWidth}" />
<ColumnDefinition Width="250" />
</Grid.ColumnDefinitions>
<Image Grid.Column="0" Source="{Binding ThumbnailURL}" Width="{Binding ImageThumbnailWidth}" Height="{Binding ImageThumbnailHeight}" Visibility="{Binding HasImage, Converter={StaticResource BooleanVisibilityConverter}}" >
<ToolTipService.ToolTip>
<Image Source="{Binding ResizedImageURL}" />
</ToolTipService.ToolTip>
</Image>
<TextBlock Grid.Column="1" Text="{Binding Title}" TextWrapping="Wrap" />
</Grid>
</sdk:HierarchicalDataTemplate>
<Style TargetType="sdk:TreeViewItem" >
<Setter Property="IsExpanded" Value="True" />
</Style>
</UserControl.Resources>
(稍后)
<controlsToolkit:TreeViewDragDropTarget Grid.Row="1" Grid.Column="0" x:Name="ddtAgenda" AllowDrop="True"
HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" >
<sdk:TreeView Width="375" ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Visible" ItemsSource="{Binding DailyAgenda, Mode=TwoWay}" ItemTemplate="{StaticResource hdtAgenda}">
</sdk:TreeView>
</controlsToolkit:TreeViewDragDropTarget>
ViewModel 代码 (AgendaViewModel.cs) -->我尝试监听 CollectionChanged,到目前为止,这似乎不起作用
(在构造函数中)
//add notification of agenda changes
DailyAgenda.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(DailyAgenda_CollectionChanged);
(事件)
void DailyAgenda_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
System.Windows.MessageBox.Show("Daily agenda updated, now has " + e.NewItems.Count.ToString() + " top-level elements.");
}
来自模型的代码(WCFAgendaItem.cs)
[ContentProperty("ChildItems")]
public partial class WCFAgendaItem: INotifyPropertyChanged
{
private ObservableCollection<WCFAgendaItem> _childItems = new ObservableCollection<WCFAgendaItem>();
public ObservableCollection<WCFAgendaItem> ChildItems
{
get
{
return _childItems;
}
set
{
_childItems = value;
}
}
...
我很确定我知道监听 CollectionChanged 在任何情况下都是不正确的,因为这些数据不仅仅在顶层发生变化。我查看了 Blend 中的 EventToCommand(MVVM Light,记住),但唯一的 TreeView 特定事件似乎是 SelectionChanged,这似乎也不正确。我考虑过在 TreeViewDragDropTarget 上放置一个 EventToCommand 触发器,但这些方法不是要覆盖 UI 交互的发生方式吗?我认为 WCFAgendaItem 上的 INotifyPropertyChanged 也不适合这种情况:虽然我稍后会需要它来编辑项目标题,但当项目移动时它似乎不会帮助我。
也许我正在寻找的是一个延伸,但我真正想要发生的是 Silverlight 能够理解数据绑定在 WCFAgendaItem 集合的顺序和内容上是双向工作的,并根据 UI 重新设计所有集合本身互动。然后我可以在集合重新设计后侦听更新事件 - 之后我可以抓取绑定到 TreeView 的修改后的 ObservableCollection,并通过 WCF 展平/序列化/更新。
未能达到理想的情况:如果需要,我愿意抓取 TreeViewItems,但即使这是我需要做的,我也无法确定何时执行。另外,我需要一种方法将所有这些传递回 ViewModel,这样我就不会在后面编写代码。我是否需要附加到 Drop() 并重新设计删除逻辑?我发现了几篇关于从 Toolkit 开始的自定义拖放实现的旧文章,但没有人提到如何保存修改后的 TreeView,特别是在 MVVM 情况下。
最后{
在输入此内容时,我发现 这篇文章 可能有用,尽管 ViewModel 中的工作量相当大。这是有希望的,我会调查,但我仍然对更简单的事情抱有希望。而且,自本文撰写以来,工具包事件似乎发生了一些变化。
}
Here's the question at its most basic: how do I listen for an update of what is changing in a TreeView control modified via a DragDropTarget?
So here's my deal: I have a TreeView that holds agenda items. All are of the same data type (WCFAgendaItem), and are loaded into a hierarchy with children expressed as a property ChildItems. The whole thing is wrapped up in an ObservableCollection and bound to the TreeView using MVVM Light. Works great to view. I also want users to be able to use drag and drop to reorder, reorganize and add new items to this agenda coming from a variety of other sources (one example is a ListView of image slides). All new items would also have the same data type of WCFAgendaItem, for consistency's sake and easy serialization.
Here's my issue: dragging and dropping works beautifully on the UI using the Toolkit's drag drop functionality. But I have no idea how to get the ViewModel to understand changes to the contents of the TreeView.
Code from the view (Agenda.xaml):
(up top)
<UserControl.Resources>
<AHHSTeam_SLClassroomManagerMVVM_Helpers_Converters:BooleanVisibilityConverter x:Key="BooleanVisibilityConverter"/>
<sdk:HierarchicalDataTemplate x:Key="hdtAgenda" ItemsSource="{Binding ChildItems, Mode=TwoWay}" >
<Grid HorizontalAlignment="Left">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding ImageThumbnailWidth}" />
<ColumnDefinition Width="250" />
</Grid.ColumnDefinitions>
<Image Grid.Column="0" Source="{Binding ThumbnailURL}" Width="{Binding ImageThumbnailWidth}" Height="{Binding ImageThumbnailHeight}" Visibility="{Binding HasImage, Converter={StaticResource BooleanVisibilityConverter}}" >
<ToolTipService.ToolTip>
<Image Source="{Binding ResizedImageURL}" />
</ToolTipService.ToolTip>
</Image>
<TextBlock Grid.Column="1" Text="{Binding Title}" TextWrapping="Wrap" />
</Grid>
</sdk:HierarchicalDataTemplate>
<Style TargetType="sdk:TreeViewItem" >
<Setter Property="IsExpanded" Value="True" />
</Style>
</UserControl.Resources>
(later on)
<controlsToolkit:TreeViewDragDropTarget Grid.Row="1" Grid.Column="0" x:Name="ddtAgenda" AllowDrop="True"
HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" >
<sdk:TreeView Width="375" ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Visible" ItemsSource="{Binding DailyAgenda, Mode=TwoWay}" ItemTemplate="{StaticResource hdtAgenda}">
</sdk:TreeView>
</controlsToolkit:TreeViewDragDropTarget>
ViewModel code (AgendaViewModel.cs) --> I tried listening for CollectionChanged, so far that doesn't seem to work
(in constructor)
//add notification of agenda changes
DailyAgenda.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(DailyAgenda_CollectionChanged);
(event)
void DailyAgenda_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
System.Windows.MessageBox.Show("Daily agenda updated, now has " + e.NewItems.Count.ToString() + " top-level elements.");
}
Code from model (WCFAgendaItem.cs)
[ContentProperty("ChildItems")]
public partial class WCFAgendaItem: INotifyPropertyChanged
{
private ObservableCollection<WCFAgendaItem> _childItems = new ObservableCollection<WCFAgendaItem>();
public ObservableCollection<WCFAgendaItem> ChildItems
{
get
{
return _childItems;
}
set
{
_childItems = value;
}
}
...
I am pretty sure that I get that listening for CollectionChanged isn't right in any case, given that this data doesn't just change at the top level. I looked at EventToCommand in Blend (MVVM Light, remember) but the only TreeView-specific event appears to be SelectionChanged, which doesn't seem right either. I looked at putting an EventToCommand trigger on the TreeViewDragDropTarget, but aren't those methods about overriding how the UI interactions happen? I don't think INotifyPropertyChanged on WCFAgendaItem is right for this either: although I'm going to want that later for editing item titles, it doesn't seem like it'll help me when items get moved around.
Maybe what I'm looking for is a stretch, but what I really want to have happen is for Silverlight to understand that the databinding works both ways on the ordering and contents of the WCFAgendaItem collection, and do all the collection reworking itself based on UI interactions. Then I could just listen for an update event after the collection is reworked - after that I can just crawl the modified ObservableCollection bound to the TreeView, and flatten/serialize/update via WCF.
Failing the ideal situation: I'm willing to crawl TreeViewItems if need be, but even if that's what I need to do I'm stuck on when to do it. Plus I need a way to pass all that back to the ViewModel so I'm not writing code behind. Do I need to attach to Drop() and rework the dropping logic? I found several old articles about custom drag drop implementations starting from the Toolkit, but nobody mentions how to save out the modified TreeView, especially in an MVVM situation.
finally {
While typing this out I found this article which may be useful, though that's a fair amount of work in the ViewModel. This is promising and I'll investigate, but I'm still holding out hope for something simpler. Also it looks like the Toolkit events have changed a little since the article was written.
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我在实现这种类型的 DragDrop 功能时也遇到了问题。根本原因似乎是 ItemDragCompleted 事件 (EventHandler) 和 ItemDroppedOnSource (DragEventHandler) 都没有传递项目被删除的索引。
我最终对 DragDropTarget 进行了子类化,以便公开受保护的方法:
然后,我使用附加行为来承担将指定索引处的项目插入到基础集合中的责任。
我担心底层代码过于庞大,无法包含在 StackOverflow 答案中(主要是由于广泛的解耦),但它在我的博客主题列表中。同时,我希望上述信息有所帮助,这对我来说无疑是解决方案的关键。
伊恩
I also had issues implementing this type of DragDrop functionality. The root cause seemed to be that neither the ItemDragCompleted event (EventHandler) nor ItemDroppedOnSource (DragEventHandler) pass the index at which the item was dropped.
I ended up subclassing the DragDropTarget in order to expose the protected method:
I then used an attached behavior to assume responsibility for inserting items at the specified index into to the underlying collections.
I'm afraid the underlying code is too expansive to include in a StackOverflow answer (mainly due to extensive decoupling) but it's on my list of subjects to blog about. In the mean time, I hope the information above helps, it was certainly the key to the solution for me.
Ian