【问题标题】:WPF Drag & drop from ListBox with SelectionMode MultipleWPF 使用 SelectionMode Multiple 从 ListBox 拖放
【发布时间】:2010-12-05 22:24:40
【问题描述】:

除了一件烦人的小事之外,我几乎已经完成了这项工作......

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

【问题讨论】:

    标签: wpf listbox drag-and-drop


    【解决方案1】:

    我找到了一种非常简单的方法,可以在选择多个项目时启用 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);
            }
        }
    }
    

    【讨论】:

    • 这是一个很好的解决方案。我刚刚测试过它,它就像一个魅力。我认为这应该是选择的答案,因为它更清洁、更可靠的 IMO。
    • 的。完美的。解决方案。
    • 不错!尽管有必要令人毛骨悚然... 一个问题:为什么try...finally 块?有什么问题会出现,可以安全地忽略吗?
    • finally 子句确保_deferSelection 在事件中正确重置。那是五年前的事了,所以不知道究竟是什么会导致那里抛出异常,但我想该事件的任何其他处理程序都可能搞砸了。
    • 太棒了! :) 也适用于ListView
    【解决方案2】:

    所以......成为风滚草徽章的骄傲拥有者后,我重新开始尝试并找到解决它的方法。 ;-)

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

    基本上,我最终要做的是记住上次单击 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;
    }
    

    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>
    

    【讨论】:

      【解决方案3】:

      一个选项是在触发 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();
              }
          }
      

      【讨论】:

      • 感觉非常错误,但这为我解决了同样的问题!
      【解决方案4】:

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

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

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

      鼠标按下时

      • 如果没有选择目标项,清除现有选择
        • 如果按下 Ctrl 键,则将目标项添加到选择中
        • 如果 Shift 键按下
          • 如果有先前选择的项目,则将目标项目和上一个项目之间的所有项目添加到选择中
          • 否则只将目标项添加到选择中
      • 如果选择了目标项,则仅在按下 Ctrl 键时取消选择

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

      • 如果选择了目标项目
        • 如果按下 Ctrl 键,则从选择中删除项目
        • 如果 Shift 键按下
          • 如果有以前选择的项目,则从选择中删除目标项目和上一个项目之间的所有项目
          • 否则只从选择中删除目标项

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-01-31
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-01-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多