Mouse scroll not working in a scroll viewer with a

2020-05-25 04:38发布

I am trying to figure out how to get the mouse scroll working on a wpf window with a scrollviewer and a datagrid within it. The WPF and C# code is below

<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Grid Grid.Row="0">

            <Border Name="DataGridBorder" BorderThickness="2"  Margin="1" CornerRadius="4" BorderBrush="#FF080757">
                <dg:DataGrid AutoGenerateColumns="False" Name="ValuesDataGrid" 
                         BorderThickness="0" CanUserResizeColumns="True" FontWeight="Bold" HorizontalScrollBarVisibility="Auto" 
                         CanUserReorderColumns="False" IsReadOnly="True" IsTextSearchEnabled="True" AlternationCount="2"
                         SelectionMode="Extended" GridLinesVisibility="All"                
                         HeadersVisibility="Column" CanUserAddRows="False" CanUserDeleteRows="False" CanUserResizeRows="False" CanUserSortColumns="False"
                         RowDetailsVisibilityMode="Collapsed"  SelectedIndex="0"
                         RowStyle="{StaticResource CognitiDataGridRowStyle}"
                         >

                    <dg:DataGrid.Columns>
                        <dg:DataGridTemplateColumn Header="Title" >
                            <dg:DataGridTemplateColumn.CellTemplate>
                                <DataTemplate>
                                    <StackPanel Orientation="Horizontal" >
                                        <TextBlock HorizontalAlignment="Left" VerticalAlignment="Center" Text="{Binding Path=Name}" FontWeight="Normal"  />
                                    </StackPanel>
                                </DataTemplate>
                            </dg:DataGridTemplateColumn.CellTemplate>
                        </dg:DataGridTemplateColumn>
                    </dg:DataGrid.Columns>
                </dg:DataGrid>
            </Border>
        </Grid>
        <Button Grid.Row="1" Height="90" >hello world</Button>
    </Grid>
</ScrollViewer>

and the C# code is as follows

 public partial class Window1 : Window
  {
     public Window1()
     {
        InitializeComponent();
        initialize();
      }

    public void initialize()
    {
        ObservableCollection<MyObject> testList = new ObservableCollection<MyObject>();

        for (int i = 0; i < 20; i++)
        {
            MyObject my = new MyObject("jack " + i);
            testList.Add(my);
        }

        ValuesDataGrid.ItemsSource = testList;



    }
}

public class MyObject
{
    public string Name { get; set; }



    public MyObject(string name)
    {
        Name = name;
    }
   }

The problem i am facing is that when using the mouse to scroll, it works fine when it is over the button but as soon as i move the mouse pointer over the grid and try to scroll, nothing happens. I am able to move the scrollbar of the scrollviewer directly though. I am still a wpf novice so any help on how to get the mouse scroll to work over the datagrid would be appreciated. I am guessing there should be a pretty easy solution for this but I havent been able to figure it out

9条回答
我只想做你的唯一
2楼-- · 2020-05-25 05:13

@fjch1997, I used your solution and it works pretty well. There is only one problem which I found. It's connected with the comment of @Vadim Tofan:

For the case when the scroll viewer has as children other scrollable controls and a data grid, then that requires that the event shouldn't be marked as handled at the end of the event handler for the main scroll viewer so it could be catched also in the inner scrollable controls, however that has the side effect that when the scroll should occur only on the inner scrollable control, it will also occur on the main scroll viewer.

I also tried to remove e.Handled = true statement, but then the effect is not nice - both scrolls are moved in the same time. So, finally I enhanced a little bit event handler method to the following one:

private static void ScrollViewer_PreviewMouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e)
{
    ScrollViewer scrollViewer = (ScrollViewer)sender;
    FrameworkElement origicalControlSender = e.OriginalSource as FrameworkElement;

    ScrollViewer closestScrollViewer = origicalControlSender.GetParent<ScrollViewer>();

    if (closestScrollViewer != null && !ReferenceEquals(closestScrollViewer, scrollViewer))
    {
        return;
    }

    scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset - e.Delta);
    e.Handled = true;
}

public static T GetParent<T>(this FrameworkElement control)
    where T : DependencyObject
{
    FrameworkElement parentElement = control?.Parent as FrameworkElement;

    if (parentElement == null)
    {
        return null;
    }

    T parent = parentElement as T;

    if (parent != null)
    {
        return parent;
    }

    return GetParent<T>(parentElement);
}

This now prevents the outside scroller to scroll in case inner ScrollViewer exists.

查看更多
淡お忘
3楼-- · 2020-05-25 05:19

