Question

<DataGrid>
  <DataGrid.Columns>
    <DataGridTextColumn Binding="{Binding Path=test}"></DataGridTextColumn>
  </DataGrid.Columns>
  <DataGrid.RowDetailsTemplate>
    <DataTemplate>
     <DataGrid Template="{DynamicResource TemplateDataGridPrintAndExport}"/>
    </DataTemplate>
  </DataGrid.RowDetailsTemplate>
 <DataGrid/>

I have a datagrid like above. Datgrid's row detail template also contains a datagrid. Inner datagrid is filled when the parent one's columns are clicked. My problem is this : if the row detail template datagrid is fulfilled and user mouse hovers on it while scrolling parent datagrid the scroll is not working. User should hover the mouse to the main datagriid to scroll. However, it is not user friendly. How can I prevent inner datagrid behaving in such a way?

Was it helpful?

Solution

I found the soultion by trying alternatives :

<DataGrid  ScrollViewer.CanContentScroll="False">
  <DataGrid.Columns>
    <DataGridTextColumn Binding="{Binding Path=test}"></DataGridTextColumn>
  </DataGrid.Columns>
  <DataGrid.RowDetailsTemplate>
    <DataTemplate>
     <DataGrid Template="{DynamicResource TemplateDataGridPrintAndExport}"  IsReadOnly="True" ScrollViewer.CanContentScroll="False"  IsEnabled="False"/>
    </DataTemplate>
  </DataGrid.RowDetailsTemplate>
 <DataGrid/>

The solution is to give ScrollViewer.CanContentScroll="False" attribute to the outer data grid and IsReadOnly="True" ScrollViewer.CanContentScroll="False" IsEnabled="False" attributes to inner datagrid. Now it is scrolling smoothly and accoording to the parent datagrid.

OTHER TIPS

I would like to propose two alternative solutions, since the chosen one has serious side effects. One of them mentioned by Kaizen - you lose ability to interact with nested DataGrid and its child controls. Second one is the change of appearence of controls in their disabled state.

  1. Change IsReadOnly="True" to IsHitTestVisible="False" in osmanraifgunes' solution. This will fix the appearence side effect, but you still won't be able to interact with inner controls (using mouse). Code:

    <DataGrid ScrollViewer.CanContentScroll="False">
        <DataGrid.Columns>
            <DataGridTextColumn Binding="{Binding Path=test}" />
        </DataGrid.Columns>
        <DataGrid.RowDetailsTemplate>
            <DataTemplate>
                <DataGrid
                    IsHitTestVisible="False"
                    ScrollViewer.CanContentScroll="False"
                    Template="{DynamicResource TemplateDataGridPrintAndExport}" />
            </DataTemplate>
        </DataGrid.RowDetailsTemplate>
    </DataGrid>
    
  2. Catch the tunneling PreviewMouseWheel event in the control within RowDetailsTemplate and pass it back to parent as a bubbling event. This will effectively make controls within RowDetailsTemplate blind only to mouse scrolling, and allow controls above in visual tree to handle it however they want to. xaml:

    <DataGrid ScrollViewer.CanContentScroll="False">
        <DataGrid.Columns>
            <DataGridTextColumn Binding="{Binding Path=test}" />
        </DataGrid.Columns>
        <DataGrid.RowDetailsTemplate>
            <DataTemplate>
                <DataGrid
                    PreviewMouseWheel="DataGrid_PreviewMouseWheel"
                    Template="{DynamicResource TemplateDataGridPrintAndExport}" />
            </DataTemplate>
        </DataGrid.RowDetailsTemplate>
    </DataGrid>
    

code behind:

        private void DataGrid_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
        {
            if (e.Handled)
            {
                return;
            }
            Control control = sender as Control;
            if(control == null)
            {
                return;
            }
            e.Handled = true;
            var wheelArgs = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta)
            {
                RoutedEvent = MouseWheelEvent,
                Source = control
            };
            var parent = control.Parent as UIElement;
            parent?.RaiseEvent(wheelArgs);
        }

If you're using .NET 4.5 and above you can use VirtualizingPanel.ScrollUnit="Pixel" on the outer grid, which will allow you to scroll by pixels instead of units (items) as that is causing pretty weird behavior when having big inner DataGrid as it starts jumping around.

Then you can just past scrolling event to the parent using PreviewMouseWheel event on the inner DataGrid since it is being captured by the inner control.

Xaml:

