Reputation: 63
I have this custom DataGrid. What I want to achieve is, when the user has the mouse inside of the DataGrid (which does not have max height), it should allow to scroll the whole parent component inside of the ScrollViewer. But right now I have to move the mouse outside of the DataGrid in order to scroll.
Below is my code:
<Style TargetType="{x:Type DataGrid}">
<Setter Property="Background" Value="Transparent" />
<Setter Property="VerticalAlignment" Value="Top" />
<Setter Property="RowDetailsVisibilityMode" Value="Collapsed" />
<Setter Property="AutoGenerateColumns" Value="False" />
<Setter Property="IsReadOnly" Value="True" />
<Setter Property="HeadersVisibility" Value="None" />
<Setter Property="VerticalContentAlignment" Value="center" />
<Setter Property="GridLinesVisibility" Value="None" />
<Setter Property="EnableRowVirtualization" Value="False" />
<Setter Property="EnableColumnVirtualization" Value="True" />
<Setter Property="VirtualizingPanel.ScrollUnit" Value="Pixel" />
<Setter Property="VirtualizingPanel.VirtualizationMode" Value="Standard" />
<Setter Property="VirtualizingStackPanel.IsVirtualizing" Value="True" />
<Setter Property="ScrollViewer.CanContentScroll" Value="False" />
<Setter Property="BorderBrush" Value="Transparent" />
</Style>
<ScrollViewer VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
// SOME OTHER COMPONENTS
<DataGrid ItemsSource="{Binding ListOfUsers}"
PreviewMouseWheel="ListView_PreviewMouseWheel">
// custom rows
</DataGrid>
Code Behind:
private void ListView_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
// Find the ScrollViewer that contains this ListView
ScrollViewer scrollViewer = FindParentScrollViewer(PatientsListView);
if (scrollViewer != null)
{
// Scroll the ScrollViewer based on the mouse wheel delta
scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset - e.Delta);
e.Handled = true; // Mark the event as handled
}
}
private ScrollViewer FindParentScrollViewer(DependencyObject child)
{
if (child is null) return null;
for (DependencyObject parent = VisualTreeHelper.GetParent(child); parent != null; parent = VisualTreeHelper.GetParent(parent))
{
if (parent is ScrollViewer scrollViewer)
return scrollViewer;
}
return null;
}
I have tried to change ScrollViewer.CanContentScroll value to False or added PreviewMouseWheel event but it does not seem to work.
Update: I came across this webpage and I implemented it into my project https://serialseb.com/blog/2007/09/03/wpf-tips-6-preventing-scrollviewer-from/. It seems to work in case there is no max height for the DataGrid. In case there is a max height for the DataGrid, I was not able to scroll inside of it:
So, here's what I did: First, create a class named ScrollViewerCorrector to define the attached property:
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
public class ScrollViewerCorrector
{
public static bool GetFixScrolling(DependencyObject obj)
{
return (bool)obj.GetValue(FixScrollingProperty);
}
public static void SetFixScrolling(DependencyObject obj, bool value)
{
obj.SetValue(FixScrollingProperty, value);
}
public static readonly DependencyProperty FixScrollingProperty =
DependencyProperty.RegisterAttached(
"FixScrolling",
typeof(bool),
typeof(ScrollViewerCorrector),
new FrameworkPropertyMetadata(false, OnFixScrollingPropertyChanged));
private static List<MouseWheelEventArgs> _reentrantList = new List<MouseWheelEventArgs>();
private static void OnFixScrollingPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if (sender is ScrollViewer viewer)
{
if ((bool)e.NewValue)
{
viewer.PreviewMouseWheel += HandlePreviewMouseWheel;
}
else
{
viewer.PreviewMouseWheel -= HandlePreviewMouseWheel;
}
}
}
private static void HandlePreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
var scrollControl = sender as ScrollViewer;
if (!e.Handled && scrollControl != null && !_reentrantList.Contains(e))
{
var previewEventArg = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta)
{
RoutedEvent = UIElement.PreviewMouseWheelEvent,
Source = sender
};
var originalSource = e.OriginalSource as UIElement;
_reentrantList.Add(previewEventArg);
originalSource.RaiseEvent(previewEventArg);
_reentrantList.Remove(previewEventArg);
if (!previewEventArg.Handled && ((e.Delta > 0 && scrollControl.VerticalOffset == 0) ||
(e.Delta <= 0 && scrollControl.VerticalOffset >= scrollControl.ExtentHeight - scrollControl.ViewportHeight)))
{
e.Handled = true;
var eventArg = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta)
{
RoutedEvent = UIElement.MouseWheelEvent,
Source = sender
};
var parent = (UIElement)((FrameworkElement)sender).Parent;
parent.RaiseEvent(eventArg);
}
}
}
}
Next, modify your ScrollViewer in the XAML to utilize this attached property. Here’s an example of how to apply the attached property to both the outer and inner ScrollViewer:
<Window x:Class="YourNamespace.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:YourNamespace"
Title="MainWindow" Height="423" Width="596">
<Window.Resources>
<Style TargetType="{x:Type ScrollViewer}">
<Setter Property="local:ScrollViewerCorrector.FixScrolling" Value="True" />
</Style>
</Window.Resources>
<Grid>
<ScrollViewer>
<StackPanel>
<TextBlock>Content before ListView</TextBlock>
<ScrollViewer Name="InnerScrollViewer" Height="235">
<StackPanel>
<TextBlock>Scrollable Content 1</TextBlock>
<TextBlock>Scrollable Content 2</TextBlock>
<TextBlock>Scrollable Content 3</TextBlock>
<ListView Background="Red" ItemsSource="{Binding PatientsNotVerified}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel HorizontalAlignment="Right">
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} {1}">
<Binding Path="FirstName" />
<Binding Path="LastName" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0}, {1}">
<Binding Path="Email" />
<Binding Path="Cell" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</ScrollViewer>
<TextBlock>Content after ListView</TextBlock>
</StackPanel>
</ScrollViewer>
</Grid>
</Window>
Upvotes: 0
Views: 31