WPF 中的 ItemsControl 是不是有一个好的解决方案,可以通过垂直拖放对其元素进行重新排序? [关闭]

Posted

技术标签:

【中文标题】WPF 中的 ItemsControl 是不是有一个好的解决方案,可以通过垂直拖放对其元素进行重新排序? [关闭]【英文标题】:Is there a good solution for a ItemsControl in WPF that can have its elements reordered by vertical drag and drop? [closed]WPF 中的 ItemsControl 是否有一个好的解决方案,可以通过垂直拖放对其元素进行重新排序? [关闭] 【发布时间】:2012-10-16 01:51:33 【问题描述】:

这是我正在处理的场景:

我有一个优先级列表,当前表示为 ItemsControl/ListView,绑定到可观察的优先级项目集合。我想为元素重新排序提供严格的视觉约束垂直拖动。

所以,没有拖动装饰器,没有水平移动,只有垂直移动。当一个列表项移过另一个项的中点时,它应该通过动画“交换位置”。我确信这可以通过在容器本身上使用 mousedown/mousemove 来完成,并且我确信可以应用渲染转换来做到这一点,但我理想的解决方案将包含两个组件:

    该功能可以附加为 WPF 交互 behavior。

    该系统将是 MVVM 友好的,并且不需要任何重要的代码。

这已经完成了吗?我在哪里可以找到它?如果还没有,我怎么能把所有的东西放在一起才能做到这一点?

编辑:赏金开启。请通过 cmets 直接向我提问,我会尽快回复。

【问题讨论】:

我有一些东西可以做到这一点,但它使用了拖动装饰器。装饰器看起来像选定的对象,直到下降才会发生交换。不过,你的想法看起来真的很漂亮。 我可以从那开始。愿意分享吗? 你看过WPF Bag of tricks中的ReorderListbox吗? 【参考方案1】:

对于代码...

标记:

<AdornerDecorator Margin="5">
    <ListBox x:Name="_listBox" Width="300" 
              HorizontalAlignment="Left"
              ItemsSource="Binding Path=Items" 
          AllowDrop="True" Drop="listBox_Drop">
        <ListBox.ItemContainerStyle>
            <Style TargetType="x:Type ListBoxItem">
                <EventSetter Event="ListBoxItem.DragOver"  Handler="listBoxItem_DragOver"/>
                <EventSetter Event="ListBoxItem.Drop" Handler="listBoxItem_Drop"/>
                <EventSetter Event="ListBoxItem.MouseMove" Handler="listBoxItem_MouseMove"/>
                <EventSetter Event="ListBoxItem.MouseDown" Handler="listBoxItem_MouseDown"/>
                <EventSetter Event="ListBoxItem.PreviewMouseDown" Handler="listBoxItem_MouseDown"/>
                <Setter Property="AllowDrop" Value="True"/>
            </Style>
        </ListBox.ItemContainerStyle>
    </ListBox>
</AdornerDecorator>

还有代码隐藏:

private bool _isDragging;

    private void listBox_MouseDown(object sender, MouseButtonEventArgs e)
    
        _isDragging = false;
    

    Adorner _adorner;

    private void listBox_MouseMove(object sender, MouseEventArgs e)
    
        if (!_isDragging && e.LeftButton == MouseButtonState.Pressed)
        
            _isDragging = true;

            if (_listBox.SelectedValue != null)
            
                DragDrop.DoDragDrop(_listBox, _listBox.SelectedValue,
                   DragDropEffects.Move);
            

        
    




