web-dev-qa-db-fra.com

Le défilement de la souris ne fonctionne pas dans une visionneuse de défilement avec une grille de données wpf et des éléments d'interface utilisateur supplémentaires

J'essaie de comprendre comment obtenir le défilement de la souris sur une fenêtre wpf avec un scrollviewer et une grille de données. Le code WPF et C # est ci-dessous

<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>

et le code C # est comme suit

 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;
    }
   }

Le problème auquel je suis confronté est que lorsque vous utilisez la souris pour faire défiler, cela fonctionne bien quand il est au-dessus du bouton, mais dès que je déplace le pointeur de la souris sur la grille et que j'essaie de faire défiler, rien ne se passe. Je suis capable de déplacer la barre de défilement du scrollviewer directement cependant. Je suis toujours un novice wpf, toute aide sur la manière de faire fonctionner le défilement de la souris sur la grille de données serait appréciée. Je devine qu'il devrait y avoir une solution assez facile pour cela mais je n'ai pas été capable de le comprendre

28
user352093

Je pense que la solution de Dave est bonne. Cependant, une recommandation que je ferais est d'attraper l'événement PreviewMouseWheel sur le scrollviewer plutôt que sur la grille de données. Sinon, vous remarquerez peut-être quelques différences mineures selon que vous faites défiler la grille de données ou la barre de défilement elle-même. Le raisonnement est que le scrollviewer gérera le défilement lorsque la souris survolera la barre de défilement et que l'événement datagrid gérera le défilement lorsqu'il survolera la grille de données. Par exemple, une molette de la souris sur la grille de données peut vous amener plus loin dans votre liste que si vous la survoliez avec la barre de défilement. Si vous l'attrapez lors d'un événement de prévisualisation scrollviewer, tous utiliseront la même mesure lors du défilement. De même, si vous le comprenez de cette façon, vous n'aurez pas besoin de nommer l'élément scrollviewer, car vous n'aurez pas besoin d'une référence à l'objet car vous pouvez simplement transtyper l'objet émetteur transmis à l'événement scrollviewer PreviewMouseWheel. Enfin, je vous conseillerais de marquer l’événement traité à la fin de celui-ci, à moins que vous n’ayez besoin de le capturer dans un élément situé plus bas dans la hiérarchie pour une raison quelconque. Exemple ci-dessous:

private void ScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
    {
        ScrollViewer scv = (ScrollViewer)sender;
        scv.ScrollToVerticalOffset(scv.VerticalOffset - e.Delta);
        e.Handled = true;
    }
53
Don B

Je suppose que le DataGrid n'a pas besoin de défiler - Définissez le VerticalScrollBar = "Aucun" sur le DataGrid.

Le DataGrid avale l'événement de défilement de la souris.

Ce que j'ai trouvé est d'utiliser l'événement PreviewMouseWheel pour faire défiler le conteneur que vous souhaitez faire défiler. Vous devrez nommer la visionneuse de défilement pour modifier le décalage vertical.

private void DataGrid_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
    {
       scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset-e.Delta);
    }
12
Dave

J'ai essayé la solution de Don B et elle résout assez bien le problème du défilement sur une grille de données pour les cas où vous n'avez pas d'autres commandes à défilement interne.

Dans le cas où la visionneuse de défilement a pour enfants d’autres commandes défilables et une grille de données, cela nécessite que l’événement ne soit pas marqué comme géré à la fin du gestionnaire d’événements pour la visionneuse de défilement principale, afin de pouvoir également l’attraper les commandes de défilement internes, mais cela a toutefois pour effet secondaire que lorsque le défilement doit se produire uniquement sur le contrôle de défilement interne, il apparaît également sur le visualiseur de défilement principal.

J'ai donc mis à jour la solution de Dave avec la différence entre le mode d'affichage de la visionneuse de défilement, il ne sera donc pas nécessaire de connaître le nom de la visionneuse de défilement:

private void DataGrid_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
    ScrollViewer scrollViewer = (((DependencyObject)sender).GetVisualParent<ScrollViewer>());
    scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset - e.Delta);
}
2
Vadim Tofan

