【问题标题】:Copy object on Drag and Drop拖放复制对象
【发布时间】:2016-06-20 16:56:09
【问题描述】:

我有两个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 将元素从源复制到目标。

但是这样做有两个问题:

  • 有什么方法可以默认复制操作,这样就不需要按键了?
  • 如何进行真实副本?目前,目标列表的所有列表元素都指向同一个对象,因此无法更改单个元素的属性。

我已经尝试过使用自定义 DropHandler 但这使得重新排序元素变得不可能:

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

感谢任何帮助和提示。

【问题讨论】:

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


    【解决方案1】:

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

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

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

    把它想象成一个关系数据库。你有两张桌子——People 和 Pets。宠物可以由多人拥有,人们可以拥有多只宠物。在数据库中,您将有一个多对多表。

    人物 -> PeoplePets

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

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

    不要让您的收藏包含宠物的克隆,而是让您的收藏包含定义关系的对象

    public ObservableCollection<PeoplePets> Pets {get;} = 
        new ObservableCollection<PeoplePets>();
    

    因此,就您的问题而言,在您的自定义放置处理程序中,当有人在您的列表框中放置宠物时,只需创建一个新的 PeoplePets 实例,将宠物放入其中,然后将关系对象添加到集合中。您不必担心克隆任何东西,也不会添加相同事物的新实例(这可能非常有用,具体取决于您对数据的处理方式——检测和合并骗子是一个 PITA)。

    【讨论】:

      【解决方案2】:

      按照 Wills 的回答并基于默认的 drop 处理程序创建了我自己的 drop 处理程序。这样我就可以将默认行为覆盖为始终复制但不在同一个列表中。

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

      以下是相关部分代码(DropHandler-code大多基于原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:感谢您的回答,它为我指明了正确的方向。为了帮助别人,我会回答我自己的问题,但我赞成你的回答。

      【讨论】:

        【解决方案3】:

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

        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);
                }
            }
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-06-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多