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 的父级