Pour activer l'assistance tactile, vous pouvez également définir ScrollViewer.PanningMode sur None sur votre DataGrid et définir la même propriété sur VerticalFirst ou une autre valeur sur votre niveau supérieur ScrollViewer.

Exemple

<ScrollViewer VerticalScrollBarVisibility="Auto" Margin="5" PanningMode="VerticalFirst">
    <DataGrid ScrollViewer.PanningMode="None" ItemsSource="{Binding Items}" />
</ScrollViewer>

Bien sûr, utilisez également l'événement PreviewMouseWheel comme indiqué par les réponses de Don B pour résoudre le problème de défilement de la souris d'origine.

private static void ScrollViewer_PreviewMouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e)
{
    var scrollViewer = (ScrollViewer)sender;
    scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset - e.Delta);
    e.Handled = true;
}

Ou, vous pouvez simplement définir la propriété attachée suivante à votre ScrollViewer

public class TopMouseScrollPriorityBehavior
{
    public static bool GetTopMouseScrollPriority(ScrollViewer obj)
    {
        return (bool)obj.GetValue(TopMouseScrollPriorityProperty);
    }

    public static void SetTopMouseScrollPriority(ScrollViewer obj, bool value)
    {
        obj.SetValue(TopMouseScrollPriorityProperty, value);
    }

    public static readonly DependencyProperty TopMouseScrollPriorityProperty =
        DependencyProperty.RegisterAttached("TopMouseScrollPriority", typeof(bool), typeof(TopMouseScrollPriorityBehavior), new PropertyMetadata(false, OnPropertyChanged));

    private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var scrollViewer = d as ScrollViewer;
        if (scrollViewer == null)
            throw new InvalidOperationException($"{nameof(TopMouseScrollPriorityBehavior)}.{nameof(TopMouseScrollPriorityProperty)} can only be applied to controls of type {nameof(ScrollViewer)}");
        if (e.NewValue == e.OldValue)
            return;
        if ((bool)e.NewValue)
            scrollViewer.PreviewMouseWheel += ScrollViewer_PreviewMouseWheel;
        else
            scrollViewer.PreviewMouseWheel -= ScrollViewer_PreviewMouseWheel;
    }

    private static void ScrollViewer_PreviewMouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e)
    {
        var scrollViewer = (ScrollViewer)sender;
        scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset - e.Delta);
        e.Handled = true;
    }
}

Utilisation

<ScrollViewer b:TopMouseScrollPriorityBehavior.TopMouseScrollPriority="True" VerticalScrollBarVisibility="Auto" Margin="5" PanningMode="VerticalFirst">
    <DataGrid ScrollViewer.PanningMode="None" ItemsSource="{Binding Items}" />
</ScrollViewer>

Où b: est l'espace de noms qui contient ce comportement

De cette façon, votre no-code-behind est nécessaire et votre application est purement MVVM.

2
fjch1997

@ fjch1997, j'ai utilisé votre solution et cela fonctionne plutôt bien. Il n'y a qu'un seul problème que j'ai trouvé. C'est lié au commentaire de @Vadim Tofan:

Dans le cas où la visionneuse de défilement a pour enfants d’autres commandes défilables et une grille de données, cela nécessite que l’événement ne soit pas marqué comme géré à la fin du gestionnaire d’événements pour la visionneuse de défilement principale, afin de pouvoir également l’attraper les commandes de défilement internes, mais cela a toutefois pour effet secondaire que lorsque le défilement doit se produire uniquement sur le contrôle de défilement interne, il apparaît également sur le visualiseur de défilement principal.

J'ai aussi essayé de supprimer l'instruction e.Handled = true, mais l'effet n'est pas agréable: les deux parchemins sont déplacés dans le même temps. Donc, finalement, j'ai amélioré un peu la méthode du gestionnaire d'événements pour la suivante:

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);
}

Ceci empêche maintenant le défilement extérieur de défiler dans le cas où ScrollViewer intérieur existe.

0
Kostadin Marinov