拖放时复制对象

时间:2016-06-16 08:34:44

标签: c# wpf mvvm drag-and-drop

我有两个ListBoxes,两个ItemsSource都必须分开ObservableCollection<ICustomObject>ICustomObject是为不同类型的CustomObject定义一些基本属性的接口。

我希望一个ListBox成为静态的,不变的可能元素源,用户可以将其多次拖动到其他ListBox。目标中的元素也应该可以重新排列。

结果应该是一个工具箱,用户可以从中创建由多个CustomObject组成的文档。

我正在使用GongSolutions.WPF.DragDrop库,提交c680fcf

<ListBox ItemsSource="{Binding AvailableElements}" dd:DragDrop.IsDragSource="True" dd:DragDrop.DragDropCopyKeyState="ControlKey">
 <ListBox.ItemTemplate>
  <DataTemplate>
   <TextBlock Text="{Binding Name}" />
  </DataTemplate>
 </ListBox.ItemTemplate>
</ListBox>

<ListBox ItemsSource="{Binding SelectedElements}" dd:DragDrop.IsDropTarget="True" dd:DragDrop.IsDragSource="True">
 <ListBox.ItemTemplate>
  <DataTemplate>
   <TextBlock Text="{Binding Name}" />
  </DataTemplate>
 </ListBox.ItemTemplate>
</ListBox>

有了这个,我可以通过按住ControlKey将元素从源复制到目标。

但这有两个问题:

  • 有没有办法默认为复制操作,因此不需要按任何键?
  • 我该怎么做真正的副本?目前,target-list的所有list-elements都指向同一个对象,结果是无法更改单个元素的属性。

我已经尝试使用自定义DropHandler,但这样就无法重新排序元素:

public void Drop(IDropInfo dropInfo)
{
    IFormElement data = Activator.CreateInstance(dropInfo.Data.GetType()) as IFormElement;
    if (data != null)
    {
        SelectedElements.Add(data);
    }
}

任何帮助和提示都表示赞赏。

3 个答案:

答案 0 :(得分:1)

&#34; 但是这使得重新排序元素不可能&#34;为什么?它不应该,除非你做错了什么。要做你想做的事,一个自定义处理程序是要走的路,所以你应该详细说明它为什么不起作用。

要删除对象的集合应实现INotifyCollectionChanged,以便在向其中添加新项时更新UI。如果这不是您的问题,请编辑您的问题以添加详细信息。假设这不是问题所在,我建议一个替代的,更简单的,有时候更好的替代方案。

我最近使用此库添加了拖放功能到我的应用程序。我必须创建一个自定义放置处理程序,因为集合被放在包含关系上,而不是被拖动类的实例。

将其想象为关系数据库。你有两张桌子 - 人和宠物。宠物可以由多人拥有,人们可以拥有多只宠物。在数据库中,您将拥有一个多对多表。

人物 - &gt; PeoplePets&lt; - Pets

PeoplePets描述了人与宠物以及宠物与人们之间的关系。

在您的设计中,您实际上克隆宠物。这意味着你和你的女朋友现在有两只狗,它们都有相同的名字,并且都有强烈的愿望在邻居猫的大便中滚动。这很奇怪(克隆部分,而不是粪便部分),虽然我确信很多人会对这种安排感到满意。

让您的收藏品保留定义关系的对象,而不是收藏宠物克隆的收藏品。

cut -d: -f1,6 /etc/passwd | tr : ' ' | sort

所以,至于您的问题,在您的自定义放置处理程序中,当有人在您的列表框中放弃宠物时,只需创建一个新的PeoplePets实例,将宠物放入其中,然后将关系对象添加到集合中。您不必担心克隆任何内容,并且您不会添加相同内容的新实例(这可能非常有用,具体取决于您对未来数据的处理方式 - 检测和合并欺骗是PITA)。

答案 1 :(得分:1)

Followw Wills回答并基于默认的drop handler创建了我自己的drop handler。这样我可以覆盖默认行为,使其始终复制,但不在同一列表中。

