WPF拖拽及;使用SelectionMode Multiple从列表框中拖放

WPF拖拽及;使用SelectionMode Multiple从列表框中拖放,wpf,listbox,drag-and-drop,Wpf,Listbox,Drag And Drop,除了一件烦人的事之外,我几乎能让它工作了 因为列表框的选择是在鼠标按下时进行的,如果在选择最后一个要拖动的项目时鼠标按下开始拖动,效果会很好,但是如果先选择所有要拖动的项目,然后单击选择开始拖动,则在拖动后,您单击的项目将被取消选中并留在后面 你有没有想过最好的解决办法 <DockPanel LastChildFill="True"> <ListBox ItemsSource="{Binding SourceItems}" SelectionM

除了一件烦人的事之外,我几乎能让它工作了

因为列表框的选择是在鼠标按下时进行的,如果在选择最后一个要拖动的项目时鼠标按下开始拖动,效果会很好,但是如果先选择所有要拖动的项目,然后单击选择开始拖动,则在拖动后,您单击的项目将被取消选中并留在后面

你有没有想过最好的解决办法

<DockPanel LastChildFill="True">
    <ListBox ItemsSource="{Binding SourceItems}"
             SelectionMode="Multiple"
             PreviewMouseLeftButtonDown="HandleLeftButtonDown"
             PreviewMouseLeftButtonUp="HandleLeftButtonUp"
             PreviewMouseMove="HandleMouseMove"
             MultiSelectListboxDragDrop:ListBoxExtension.SelectedItemsSource="{Binding SelectedItems}"/>
    <ListBox ItemsSource="{Binding DestinationItems}"
             AllowDrop="True"
             Drop="DropOnToDestination"/>
<DockPanel>

所以……作为风滚草徽章的骄傲拥有者,我又回到了这一点上,试图找到一种绕过它的方法。;-)

我不确定我是否喜欢这个解决方案,所以我仍然非常愿意接受任何更好的方法

基本上,我最后要做的就是记住上次单击ListBoxItem的内容&然后确保在拖动之前将其添加到所选项目中。这还意味着在开始拖动之前要查看鼠标移动的距离,因为如果鼠标弹跳启动了一个小的拖动操作,单击选定的项目取消选中它有时会导致它再次被选中

private void HandleLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    var source = (FrameworkElement)sender;
    var hitItem = source.InputHitTest(e.GetPosition(source)) as FrameworkElement;
    hitListBoxItem = hitItem.FindVisualParent<ListBoxItem>();
    origPos = e.GetPosition(null);
}
private void HandleLeftButtonUp(object sender, MouseButtonEventArgs e)
{
    hitListBoxItem = null;
}
private void HandleMouseMove(object sender, MouseEventArgs e)
{
    if (ShouldStartDrag(e))
    {
        hitListBoxItem.IsSelected = true;

        var sourceItems = (FrameworkElement)sender;
        var viewModel = (WindowViewModel)DataContext;
        DragDrop.DoDragDrop(sourceItems, viewModel, DragDropEffects.Move);
        hitListBoxItem = null;
    }
}
private bool ShouldStartDrag(MouseEventArgs e)
{
    if (hitListBoxItem == null)
        return false;

    var curPos = e.GetPosition(null);
    return
  Math.Abs(curPos.Y-origPos.Y) > SystemParameters.MinimumVerticalDragDistance ||
  Math.Abs(curPos.X-origPos.X) > SystemParameters.MinimumHorizontalDragDistance;
}
最后,我在列表框项目中添加了一些热跟踪,因此,如果您将鼠标下放到选定的项目上,它将被取消选中,但您仍然会收到一些反馈,表明它将被包括在拖动操作中

private void HandleLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    var source = (FrameworkElement)sender;
    var hitItem = source.InputHitTest(e.GetPosition(source)) as FrameworkElement;
    hitListBoxItem = hitItem.FindVisualParent<ListBoxItem>();
    origPos = e.GetPosition(null);
}
private void HandleLeftButtonUp(object sender, MouseButtonEventArgs e)
{
    hitListBoxItem = null;
}
private void HandleMouseMove(object sender, MouseEventArgs e)
{
    if (ShouldStartDrag(e))
    {
        hitListBoxItem.IsSelected = true;

        var sourceItems = (FrameworkElement)sender;
        var viewModel = (WindowViewModel)DataContext;
        DragDrop.DoDragDrop(sourceItems, viewModel, DragDropEffects.Move);
        hitListBoxItem = null;
    }
}
private bool ShouldStartDrag(MouseEventArgs e)
{
    if (hitListBoxItem == null)
        return false;

    var curPos = e.GetPosition(null);
    return
  Math.Abs(curPos.Y-origPos.Y) > SystemParameters.MinimumVerticalDragDistance ||
  Math.Abs(curPos.X-origPos.X) > SystemParameters.MinimumHorizontalDragDistance;
}
private void HandleLeftButtonDown(对象发送器,MouseButtonEventArgs e)
{
var source=(FrameworkElement)发送方;
var hitItem=source.InputHitTest(e.GetPosition(source))作为框架元素;
hitListBoxItem=hitItem.FindVisualParent();
origPos=e.GetPosition(null);
}
私有void HandleLeftButtonUp(对象发送器,鼠标按钮ventargs e)
{
hitListBoxItem=null;
}
私有void HandleMouseMove(对象发送器,MouseEventArgs e)
{
如果(应开始(e))
{
hitListBoxItem.IsSelected=true;
var sourceItems=(FrameworkElement)发送方;
var viewModel=(WindowViewModel)DataContext;
DragDrop.DoDragDrop(sourceItems、viewModel、DragDropEffects.Move);
hitListBoxItem=null;
}
}
私人住宅应开始安装(MouseEventArgs e)
{
if(hitListBoxItem==null)
返回false;
var curPos=e.GetPosition(null);
返回
Math.Abs(curPos.Y-origPos.Y)>系统参数。最小垂直牵引距离||
Math.Abs(curPos.X-origPos.X)>SystemParameters.MinimumHorizontalDragDistance;
}
XAML更改以包括热跟踪

