WPF使用SelectionMode Multiple从ListBox拖放

Ian*_*anR 23 wpf drag-and-drop listbox

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

因为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>
Run Code Online (Sandbox Code Playgroud)

...

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Dav*_*itt 14

我找到了一种非常简单的方法来启用Windows资源管理器,例如选择多个项目时的拖放行为.该解决方案取代了常见ListBox的一个派生垫片,取代ListBoxItem了更智能的版本.通过这种方式,我们可以将点击状态封装在正确的级别,并调用受保护的选择机制ListBox.这是相关的课程.有关完整示例,请参阅我在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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Ian*_*anR 13

所以...我已经成为风滚草徽章的骄傲拥有者,我已经回到这里试图找到解决方法.;-)

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

基本上,我最终做的是记住上次单击的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;
}
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)