一般拖放 ListBoxItems

Posted

技术标签:

【中文标题】一般拖放 ListBoxItems【英文标题】:Drag and Drop ListBoxItems generically 【发布时间】:2013-11-25 00:19:28 【问题描述】:

我想在数据绑定的多个列表框上实现拖放操作 - 使用 MVVM 模式。我不是试图在列表框之间拖放,而是希望用户能够在每个列表框中拖放列表框项,以便他们可以重新排列排序顺序。我在 SO 上发现这篇文章非常有帮助:

WPF C#: Rearrange items in listbox via drag and drop

我想尝试使这些方法更“通用”,以便它可以在绑定到不同类型的 Observable 集合的任何列表框上工作。所以说这是我在 VIEW 中的 XAML:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">

    <Window.Resources>
        <Style TargetType="x:Type ListBoxItem" x:Key="ListBoxItemDragDrop">
            <Setter Property="AllowDrop" Value="True" />
            <EventSetter Event="PreviewMouseMove" Handler="ListBoxItem_PreviewMouseMoveEvent" />
            <EventSetter Event="Drop" Handler="listbox1_Drop" />
        </Style>
    </Window.Resources>

    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>

        <ListBox Name="listbox1"
                 ItemsSource="Binding OCofType1"
                 ItemContainerStyle="StaticResource ListBoxItemDragDrop" />

        <ListBox Name="listbox2" Grid.Column="1"
                 ItemsSource="Binding OCofType2"
                 ItemContainerStyle="StaticResource ListBoxItemDragDrop"/>
    </Grid>

</Window>

OC 绑定是 ObservalbeCollection 和 ObservalbeCollection。这是 VIEW 中抓取鼠标移动事件并检查是否为拖动的方法:

void ListBoxItem_PreviewMouseMoveEvent(object sender, MouseEventArgs e)

    if (e.LeftButton == MouseButtonState.Pressed && sender is ListBoxItem)
    
        ListBoxItem draggedItem = (ListBoxItem)sender;
        DragDrop.DoDragDrop(draggedItem, draggedItem.DataContext, DragDropEffects.Move);
        draggedItem.IsSelected = true;
    

这似乎足够“通用”。接下来是方法,也在 VIEW 中,处理 drop,这就是我卡住的地方:

void ListBoxItem_Drop(object sender, DragEventArgs e)

    object Target = ((ListBoxItem)(sender)).DataContext;
    object Dropped = e.Data.GetData(Target.GetType());

    int RemoveIndex = listbox1.Items.IndexOf(Dropped);
    int TargetIndex = listbox1.Items.IndexOf(Target);

    ListBox container = ((DependencyObject)sender).GetAncestor<ListBox>();

    if (RemoveIndex < TargetIndex)
    
        //THESE WILL NOT WORK IF I AM DOING BINDINGS THROUGH THE ITEMSSOURCE
        //container.Items.Insert(RemoveIndex + 1, Dropped);
        //container.Items.RemoveAt(RemoveIndex);

        //SO HAVE TO USE THE ITEMSSOURCE DIRECTLY BUT HOW WITHOUT A SPECIFIC CAST TO THE OC<TYPE#>
        container.ItemsSource.Insert(RemoveIndex + 1, Dropped);  //ERROR: IENUMERATOR DOES NOT CONTAIN A DEFINITION FOR INSERT....
        container.ItemsSource.RemoveAt(RemoveIndex); //ERROR: IENUMERATOR DOES NOT CONTAIN A DEFINITION FOR REMOVEAT....
    
    else
        if (container.Items.Count > RemoveIndex)
        
            //THESE WILL NOT WORK IF I AM DOING BINDINGS THROUGH THE ITEMSSOURCE                    
            //container.Items.Insert(TargetIndex, Dropped);
            //container.Items.RemoveAt(RemoveIndex + 1); 

            //SO HAVE TO USE THE ITEMSSOURCE DIRECTLY BUT HOW WITHOUT A SPECIFIC CAST TO THE OC<TYPE#>
            container.ItemsSource.Insert(TargetIndex, Dropped);  //ERROR: IENUMERATOR DOES NOT CONTAIN A DEFINITION FOR INSERT....
            container.ItemsSource.RemoveAt(RemoveIndex + 1);  //ERROR: IENUMERATOR DOES NOT CONTAIN A DEFINITION FOR REMOVEAT....

        

查找祖先函数是这样的(来自 SO 上的另一篇文章):

static T FindAnchestor<T>(DependencyObject current) where T : DependencyObject

    do
    
        if (current is T)
            return (T)current;

        current = VisualTreeHelper.GetParent(current);
    
    while (current != null);
    return null;

在 drop 函数中,如果我直接添加到 ListBox 的 List 集合中,这可能会起作用。但由于我在 VIEWMODEL 中对集合进行绑定,因此会出错,说我必须通过 ItemsSource 处理这些对象。但是,如果我使用 ItemsSource,我将不得不为每个 OC 类型制作函数的可变版本,因为我不会在运行时也投射 ItemsSource。我可以使用 if 语句将其减少到 1 个函数,该语句确定将其显式转换为什么,但如果为每个新的 OC 应用它,则不必记住更新它会更干净。

那么问题是如何在不知道确切要投射什么的情况下将项目添加/移动到 ItemsSource?

感谢您的帮助。

【问题讨论】:

我不明白什么需要更通用.. @eranotzap:我添加了一些 cmets 来向您展示我的意思。厄尼。 【参考方案1】:

这是一个非常方便的工具/框架。 Gong WPF Drag & Drop

【讨论】:

谢谢安德鲁。我知道 Gongs 工具,这是一件非常令人印象深刻的作品,但对于我想要的东西来说似乎有点太重了。【参考方案2】:

感谢各位的帮助。结果比我想的要简单得多,因为 OC 实现了它,所以只需要将它转换为 IList 并且它就像一个魅力。这是任何可能需要它的人的完整代码。这可以应用于由实现 IList 的任何集合支持的任何列表框,包括可观察集合和通用列表等:

void ListBoxItem_Drop(object sender, DragEventArgs e)

    object Target = ((ListBoxItem)(sender)).DataContext;
    object Dropped = e.Data.GetData(Target.GetType());

    ListBox container = ((DependencyObject)sender).GetAncestor<ListBox>();

    int RemoveIndex = container.Items.IndexOf(Dropped);
    int TargetIndex = container.Items.IndexOf(Target);

    IList IList = (IList)container.ItemsSource;

    if (RemoveIndex < TargetIndex)
    
        IList.Insert(TargetIndex + 1, Dropped); 
        IList.RemoveAt(RemoveIndex);
    
    else
        if (IList.Count > RemoveIndex)
        
            IList.Insert(TargetIndex, Dropped);
            IList.RemoveAt(RemoveIndex + 1);
        

厄尼

【讨论】:

这是一个不错的精益解决方案。像魅力一样工作,

以上是关于一般拖放 ListBoxItems的主要内容,如果未能解决你的问题,请参考以下文章

如果图像是链接,如何从拖放中获取图像 URL?

Qt - 在QGraphicScene中拖放时如何从项目中获取文本?

CAD交互绘制矩形框(com接口)

C#界面里的AllowDrop属性DragDrop和DragEnter事件

C#界面里的AllowDrop属性DragDrop和DragEnter事件

H5 拖放