<Style TargetType="ListBoxItem">
    <Setter Property="Margin" Value="1"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type ListBoxItem}">
                <Grid>
                  <Border Background="{TemplateBinding Background}" />
                  <Border Background="#BEFFFFFF" Margin="1">
                    <Grid>
                      <Grid.RowDefinitions>
                        <RowDefinition /><RowDefinition />
                      </Grid.RowDefinitions>
                      <Border Margin="1" Grid.Row="0" Background="#57FFFFFF" />
                    </Grid>
                  </Border>
                  <ContentPresenter Margin="8,5" />
                </Grid>
                <ControlTemplate.Triggers>
                  <Trigger Property="IsSelected" Value="True">
                    <Setter Property="Background" Value="PowderBlue" />
                  </Trigger>
                  <MultiTrigger>
                    <MultiTrigger.Conditions>
                      <Condition Property="IsMouseOver" Value="True" />
                      <Condition Property="IsSelected" Value="False"/>
                    </MultiTrigger.Conditions>
                    <Setter Property="Background" Value="#5FB0E0E6" />
                  </MultiTrigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

一个选项是在触发MouseLeftButtonUp之前不允许ListBox或ListView删除所选项目。 示例代码:

    List<object> removedItems = new List<object>();

    private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (e.RemovedItems.Count > 0)
        {
            ListBox box = sender as ListBox;
            if (removedItems.Contains(e.RemovedItems[0]) == false)
            {
                foreach (object item in e.RemovedItems)
                {
                    box.SelectedItems.Add(item);
                    removedItems.Add(item);
                }
            }
        }
    }

    private void ListBox_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        if (removedItems.Count > 0)
        {
            ListBox box = sender as ListBox;
            foreach (object item in removedItems)
            {
                box.SelectedItems.Remove(item);
            }
            removedItems.Clear();
        }
    }
List removedItems=new List();
私有无效列表框\u SelectionChanged(对象发送方,SelectionChangedEventArgs e)
{
如果(e.RemovedItems.Count>0)
{
ListBox=发送方作为ListBox;
if(removedItems.Contains(e.removedItems[0])==false)
{
foreach(e.RemovedItems中的对象项)
{
框。选择编辑项。添加(项);
移除项目。添加(项目);
}
}
}
}
私有无效列表框\u MouseLeftButtonUp(对象发送器,MouseButtonEventArgs e)
{
如果(removedItems.Count>0)
{
ListBox=发送方作为ListBox;
foreach(removedItems中的对象项)
{
框。选择编辑项。删除(项);
}
removedItems.Clear();
}
}

令我惊讶的是,ListBox和Windows资源管理器之间的行为差异在.NET framework的3次主要更新中4年后仍未得到解决

我在Silverlight 3中遇到了这个问题。我最终覆盖了鼠标下移和鼠标上移事件处理程序,以完全模拟Windows资源管理器的行为

我没有更多的源代码,但逻辑应该是:

当鼠标按下时

  • 如果未选择目标项,请清除现有选择
    • 如果按住Ctrl键,则将目标项添加到选择中
    • 如果Shift键按下
      • 如果存在以前选定的项目,请将目标项目和以前项目之间的所有项目添加到当前选择中
      • 否则仅将目标项添加到所选内容
  • 如果选择了目标项,则仅在按下Ctrl键时取消选择
鼠标向上移动时(在同一项目上)

  • 如果选择了目标项
    • 如果按住Ctrl键,则从选择中删除项目
    • 如果Shift键按下
      • 如果存在以前选择的项目,请从选择中删除目标项目和以前项目之间的所有项目
      • 否则仅从选择中删除目标项
但是
微软的工作应该是更新行为,使其与操作系统保持一致,并且更加直观。如果有人想投票支持它,我会将它作为一个bug提交给微软:

我发现了一种非常简单的方法,可以在选择多个项目时启用类似Windows资源管理器的拖放行为。该解决方案用一个派生的小垫片替换了常见的
ListBox
,该垫片用一个更智能的版本替换了
ListBoxItem
。通过这种方式,我们可以在正确的级别封装单击状态,并调用
列表框的受保护选择机制。这是相关的cla
public class ListBoxEx : ListBox
{
    protected override DependencyObject GetContainerForItemOverride()
    {
        return new ListBoxItemEx();
    }

    class ListBoxItemEx : ListBoxItem
    {
        private bool _deferSelection = false;

        protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
        {
            if (e.ClickCount == 1 && IsSelected)
            {
                // the user may start a drag by clicking into selected items
                // delay destroying the selection to the Up event
                _deferSelection = true;
            }
            else
            {
                base.OnMouseLeftButtonDown(e);
            }
        }

        protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
        {
            if (_deferSelection)
            {
                try
                {
                    base.OnMouseLeftButtonDown(e);
                }
                finally
                {
                    _deferSelection = false;
                }
            }
            base.OnMouseLeftButtonUp(e);
        }

        protected override void OnMouseLeave(MouseEventArgs e)
        {
            // abort deferred Down
            _deferSelection = false;
            base.OnMouseLeave(e);
        }
    }
}