I'm assuming the DataGrid does not need to scroll - Set the VerticalScrollBar="None" on the DataGrid.

The DataGrid swallows the mouse scroll event.

What I found is to use the PreviewMouseWheel event to scroll the container that you want to scroll. You will need to name the scrollviewer to change the Vertical offset.

private void DataGrid_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
    {
       scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset-e.Delta);
    }
查看更多
▲ chillily
4楼-- · 2020-05-25 05:20

Guys I`ve seen most of the solution posted over here and fundamentally all of them are right, but I think there is an easiest and more elegant syntax that we could apply.

Assuming that we don't need to scroll with our data grid and instead we would like to scroll with our MainWindow

fallowing "Dave" and "Vladim Tofan" answer

Private Void Scrlll(Object sebder, MouseWheelEventArgs e)
{
   var windows = (Window.GetWindow(this) as MainWindow).MainScroll;
   windows.ScrollToVerticalOffset(windows.VerticalOffset - e.Delta);
}

Sorry, for bad english.

查看更多
Rolldiameter
5楼-- · 2020-05-25 05:22

Here is a larger example of creating a WPF behavior that scrolls the DataGrid.

First define the following base class for gluing together framework element with behavior class and its behavior implementation.

using System.Diagnostics.CodeAnalysis;
using System.Windows;
using System.Windows.Input;

namespace SomeAcme.Client.Infrastructure
{


    /// <summary>
    /// Behavior handler class for creating WPF behaviors.
    /// </summary>  
     [ExcludeFromCodeCoverage] 
    public class BehaviorHandler<TAssociatedObject, TBehaviorClass> 
        where TAssociatedObject: DependencyObject
        where TBehaviorClass : class, IAssociationBehavior, new()
    {

        public BehaviorHandler()
        {
        }

        public static TBehaviorClass GetBehavior(DependencyObject obj)
        {
            if (obj == null)
                return null; 

            return (TBehaviorClass)obj.GetValue(BehaviorProperty);
        }

        public static void SetBehavior(DependencyObject obj, TBehaviorClass value)
        {
            if (obj != null)
            {
                obj.SetValue(BehaviorProperty, value);
            }
        }

        // Using a DependencyProperty as the backing store for Behavior.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty BehaviorProperty =
            DependencyProperty.RegisterAttached("Behavior", typeof(TBehaviorClass), typeof(object), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits));


        public void FindOrCreateBehaviorOnDemand(DependencyObject dependencyObject)
        {
             //Apply the behavior 
             TBehaviorClass behavior = FindOrCreateBehavior(dependencyObject);
             if (behavior != null)
             {
                 dependencyObject.SetValue(BehaviorProperty, behavior); 
             }
        }

        public TBehaviorClass FindOrCreateBehavior(DependencyObject dependencyObject)
        {
            if (dependencyObject == null)
                return null;
            var associatedObject = dependencyObject;
            if (associatedObject == null)
                return null;
            var behavior = dependencyObject.GetValue(BehaviorProperty) as TBehaviorClass;
            if (behavior == null)
            {
                var behaviorAssociated = new TBehaviorClass();
                if (behaviorAssociated == null)
                    return null;
                behaviorAssociated.InitializeAssociation(associatedObject, AssociatedCommands);
                return behaviorAssociated;
            }
            else
            {
                return behavior;
            }
        } //TBehaviorClass FindOrCreateBehavior 

        /// <summary>
        /// Set the associated commands to pass into the WPF behavior, if desired
        /// </summary>
        public ICommand[] AssociatedCommands { get; set; }

    }

}

This looks a bit more complex, but it simplifies boiler plate coding when it comes to gluing together the behavior class with the framework element. In this particular case we will not need to use an associated command.

Our scroll viewer behavior then looks like this:

    using SomeAcme.Client.Infrastructure;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Input;

    namespace SomeAcme.Client.Infrastructure
    {

        public static class ScrollViewerMouseWheelScrollBehavior
        {


                public static string GetUseMouseScrollForScrollViewer(DependencyObject obj)
                {
                    if (obj != null)
                    {
                        return (string)obj.GetValue(UseMouseScrollForScrollViewerProperty);
                    }
                    else
                        return null;
                }

                public static void SetUseMouseScrollForScrollViewer(DependencyObject obj, string value)
                {
                    if (obj != null)
                    {
                        obj.SetValue(UseMouseScrollForScrollViewerProperty, value);
                    }
                }


 public static readonly DependencyProperty UseMouseScrollForScrollViewerCommandProperty =
                    DependencyProperty.RegisterAttached("UseScrollForScrollViewer", typeof(string), typeof(ScrollViewerMouseWheelScrollBehavior),
                        new PropertyMetadata(new PropertyChangedCallback(OnUseScrollForScrollViewerSet)));

                public static void OnUseScrollForScrollViewerSet(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs)
                {
                    bool useScroll;
                    bool.TryParse(eventArgs.NewValue?.ToString(), out useScroll);
                    if (useScroll)
                    {
                        var behaviorHandler =
                            new BehaviorHandler<ScrollViewer, ScrollViewerMouseScrollBehaviorImplementation>();
                        //behaviorHandler.AssociatedCommands = 
                        //    new Microsoft.Practices.Prism.Commands.DelegateCommand<object>[] { (Microsoft.Practices.Prism.Commands.DelegateCommand<object>)eventArgs.NewValue };
                        behaviorHandler.FindOrCreateBehaviorOnDemand(dependencyObject);
                    }
                }

            }

    }

    namespace SomeAcme.Client.Infrastructure
    {
        public class ScrollViewerMouseScrollBehaviorImplementation : IAssociationBehavior
        {
            public void InitializeAssociation(DependencyObject associatedObject, params ICommand[] commands)
            {
                //TODO: Add commands to associate
                var scrollViewer = associatedObject as ScrollViewer;
                if (scrollViewer != null)
                {
                    scrollViewer.PreviewMouseWheel += ScrollViewer_PreviewMouseWheel;
                }
            }

            private void ScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
            {
                var scrollViewer = sender as ScrollViewer;
                if (scrollViewer != null)
                {
                    scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset - e.Delta);
                }
            }
        }
    }

In XAML we then can first define the namespaces:

xmlns:local="clr-namespace:SomeAcme.Client.Infrastructure"
xmlns:sys="clr-namespace:System;assembly=mscorlib"

Finally we are ready to use the mouse wheel scroll by using the WPF behavior in the XAML.

..

 <TabControl Grid.Row="1">
        <TabItem Header="Skjemafelter">
            <ScrollViewer Height="700" local:ScrollViewerMouseWheelScrollBehavior.UseMouseScrollForScrollViewer="{x:Static sys:Boolean.TrueString}">
                <ListView x:Name="ColumnsListView" ItemsSource="{Binding CurrentFields}">
                    <ListView.View>
                        <GridView>

I have tested and verified that this approach works. For developers working with a WPF app, utilizing WPF behaviors keeps amount of code in code behind still to the minimum bits and staying faitfully to the MVVM approach.

查看更多
神经病院院长
6楼-- · 2020-05-25 05:22

I found that ScrollViewer can't be concatenated, which means if it is concatenated like in your case the Grid starts under the ScrollViewer tag and in the Grid we have DataGrid and in the DataGrid again the ScrollViewer property has been set. i.e.

  <Grid>
     <Grid.RowDefinitions>
        <RowDefinition Height="30" />
        <RowDefinition Height="45" />
        <RowDefinition Height="100*" />
        <RowDefinition Height="105" />
     </Grid.RowDefinitions>
     <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="*" />
     </Grid.ColumnDefinitions>

     <Label Grid.Row="0"
            Grid.Column="0"
            Margin="10,0,0,0"
            HorizontalAlignment="Left"
            VerticalAlignment="Center"
            FontWeight="Bold"
            Content="Vessel: " />
     <TextBox Height="30"
              Width="300"
              Margin="70,0,0,0"
              HorizontalAlignment="Left"
              BorderThickness="1,1,1,1"
              IsReadOnly="True"
              Name="txtVessel" />

     <Label  Grid.Row="0"
             Grid.Column="2"
             Margin="0,0,185,0"
             HorizontalAlignment="Right"
             VerticalAlignment="Center"
             FontWeight="Bold"
             Content="Month:" />

     <StackPanel Orientation="Horizontal"
                 Grid.Row="0"
                 Grid.Column="2"
                 Margin="0,0,0,0"
                 HorizontalAlignment="Right">

        <ComboBox BorderThickness="2"
                  HorizontalAlignment="Right"
                  Name="CmbMonth"
                  VerticalAlignment="Center"
                  Width="90" />
        <ComboBox BorderThickness="2"
                  HorizontalAlignment="Right"
                  Margin="5,0,0,0"
                  Name="CmbYear"
                  VerticalAlignment="Center"
                  Width="90" />

     </StackPanel>

     <Grid Grid.Row="1"
           Grid.ColumnSpan="2">
        <Grid.RowDefinitions>
           <RowDefinition Height="45" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
           <ColumnDefinition Width="30" />
           <ColumnDefinition Width="220" />
           <ColumnDefinition Width="80" />
           <ColumnDefinition Width="80" />
           <ColumnDefinition Width="80" />
           <ColumnDefinition Width="80" />
           <ColumnDefinition Width="120" />
           <ColumnDefinition Width="120" />
           <ColumnDefinition Width="140*" />
        </Grid.ColumnDefinitions>

        <Border BorderBrush="Black"
                BorderThickness="0,1,1,1"
                Grid.Row="0"
                Grid.Column="0" />
        <Border BorderBrush="Black"
                BorderThickness="0,1,1,1"
                Grid.Row="0"
                Grid.Column="1" />
        <Border BorderBrush="Black"
                BorderThickness="0,1,1,1"
                Grid.Row="0"
                Grid.Column="2" />
        <Border BorderBrush="Black"
                BorderThickness="0,1,1,1"
                Grid.Row="0"
                Grid.Column="3" />
        <Border BorderBrush="Black"
                BorderThickness="0,1,1,1"
                Grid.Row="0"
                Grid.Column="4" />
        <Border BorderBrush="Black"
                BorderThickness="0,1,1,1"
                Grid.Row="0"
                Grid.Column="5" />
        <Border BorderBrush="Black"
                BorderThickness="0,1,1,1"
                Grid.Row="0"
                Grid.Column="6" />
        <Border BorderBrush="Black"
                BorderThickness="0,1,1,1"
                Grid.Row="0"
                Grid.Column="7" />
        <Border BorderBrush="Black"
                BorderThickness="0,1,1,1"
                Grid.Row="0"
                Grid.Column="8" />

        <Label  Grid.Row="0"
                Grid.Column="1"
                Margin="0,0,0,0"
                HorizontalAlignment="Center"
                VerticalAlignment="Center"
                FontWeight="Bold"
                Content="Item" />

        <Label  Grid.Row="0"
                Grid.Column="2"
                Margin="0,0,0,0"
                HorizontalAlignment="Center"
                VerticalAlignment="Center"
                FontWeight="Bold"
                Content="Maker" />

        <Label  Grid.Row="0"
                Grid.Column="3"
                Margin="0,0,0,0"
                HorizontalAlignment="Center"
                VerticalAlignment="Center"
                FontWeight="Bold"
                Content="Model" />

        <Label  Grid.Row="0"
                Grid.Column="4"
                Margin="0,0,0,0"
                HorizontalAlignment="Center"
                VerticalAlignment="Center"
                FontWeight="Bold"
                Content=" Part No.&#x0a;Serial No." />

        <Label  Grid.Row="0"
                Grid.Column="5"
                Margin="0,0,0,0"
                HorizontalAlignment="Center"
                VerticalAlignment="Center"
                FontWeight="Bold"
                Content="Condition" />

        <Label  Grid.Row="0"
                Grid.Column="6"
                Margin="0,0,0,0"
                HorizontalAlignment="Center"
                VerticalAlignment="Center"
                FontWeight="Bold"
                Content="   Onboard&#x0a; Calibr/Test" />

        <Label  Grid.Row="0"
                Grid.Column="7"
                Margin="0,0,0,0"
                HorizontalAlignment="Center"
                VerticalAlignment="Center"
                FontWeight="Bold"
                Content="     Shore&#x0a; Callibration" />

        <Label  Grid.Row="0"
                Grid.Column="8"
                Margin="0,0,0,0"
                HorizontalAlignment="Center"
                VerticalAlignment="Center"
                FontWeight="Bold"
                Content="Remarks" />

     </Grid>

     <Border Grid.Row="2"
             Grid.ColumnSpan="2">
        <ScrollViewer Grid.Row="2"
                      Grid.ColumnSpan="2"
                      CanContentScroll="True"
                      HorizontalScrollBarVisibility="Disabled"
                      VerticalScrollBarVisibility="Auto"
                      Name="ScrollViewer3"
                      Margin="0,0,0,0">
           <Grid Name="grdOnBoardCalibrationRecord"
                 Margin="0,0,0,0">
              <Grid.ColumnDefinitions>
                 <ColumnDefinition Width="30"></ColumnDefinition>
                 <ColumnDefinition Width="220"></ColumnDefinition>
                 <ColumnDefinition Width="80"></ColumnDefinition>
                 <ColumnDefinition Width="80"></ColumnDefinition>
                 <ColumnDefinition Width="80"></ColumnDefinition>
                 <ColumnDefinition Width="80"></ColumnDefinition>
                 <ColumnDefinition Width="120"></ColumnDefinition>
                 <ColumnDefinition Width="120"></ColumnDefinition>
                 <ColumnDefinition Width="140*"></ColumnDefinition>
              </Grid.ColumnDefinitions>
              <Border Grid.Column="0"
                      BorderThickness="1,0,1,1"
                      BorderBrush="Black"
                      Grid.RowSpan="26"></Border>
              <Border Grid.Column="1"
                      BorderThickness="0,1,1,1"
                      Grid.RowSpan="26"></Border>
              <Border Grid.Column="2"
                      BorderThickness="0,1,1,1"
                      Grid.RowSpan="26"></Border>
              <Border Grid.Column="3"
                      BorderThickness="0,1,1,1"
                      Grid.RowSpan="26"></Border>
              <Border Grid.Column="4"
                      BorderThickness="0,1,1,1"
                      Grid.RowSpan="26"></Border>
              <Border Grid.Column="5"
                      BorderThickness="0,1,1,1"
                      Grid.RowSpan="26"></Border>
              <Border Grid.Column="6"
                      BorderThickness="0,1,1,1"
                      Grid.RowSpan="26"></Border>
              <Border Grid.Column="7"
                      BorderThickness="0,1,1,1"
                      Grid.RowSpan="26"></Border>
              <Border Grid.Column="8"
                      BorderThickness="0,1,1,1"
                      Grid.RowSpan="26"></Border>
           </Grid>
        </ScrollViewer>
     </Border>
     <Grid Grid.Row="3"
           Grid.ColumnSpan="2">
        <Grid.RowDefinitions>
           <RowDefinition Height="30"></RowDefinition>
           <RowDefinition Height="30"></RowDefinition>
           <RowDefinition Height="40"></RowDefinition>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
           <ColumnDefinition Width="*"></ColumnDefinition>
        </Grid.ColumnDefinitions>

        <TextBox Grid.Row="0"
                 Grid.Column="0"
                 Height="30"
                 Width="300"
                 TextAlignment="Center"
                 Background="Gray"
                 IsReadOnly="True"
                 Margin="0,0,0,0"
                 HorizontalAlignment="Right"
                 VerticalAlignment="Bottom"
                 BorderThickness="1,1,1,1"
                 Name="txtChiefEngineer" />
        <Label  Grid.Row="1"
                Grid.Column="1"
                Margin="0,0,100,0"
                HorizontalAlignment="Right"
                VerticalAlignment="Center"
                FontWeight="Bold"
                Content="Chief Engineer" />
        <StackPanel Orientation="Horizontal"
                    Grid.Row="2"
                    Margin="0,0,0,0">
           <Label Name="lblonshorecomment"
                  Content=" Shore Comment : "
                  HorizontalAlignment="Center"
                  Margin="5,0,0,0"
                  FontWeight="Bold"
                  VerticalAlignment="Center"
                  FontFamily="Calibri"
                  FontStyle="Normal"
                  FontSize="14"></Label>
           <TextBox  BorderThickness="1"
                     FontWeight="Normal"
                     IsReadOnly="True"
                     Height="44"
                     Width="878"
                     TextWrapping="Wrap"
                     AcceptsReturn="True"
                     HorizontalAlignment="left"
                     Margin="0,0,0,0"
                     Name="txtShoreComment"
                     VerticalAlignment="Center" />
        </StackPanel>

     </Grid>
  </Grid>

查看更多
手持菜刀,她持情操
7楼-- · 2020-05-25 05:23

I think Dave's solution is a good one. However, one recommendation I'd make is to catch the PreviewMouseWheel event on the scrollviewer instead of on the datagrid. If you don't, you might notice some minor differences based on whether you're scrolling over the datagrid or the scroll bar itself. The reasoning is that the scrollviewer will be handling scrolling when the mouse is hovered over the scrollbar, and the datagrid event will handle the scrolling when over the datagrid. For instance, one mouse wheel scroll over the datagrid might bring you farther down your list then it would when over the scroll bar. If you catch it on scrollviewer preview event, all will use the same measurement when scrolling. Also, if you catch it this way, you won't need to name the scrollviewer element, as you won't need a reference to the object since you can just type cast the sender object passed into the scrollviewer PreviewMouseWheel event. Lastly, I'd recommend marking the event handled at the end of the event, unless you need to catch it in an element further down the heirarchy for some reason. Example below:

private void ScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
    {
        ScrollViewer scv = (ScrollViewer)sender;
        scv.ScrollToVerticalOffset(scv.VerticalOffset - e.Delta);
        e.Handled = true;
    }
查看更多
登录 后发表回答