WPF Drag&使用SelectionMode Multiple从ListBox中删除

时间:2009-10-12 09:42:39

标签: wpf listbox drag-and-drop

我几乎把这件事与一件令人烦恼的事情分开了......

因为ListBox选择在鼠标按下时发生,如果在选择要拖动的最后一个项目时用鼠标向下开始拖动它可以正常工作,但是如果选择要先拖动的所有项目然后单击选择以启动拖动它,你点击的那个被取消选中并在拖动后留下。

有关解决这个问题的最佳方法吗?

<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>

...

public partial class Window1
{
    private bool clickedOnSourceItem;

    public Window1()
    {
        InitializeComponent();
        DataContext = new WindowViewModel();
    }

    private void DropOnToDestination(object sender, DragEventArgs e)
    {
        var viewModel = (WindowViewModel)
                            e.Data.GetData(typeof(WindowViewModel));
        viewModel.CopySelectedItems();
    }

    private void HandleLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        var sourceElement = (FrameworkElement)sender;
        var hitItem = sourceElement.InputHitTest(e.GetPosition(sourceElement))
                      as FrameworkElement;

        if(hitItem != null)
        {
            clickedOnSourceItem = true;
        }
    }

    private void HandleLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        clickedOnSourceItem = false;
    }

    private void HandleMouseMove(object sender, MouseEventArgs e)
    {
        if(clickedOnSourceItem)
        {
            var sourceItems = (FrameworkElement)sender;
            var viewModel = (WindowViewModel)DataContext;

            DragDrop.DoDragDrop(sourceItems, viewModel, DragDropEffects.Move);
            clickedOnSourceItem = false;
        }
    }
}

4 个答案:

答案 0 :(得分:13)

所以......我已成为风滚草徽章的骄傲拥有者,我已经回到这个尝试&amp;找到解决办法。 ; - )

我不确定我是否喜欢这个解决方案,所以我仍然对任何更好的方法持开放态度。

基本上,我最终做的是记住最后点击的ListBoxItem&amp;然后确保在拖动之前将其添加到所选项目。这也意味着在开始拖动之前查看鼠标移动的距离 - 因为如果鼠标弹跳开始一点拖动操作,单击所选项目以取消选择它有时会导致它再次被选中。

最后,我在列表框项目中添加了一些热跟踪,因此,如果您在选定项目上鼠标按下它将被取消选中,但您仍然会收到一些反馈,表明它将包含在拖动操作中。

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

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>

答案 1 :(得分:12)

我发现了一种非常简单的方法,可以在选择多个项目时启用Windows资源管理器,例如拖放行为。该解决方案使用一个小的派生垫片替换了常见的ListBox,该垫片用更智能的版本替换了ListBoxItem。这样,我们可以将点击状态封装在正确的级别,并调用ListBox的受保护选择机制。这是相关的课程。有关完整示例,请参阅my repo on github

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

答案 2 :(得分:2)

在触发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();
        }
    }

答案 3 :(得分:2)

我很惊讶ListBox和Windows资源管理器之间的行为差​​异在.NET框架的3个主要更新后4年没有得到解决。

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

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

鼠标按下

  • 如果未选择目标项目,则清除现有选择
    • 如果Ctrl键关闭,请将目标项添加到选择
    • 如果Shift键关闭
      • 如果有先前选择的项目,请将目标项目和上一项目之间的所有项目添加到选择
      • 否则只将目标项目添加到选择
  • 如果选择目标项目,则仅在Ctrl键关闭时取消选择

鼠标向上(在同一项目上)

  • 如果选择了目标项目
    • 如果Ctrl键已关闭,请从选择中删除项目
    • 如果Shift键关闭
      • 如果有先前选择的项目,请从选择
      • 中删除目标项目与上一项目之间的所有项目
      • 否则只从选择中删除目标项目

<强>然而 这应该是微软的工作,更新行为以与操作系统保持一致并更直观。如果有任何机构想要投票支持,我已将其作为错误提交给Microsoft:http://connect.microsoft.com/VisualStudio/feedback/details/809192/