mans
mans

Reputation: 18148

Stopping user from selecting/unselecting rows in WPF DataGrid

I have a DataGrid on a WPF page and want to prevent the user from selecting cells. As this feature is needed just for testing, I don't want to change everything in code.

After my DataGrid is filled, I make sure all of its rows are selected. Now I want to make sure that user cannot select/unselect rows.

I tried setting IsEnabled = false and IsHitTestVisible = "False" but both of these solutions disable scrollbars.

Is there any way to do this?

Upvotes: 1

Views: 10256

Answers (3)

Franz Maier
Franz Maier

Reputation: 1

To can stop the user to change the selection of a DataGrid and not restrict the interactivity of the controls in the cells, You can use the following solution:

Add PreviewMouseDown to your DataGrid in XAML:

<DataGrid PreviewMouseDown="DataGrid_DisableSelection">

And define DataGrid_DisableSelection in cs:

private void DataGrid_DisableSelection(object sender, MouseButtonEventArgs e)
{
    e.Handled = true;
}

Upvotes: 0

Smagin Alexey
Smagin Alexey

Reputation: 345

You have two choices:

  1. You disable selection in style (in this case you turn off only color in style, but physically SelectedItem or SelectedItems will change). You can easily find out how you can turn off selection style.

  2. You can disable changing selection without changing SelectedItem or SelectedItems (in this case your selection style will not change too).

In WPF i don't like to override standard controls. So, we need a Behavior:

public class DisableSelectionDataGridBehavior : Behavior<DataGrid>
{
    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.PreviewMouseLeftButtonDown += AssociatedObjectOnPreviewMouseLeftButtonDown;
    }

    private void AssociatedObjectOnPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        var dependencyObject = AssociatedObject.InputHitTest(e.GetPosition(AssociatedObject)) as DependencyObject;
        if (dependencyObject == null) return;

        var elements = dependencyObject.GetParents().OfType<FrameworkElement>().Where(DataGridCellExtended.GetIsDisableSelection).ToList();
        if (!elements.Any()) return;

        e.Handled = true;

        var args = new MouseButtonEventArgs(e.MouseDevice, e.Timestamp, e.ChangedButton, e.StylusDevice);
        args.RoutedEvent = UIElement.MouseLeftButtonDownEvent;
        args.Source = e.Source;

        elements.ForEach(item =>
        {
            item.RaiseEvent(args);
            var children = item.GetChildren<FrameworkElement>();
            children.ForEach(child => child.RaiseEvent(args));
        });
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        AssociatedObject.PreviewMouseLeftButtonDown -= AssociatedObjectOnPreviewMouseLeftButtonDown;
    }
}

Second, you need an Extended class:

public class DataGridCellExtended
{
    public static readonly DependencyProperty IsDisableSelectionProperty = DependencyProperty.RegisterAttached("IsDisableSelection", typeof(Boolean), typeof(DataGridCellExtended));

    public static Boolean GetIsDisableSelection(DependencyObject o)
    {
        return (Boolean)o.GetValue(IsDisableSelectionProperty);
    }

    public static void SetIsDisableSelection(DependencyObject o, Boolean value)
    {
        o.SetValue(IsDisableSelectionProperty, value);
    }
}

And finally in XAML you need something like this:

<DataGridTemplateColumn.CellTemplate>
    <DataTemplate DataType="{x:Type items:YourViewModel}">
        <StackPanel Orientation="Horizontal"
                    HorizontalAlignment="Center"
                    VerticalAlignment="Center">
            <Button Margin="0"
                    extends:DataGridCellExtended.IsDisableSelection="True">

                <Path Data="M5,0L3,2 1,0 0,1 2,3 0,5 1,6 3,4 5,6 6,5 4,3 6,1z"
                      Fill="{Binding Foreground, RelativeSource={RelativeSource FindAncestor, AncestorType=DataGridCell}}"
                      Width="12"
                      Height="12"
                      Stretch="Uniform"/>
            </Button>
        </StackPanel>
    </DataTemplate>
</DataGridTemplateColumn.CellTemplate>

You can write your logic for extended class.

public static IEnumerable<DependencyObject> GetParents(this DependencyObject element)
{
    if (element != null)
    {
        while (true)
        {
            var parent = element.GetParent();
            var dependencyObject = parent;
            element = parent;
            if (dependencyObject == null)
            {
                break;
            }
            yield return element;
        }
        yield break;
    }
    else
    {
        throw new ArgumentNullException("element");
    }
}

private static IEnumerable<DependencyObject> GetChildrenRecursive(this DependencyObject element)
{
    if (element != null)
    {
        for (var i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++)
        {
            var dependencyObject = VisualTreeHelper.GetChild(element, i);
            yield return dependencyObject;
            foreach (var childrenRecursive in dependencyObject.GetChildrenRecursive())
            {
                yield return childrenRecursive;
            }
        }
    }
    else
    {
        throw new ArgumentNullException("element");
    }
}

Upvotes: -1

Rachel
Rachel

Reputation: 132548

Why not just set IsHitTestVisible="False" for your DataGridRow or DataGridCell objects only?

That's easy to do using an implicit style in the <DataGrid.Resources>, and should only disable hit-testing on the rows or cells, which should leave the other areas of the DataGrid functional, such as the Headers or ScrollBars

<DataGrid.Resources>
    <Style TargetType="{x:Type DataGridRow}">
        <Setter Property="IsHitTestVisible" Value="False" />
    </Style>
</DataGrid.Resources>

Upvotes: 10

Related Questions