<DataGrid VirtualizingPanel.ScrollUnit="Pixel">
    <DataGrid.RowDetailsTemplate>
        <DataTemplate>
            <DataGrid PreviewMouseWheel="DataGrid_PreviewMouseWheel"/>
        </DataTemplate>
    </DataGrid.RowDetailsTemplate>
</DataGrid>

cs:

private void DataGrid_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
    e.Handled = true;
    var parent = ((Control)sender).Parent as UIElement;
    parent?.RaiseEvent(new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta)
    {
        RoutedEvent = MouseWheelEvent,
        Source = sender
    });
}

I've used @Bartłomiej Popielarz's 2nd approach to make it work.

In my case somehow control.Parent always returned null. Thats why I've changed the according line to

    var parent = VisualTreeHelper.GetParent(control) as UIElement;

Also I've created a attached Property that does the forwarding (better suited for MVVM approaches).

    public class FixScrollingBehaviorOn
    {
        public static readonly DependencyProperty ParentDataGridProperty = DependencyProperty.RegisterAttached("ParentDataGrid", typeof(DataGrid), typeof(FixScrollingBehaviorOn),
            new FrameworkPropertyMetadata(default(DataGrid), OnParentDataGridPropertyChanged));

        public static bool GetParentDataGrid(DependencyObject obj)

        {
            return (bool)obj.GetValue(ParentDataGridProperty);
        }

        public static void SetParentDataGrid(DependencyObject obj, bool value)

        {
            obj.SetValue(ParentDataGridProperty, value);
        }

        public static void OnParentDataGridPropertyChanged(object sender, DependencyPropertyChangedEventArgs e)

        {
            var dataGrid = sender as DataGrid;

            if (dataGrid == null)

            {
                throw new ArgumentException("The dependency property can only be attached to a DataGrid", "sender");
            }

            if (e.NewValue is DataGrid parentGrid)
            {
                dataGrid.PreviewMouseWheel += HandlePreviewMouseWheel;
                parentGrid.SetValue(ScrollViewer.CanContentScrollProperty, false);
            }
            else
            {
                dataGrid.PreviewMouseWheel -= HandlePreviewMouseWheel;

                if (e.OldValue is DataGrid oldParentGrid)
                {
                    oldParentGrid.SetValue(ScrollViewer.CanContentScrollProperty, ScrollViewer.CanContentScrollProperty.DefaultMetadata.DefaultValue);
                }
            }
        }

        private static void HandlePreviewMouseWheel(object sender, MouseWheelEventArgs e)

        {
            if (e.Handled)
            {
                return;
            }
            var control = sender as DataGrid;
            if (control == null)
            {
                return;
            }
            e.Handled = true;
            var wheelArgs = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta)
            {
                RoutedEvent = UIElement.MouseWheelEvent,
                Source = control
            };
            var parent = VisualTreeHelper.GetParent(control) as UIElement;
            parent?.RaiseEvent(wheelArgs);
        }

Feel free to use this as follows on your inner DataGrid. Note that The ScrollViewer.CanContentScrollProperty is being set from within the AttachedProperty, which may not be everyone's favourite approach.

<DataGrid>
    <DataGrid.Columns>
        <DataGridTextColumn Binding="{Binding Path=test}" />
    </DataGrid.Columns>
    <DataGrid.RowDetailsTemplate>
        <DataTemplate>
            <DataGrid
                attachedProperties:FixScrollingBehaviorOn.ParentDataGrid="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}"
                Template="{DynamicResource TemplateDataGridPrintAndExport}" />
        </DataTemplate>
    </DataGrid.RowDetailsTemplate>
</DataGrid>

Xaml :

<DataGrid ScrollViewer.CanContentScroll="False">
    <DataGrid.Columns>
        <DataGridTextColumn Binding="{Binding Path=test}" />
    </DataGrid.Columns>
    <DataGrid.RowDetailsTemplate>
        <DataTemplate>
            <DataGrid
                PreviewMouseWheel="DataGrid_PreviewMouseWheel"
                Template="{DynamicResource TemplateDataGridPrintAndExport}" />
        </DataTemplate>
    </DataGrid.RowDetailsTemplate>
</DataGrid>

Code behind :

private void DataGrid_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
  DataGrid dg = new DataGrid();
            if (sender is DataGrid)
            {
                dg = (DataGrid)sender;
            }
            dg.IsEnabled = false;
            await Task.Delay(200);
            dg.IsEnabled = true;
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top