我几乎把这件事与一件令人烦恼的事情分开了......
因为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;
}
}
}
答案 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资源管理器的行为。
我没有源代码,但逻辑应该是:
鼠标按下
鼠标向上(在同一项目上)
<强>然而强> 这应该是微软的工作,更新行为以与操作系统保持一致并更直观。如果有任何机构想要投票支持,我已将其作为错误提交给Microsoft:http://connect.microsoft.com/VisualStudio/feedback/details/809192/