WPF拖放从Control用的SelectionMode多(WPF Drag & drop from

2019-07-22 05:48发布

我几乎得到这个从一个小恼人的事情工作除了...

由于列表框选择鼠标情况下,如果你开始用鼠标向下拖动时选择的最后一个项目拖动它工作正常,但如果你选择所有项目拖动,然后再点击选择开始拖动,你点击一个被勾选,拖拽后留下的。

的最佳方式有什么想法来解决这个问题?

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

Answer 1:

所以......已成为一个滚草徽章骄傲的主人,我得回到这个尝试和寻找办法解决它。 ;-)

我不知道我喜欢的解决方案,所以我还是很愿意接受任何更好的方法。

基本上,我落得这样做是记住ListBoxItem的最后点击&然后确保被拖拽前添加到选定的项目。 这也意味着在看多远开始拖动之前移动鼠标 - 因为点击所选项目不选它有时可能会导致它越来越再次选择,如果鼠标反弹开始有点拖动操作。

最后,我加了一些热点追踪到ListBox项目,因此,如果你上的鼠标按下所选项目它会得到未选中的,但你仍然得到一些反馈,以表明它会被包含在拖动操作。

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>


Answer 2:

我发现一个很简单的方法,使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);
        }
    }
}


Answer 3:

一个办法是不要让列表框或ListView删除选定的项目,直到的MouseLeftButtonUp被触发。 示例代码:

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


Answer 4:

我很惊讶,ListBox和Windows资源管理器之间的行为差​​异还没有被照顾后整个.NET框架的3个大更新4年。

我跑这个问题早在Silverlight中3.我结束了覆盖按下鼠标和鼠标了事件处理程序完全模拟Windows资源管理器的行为。

我没有源代码的任何以上,但逻辑应该是:

当按下鼠标

  • 如果没有选择目标项,清除现有的选择
    • 如果Ctrl键是向下,目标项目添加到选择
    • 如果Shift键是向下
      • 如果有以前选定的项目,添加目标项目和以往项目选择之间的所有项目
      • 否则唯一的目标项目添加到选择
  • 如果按Ctrl键被按下,如果目标项目只选择取消选择

当鼠标向上(同一项目)

  • 如果目标项目被选择
    • 如果Ctrl键是向下,从列表中删除项目
    • 如果Shift键是向下
      • 如果有以前选定的项目,删除目标项目,并从以前的选择项之间的所有项
      • 否则只从列表中删除目标项目

不过这确实应该是微软的工作,以更新的行为是操作系统一致,更加直观。 我,如果任何机构想为它投票提交它作为微软的一个错误: http://connect.microsoft.com/VisualStudio/feedback/details/809192/



文章来源: WPF Drag & drop from ListBox with SelectionMode Multiple