private ListBoxItem FindlistBoxItem(DragEventArgs e)
    
        var visualHitTest = VisualTreeHelper.HitTest(_listBox, e.GetPosition(_listBox)).VisualHit;

        ListBoxItem listBoxItem = null;

        while (visualHitTest != null)
        
            if (visualHitTest is ListBoxItem)
            
                listBoxItem = visualHitTest as ListBoxItem;

                break;
            
            else if (visualHitTest == _listBox)
            
                Console.WriteLine("Found listBox instance");
                return null;
            

            visualHitTest = VisualTreeHelper.GetParent(visualHitTest);
        

        return listBoxItem;
    

    void ClearAdorner()
    
        if (_adorner != null)
        
            var adornerLayer = AdornerLayer.GetAdornerLayer(_listBox);
            adornerLayer.Remove(_adorner);
        
    

    private void listBox_DragOver(object sender, DragEventArgs e)
    
        e.Effects = DragDropEffects.Move;

        ClearAdorner();

        var listBoxItem = FindlistBoxItem(e);

        if (listBoxItem == null || listBoxItem.DataContext == _listBox.SelectedItem) return;

        if (IsInFirstHalf(listBoxItem, e.GetPosition(listBoxItem)))
        
            var adornerLayer = AdornerLayer.GetAdornerLayer(_listBox);
            _adorner = new DropBeforeAdorner(listBoxItem);
            adornerLayer.Add(_adorner);
        
        else if (IsInLastHalf(listBoxItem, e.GetPosition(listBoxItem)))
        
            var adornerLayer = AdornerLayer.GetAdornerLayer(_listBox);
            _adorner = new DropAfterAdorner(listBoxItem);
            adornerLayer.Add(_adorner);
        

    

    private void listBox_Drop(object sender, DragEventArgs e)
    
        if (_isDragging)
        
            _isDragging = false;
            ClearAdorner();

            var listBoxItem = FindlistBoxItem(e);

            if (listBoxItem == null || listBoxItem.DataContext == _listBox.SelectedItem) return;

            var drop = _listBox.SelectedItem as Export.Domain.Components.Component;
            var target = listBoxItem.DataContext as Export.Domain.Components.Component;

            var listBoxItem = GetlistBoxItemControl(listBoxItem);

            if (IsInFirstHalf(listBoxItem, e.GetPosition(listBoxItem)))
            
                var vm = this.DataContext as ComponentlistBoxModel;
                vm.DropBefore(drop, target);
                            
            else if (IsInLastHalf(listBoxItem, e.GetPosition(listBoxItem)))
            
                var vm = this.DataContext as ComponentlistBoxModel;
                vm.DropAfter(drop, target);
            
        
    



    public static bool IsInFirstHalf(FrameworkElement container, Point mousePosition)
    
        return mousePosition.Y < (container.ActualHeight/2);
    

    public static bool IsInLastHalf(FrameworkElement container, Point mousePosition)
    
        return mousePosition.Y > (container.ActualHeight/2);
    

您可能不喜欢我的代码隐藏按类型具体引用视图模型的事实,但它完成了工作,并且快速简单,并且从技术上讲它不会破坏 MVVM 模式。我仍然将逻辑留给视图模型。

加法 1 动画可能会提供您正在寻找的效果。在我的实现中,交换发生在丢弃时。但是,您可以通过使用装饰器并在拖动时进行交换来实现动画效果。拖动事件将更新装饰器位置和集合中对象的索引。

【讨论】:

这是一个有用的答案,但非常不通用,无法充分解决原始问题。 @Firoso 我几乎已经为您编写了解决方案。你还想要什么? 这有点不同,没有视觉上的项目“腾出空间”。正如您所指出的,非常具体的用例。一点也不差:D,但我认为 SO 可以做得更好。 @Firoso 我已经更新了我的答案。再读一遍,看看它是否不能满足您的需求。 当然好一点。我会看看我能做些什么来适应这种行为【参考方案2】:

虽然我自己没有提出解决方案,但我曾经遇到过一篇博客文章,我认为它完全符合您的要求,使用附加属性和装饰器实现了出色的关注点分离。在这里看看:ZagStudio。希望有帮助。

【讨论】:

不,看,这是 BETWEEN 组件,并没有解决就地拖动重新定位行为。 @Firoso,如果我理解正确你在说什么,你想在同一个控件中重新排列项目。并且代码可以通过在同一个控件上将附加的 IsDragSource 和 IsDropTarget 属性设置为 true 来实现。 对,但我希望视觉效果完全包含在原始控件中,因此项目只会垂直移动,它们不会超出控件的边界,也不会用鼠标水平移动.

以上是关于WPF 中的 ItemsControl 是不是有一个好的解决方案,可以通过垂直拖放对其元素进行重新排序? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

WPF中Expander与ListBox(ItemsControl)嵌套中的问题

在WPF中的UserControl中的ItemsControl中绑定问题

ItemsControl 中 DataTemplate 中的 WPF UserControl - 如何绑定到 ItemsSource 的父级

ItemsControl 上的 WPF MVVM 单选按钮

WPF ItemsControl DataTrigger EnterActions动画未启动

UWP ItemsControl 内容不像 WPF 中那样拉伸