查看默认代码我还发现它试图克隆副本上的对象,如果它们实现ICloneable。所以我让它们可以克隆,返回一个新的自我实例。

以下是代码的相关部分(DropHandler代码主要基于原始DefaultDropHandler.cs):

<强> View.xaml:

<ListBox ItemsSource="{Binding AvailableElements}" dd:DragDrop.IsDragSource="True">
 <ListBox.ItemTemplate>
  <DataTemplate>
   <TextBlock Text="{Binding Name}" />
  </DataTemplate>
 </ListBox.ItemTemplate>
</ListBox>
<ListBox ItemsSource="{Binding SelectedElements}" dd:DragDrop.IsDropTarget="True" dd:DragDrop.IsDragSource="True" dd:DragDrop.DropHandler="{Binding}">
 <ListBox.ItemTemplate>
  <DataTemplate>
   <TextBlock Text="{Binding Name}" />
  </DataTemplate>
 </ListBox.ItemTemplate>
</ListBox>

ViewModel.cs (必须实施IDropTarget

public void DragOver(IDropInfo dropInfo)
{
    DragDrop.DefaultDropHandler.DragOver(dropInfo);
}

public void Drop(IDropInfo dropInfo)
{
    if (dropInfo == null || dropInfo.DragInfo == null)
    {
        return;
    }

    var insertIndex = dropInfo.InsertIndex != dropInfo.UnfilteredInsertIndex ? dropInfo.UnfilteredInsertIndex : dropInfo.InsertIndex;
    var destinationList = dropInfo.TargetCollection.TryGetList();
    var data = ExtractData(dropInfo.Data);

    // default to copy but not if source equals target
    var copyData = (!Equals(dropInfo.DragInfo.SourceCollection, dropInfo.TargetCollection)) 
                   && !(dropInfo.DragInfo.SourceItem is HeaderedContentControl)
                   && !(dropInfo.DragInfo.SourceItem is HeaderedItemsControl)
                   && !(dropInfo.DragInfo.SourceItem is ListBoxItem);
    if (!copyData)
    {
        var sourceList = dropInfo.DragInfo.SourceCollection.TryGetList();

        foreach (var o in data)
        {
            var index = sourceList.IndexOf(o);

            if (index != -1)
            {
                sourceList.RemoveAt(index);
                if (Equals(sourceList, destinationList) && index < insertIndex)
                {
                    --insertIndex;
                }
            }
        }
    }

    var tabControl = dropInfo.VisualTarget as TabControl;

    // clone data but not if source equals target
    var cloneData = !Equals(dropInfo.DragInfo.SourceCollection, dropInfo.TargetCollection); 
    foreach (var o in data)
    {
        var obj2Insert = o;
        if (cloneData)
        {
            var cloneable = o as ICloneable;
            if (cloneable != null)
            {
                obj2Insert = cloneable.Clone();
            }
        }

        destinationList.Insert(insertIndex++, obj2Insert);

        if (tabControl != null)
        {
            var container = tabControl.ItemContainerGenerator.ContainerFromItem(obj2Insert) as TabItem;
            if (container != null)
            {
                container.ApplyTemplate();
            }

            tabControl.SetSelectedItem(obj2Insert);
        }
    }
}

public static IEnumerable ExtractData(object data)
{
    if (data is IEnumerable && !(data is string))
    {
        return (IEnumerable)data;
    }
    else
    {
        return Enumerable.Repeat(data, 1);
    }
}

@Will:谢谢你的回答,它指出了我正确的方向。为了帮助别人,我会回答我自己的问题,但我赞成你的回答。

答案 2 :(得分:0)

我可以回答你的第二个问题。要进行真正的复制,您可以使用以下类或只在代码中使用克隆部分:

public class GenericCloner<T> where T : class
{
    public T Clone(T obj)
    {
        using (var ms = new MemoryStream())
        {
            var formatter = new BinaryFormatter();
            formatter.Serialize(ms, obj);
            ms.Position = 0;
            return (T)formatter.Deserialize(ms);
        }
    }
}