【发布时间】:2011-01-04 10:36:30
【问题描述】:
WPF Datagrid 有两种选择模式,Single 或 Extended。 WPF ListView 有第三个 - Multiple。此模式允许您在不按住 CTRL 或 Shift 的情况下单击并选择多行。有人知道如何为数据网格执行此操作吗?
【问题讨论】:
标签: wpf datagrid selection wpftoolkit
WPF Datagrid 有两种选择模式,Single 或 Extended。 WPF ListView 有第三个 - Multiple。此模式允许您在不按住 CTRL 或 Shift 的情况下单击并选择多行。有人知道如何为数据网格执行此操作吗?
【问题讨论】:
标签: wpf datagrid selection wpftoolkit
我正在创建一个具有类似要求的应用程序,该应用程序适用于触摸屏和桌面。在花了一些时间之后,我想出的解决方案似乎更干净了。 在设计器中,我将以下事件设置器添加到数据网格中:
<DataGrid.RowStyle>
<Style TargetType="DataGridRow" >
<EventSetter Event="MouseEnter" Handler="MouseEnterHandler"></EventSetter>
<EventSetter Event="PreviewMouseDown" Handler="PreviewMouseDownHandler"></EventSetter>
</Style>
</DataGrid.RowStyle>
然后在代码隐藏中,我将事件处理为:
private void MouseEnterHandler(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed &&
e.OriginalSource is DataGridRow row)
{
row.IsSelected = !row.IsSelected;
e.Handled = true;
}
}
private void PreviewMouseDownHandler(object sender, MouseButtonEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed &&
e.OriginalSource is FrameworkElement element &&
GetVisualParentOfType<DataGridRow>(element) is DataGridRow row)
{
row.IsSelected = !row.IsSelected;
e.Handled = true;
}
}
private static DependencyObject GetVisualParentOfType<T>(DependencyObject startObject)
{
DependencyObject parent = startObject;
while (IsNotNullAndNotOfType<T>(parent))
{
parent = VisualTreeHelper.GetParent(parent);
}
return parent is T ? parent : throw new Exception($"Parent of type {typeof(T)} could not be found");
}
private static bool IsNotNullAndNotOfType<T>(DependencyObject obj)
{
return obj != null && !(obj is T);
}
希望它也对其他人有所帮助。
【讨论】:
根据之前的一篇文章,我写了一段(“点赞”)MVVM代码:
首先将其添加到您的主视图中:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
View的相关部分:
<DataGrid
Style="{StaticResource DataGridStyle}"
ItemsSource="{Binding Results}"
SelectionUnit="FullRow"
SnapsToDevicePixels="True"
SelectionMode="Extended"> <!--You can change selection mode with converter. It will work (i tested it.)-->
<i:Interaction.Behaviors>
<utils:EventToCommandBehavior Command="{Binding TouchCommand}"
Event="PreviewTouchDown"
PassArguments="True"></utils:EventToCommandBehavior>
<utils:EventToCommandBehavior Command="{Binding MouseCommand}"
Event="PreviewMouseDown"
PassArguments="True"></utils:EventToCommandBehavior>
</i:Interaction.Behaviors>
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="IsSelected"<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush>
<SolidColorBrush.Color>
<Color A="50" R="0" G="0" B="0" />
</SolidColorBrush.Color>
</SolidColorBrush>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
<DataGrid.Columns>
<!-- your columns -->
</DataGrid.Columns>
</DataGrid>
有关 EventToCommandBehavior 的更多信息: here
这样,您的 ViewModel 必须实现这些命令:
//i skipped the TouchCommand definition because MouseCommand runs for touch on screen too.
public RelayCommand<MouseButtonEventArgs> MouseCommand
{
get
{
return new RelayCommand<MouseButtonEventArgs>((e)=> {
if (e.LeftButton == MouseButtonState.Pressed)
{
//call this function from your utils/models
var row = FindTemplatedParentByVisualParent<DataGridRow>((FrameworkElement)e.OriginalSource,typeof(ICommandSource));
//add ICommanSource to parameters. (if actual cell contains button instead of data.) Its optional.
if(row!=null)
{
row.IsSelected = !row.IsSelected;
e.Handled = true;
}
}
});
}
}
最后实现一个方法(在模型中的某处)来查找行。
public static T FindTemplatedParentByVisualParent<T>(FrameworkElement element,Type exceptionType = null) where T : class
{
if (element != null && (exceptionType == null || element.TemplatedParent == null || (exceptionType != null && element.TemplatedParent !=null && !exceptionType.IsAssignableFrom(element.TemplatedParent.GetType()))))
{
Type type = typeof(T);
if (type.IsInstanceOfType(element.TemplatedParent))
{
return (element.TemplatedParent as T);
}
else
{
return FindTemplatedParentByVisualParent<T>((FrameworkElement)VisualTreeHelper.GetParent(element));
}
}
else
return null;
}
这个解决方案非常适合我,所以我希望它对你也有帮助。
【讨论】:
您可以尝试这个简单的解决方法,而无需修改/继承DataGrid 控件,方法是按如下方式处理预览鼠标按下事件:
TheDataGrid.PreviewMouseLeftButtonDown +=
new MouseButtonEventHandler(TheDataGrid_PreviewMouseLeftButtonDown);
void TheDataGrid_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
// get the DataGridRow at the clicked point
var o = TryFindFromPoint<DataGridRow>(TheDataGrid, e.GetPosition(TheDataGrid));
// only handle this when Ctrl or Shift not pressed
ModifierKeys mods = Keyboard.PrimaryDevice.Modifiers;
if (o != null && ((int)(mods & ModifierKeys.Control) == 0 &&
(int)(mods & ModifierKeys.Shift) == 0))
{
o.IsSelected = !o.IsSelected;
e.Handled = true;
}
}
public static T TryFindFromPoint<T>(UIElement reference, Point point)
where T:DependencyObject
{
DependencyObject element = reference.InputHitTest(point) as DependencyObject;
if (element == null)
return null;
else if (element is T)
return (T)element;
else return TryFindParent<T>(element);
}
来自blog post by Philipp Sumi 的TryFindFromPoint 方法用于从您单击的点获取DataGridRow 实例。
通过检查ModifierKeys,您仍然可以将 Ctrl 和 Shift 保留为默认行为。
此方法的唯一缺点是您不能像原来那样单击并拖动来执行范围选择。
【讨论】:
工具包中的 DataGrid 不支持此功能,当 DataGrid 与 .NET 4 一起提供时,它看起来像 won't be supported。这个控件还没有准备好用于生产的另一个原因。我会选择以下选项之一:
我同意 DataGrid 应该支持这一点,我认为无论如何你都应该file a bug/suggestion。也许现在将其纳入 .NET 4.. 还为时不晚 :)
【讨论】: