views:

437

answers:

3

I've retemplated the DataGridRow in the Microsoft WPF DataGrid to the below, the problem I'm having is if the user clicks on the border elements of the template the Row(s) don't get selected. Is there a way to make the click on the border cause a row selection.

<Grid x:Name="LayoutRoot" Margin="0,0,0,-1">
        <Border x:Name="DGR_Border" BorderBrush="Transparent" Background="Transparent" BorderThickness="1" CornerRadius="5" SnapsToDevicePixels="True">
            <Border x:Name="DGR_InnerBorder" BorderBrush="Transparent" Background="Transparent" BorderThickness="1" CornerRadius="5" SnapsToDevicePixels="True">
                <toolkit:SelectiveScrollingGrid Name="DGR_SelectiveScrollingGrid">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto"/>
                        <ColumnDefinition Width="*"/>
                    </Grid.ColumnDefinitions>

                    <Grid.RowDefinitions>
                        <RowDefinition Height="*"/>
                        <RowDefinition Height="Auto"/>
                    </Grid.RowDefinitions>

                    <toolkit:DataGridCellsPresenter Grid.Column="1" Name="DGR_CellsPresenter" ItemsPanel="{TemplateBinding ItemsPanel}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                    <toolkit:DataGridDetailsPresenter Grid.Column="1" Grid.Row="1" Visibility="{TemplateBinding DetailsVisibility}" toolkit:SelectiveScrollingGrid.SelectiveScrollingOrientation="{Binding RelativeSource={RelativeSource AncestorType={x:Type Controls:DataGrid}}, Path=AreRowDetailsFrozen, Converter={x:Static Controls:DataGrid.RowDetailsScrollingConverter}, ConverterParameter={x:Static Controls:SelectiveScrollingOrientation.Vertical}}" />
                    <toolkit:DataGridRowHeader Grid.RowSpan="2" toolkit:SelectiveScrollingGrid.SelectiveScrollingOrientation="Vertical" Visibility="{Binding RelativeSource={RelativeSource AncestorType={x:Type Controls:DataGrid}}, Path=HeadersVisibility, Converter={x:Static Controls:DataGrid.HeadersVisibilityConverter}, ConverterParameter={x:Static Controls:DataGridHeadersVisibility.Row}}"/>
                </toolkit:SelectiveScrollingGrid>
            </Border>
        </Border>
    </Grid>
+1  A: 

The toolkit DataGridRow has no defined OnMouseDown override or similar method. The selection of a full row is handled trough this method:

internal void HandleSelectionForCellInput(DataGridCell cell, bool startDragging, bool allowsExtendSelect, bool allowsMinimalSelect)
{
    DataGridSelectionUnit selectionUnit = SelectionUnit;

    // If the mode is None, then no selection will occur
    if (selectionUnit == DataGridSelectionUnit.FullRow)
    {
        // In FullRow mode, items are selected
        MakeFullRowSelection(cell.RowDataItem, allowsExtendSelect, allowsMinimalSelect);
    }
    else
    {
        // In the other modes, cells can be individually selected
        MakeCellSelection(new DataGridCellInfo(cell), allowsExtendSelect, allowsMinimalSelect);
    }

    if (startDragging)
    {
        BeginDragging();
    }
}

This method is called when input occurs on a cell. The MakeFullRowSelection method only selects all cells in a row, not the row itself.

So, when you click on a DataGridRow (and not a DataGridCell), no mouse down or selection handling occurs. To accomplish what you desire, you should add some sort of mouse down handler to your rows' mouse down events where you would set the IsSelected property. Of course, note that you should specify the selected property for each cell in the row individually, since the row.IsSelected does not imply or set that.

kek444
A: 

Thanks for the response, I got the general idea, however setting the IsSelected to true didn't work the way I expected, when that hit it would cause the row to become selected but not unselect the other row (in the cases it should have). I was able to get around the issue by calling the HandleSelectionForCellInput in my handler. Then I just call the Lambda created from below with the grid and cell, works perfectly.

public static Action<DataGrid, DataGridCell, bool> CreateSelectRowMethod(bool allowExtendSelect, bool allowMinimalSelect)
{
var selectCellMethod = typeof(DataGrid).GetMethod("HandleSelectionForCellInput", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);

ParameterExpression dataGrid = Expression.Parameter(typeof(DataGrid), "dataGrid");
ParameterExpression paramCell = Expression.Parameter(typeof(DataGridCell), "cell");
ParameterExpression paramStartDragging = Expression.Parameter(typeof(bool), "startDragging");
var paramAllowsExtendSelect = Expression.Constant(allowExtendSelect, typeof(bool));
var paramAllowsMinimalSelect = Expression.Constant(allowMinimalSelect, typeof(bool));

var call = Expression.Call(dataGrid, selectCellMethod, paramCell, paramStartDragging, paramAllowsExtendSelect, paramAllowsMinimalSelect);

return (Action<DataGrid, DataGridCell, bool>)Expression.Lambda(call, dataGrid, paramCell, paramStartDragging).Compile();
}
thedesertfox
+1  A: 

Invoking an internal method seems somewhat dangerous. What if implementation details change? There have been lots of changes in previous versions.

I think it may be more prudent to simply add an event handler like this to your rows:

protected void DataGridRow_MouseDown(object sender, MouseButtonEventArgs e)
{
    // GetVisualChild<T> helper method, simple to implement
    DataGridCellsPresenter presenter = GetVisualChild<DataGridCellsPresenter>(rowContainer);

    // try to get the first cell in a row
    DataGridCell cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(0);
    if (cell != null)
    {
        RoutedEventArgs newEventArgs = new RoutedEventArgs(MouseLeftButtonDownEvent);
        //if the DataGridSelectionUnit is set to FullRow this will have the desired effect
        cell.RaiseEvent(newEventArgs);
    }
}

This would have the same effect as clicking the cell itself, and would use only the public members of DataGrid elements.

kek444
Works great, thanks!
thedesertfox