web-dev-qa-db-ja.com

wpfデータグリッドと追加のUI要素があるスクロールビューアでマウススクロールが機能しない

スクロールビューアとデータグリッドが含まれているwpfウィンドウでマウススクロールを機能させる方法を理解しようとしています。 WPFとC#のコードは以下のとおりです

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

そしてC#コードは次のとおりです

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

私が直面している問題は、マウスを使用してスクロールするときに、ボタンの上にあるときに問題なく動作することですが、マウスポインターをグリッドの上に移動してスクロールしようとしても、何も起こりません。スクロールビューアのスクロールバーを直接動かすこともできます。私はまだwpfの初心者なので、マウススクロールをデータグリッド上で機能させる方法についての助けがあれば感謝します。これにはかなり簡単な解決策があるはずだと思いますが、私はそれを理解することができませんでした

35
user352093

デイブの解決策は良いものだと思います。ただし、私が行う1つの推奨事項は、datagridではなくscrollviewerでPreviewMouseWheelイベントをキャッチすることです。そうしないと、データグリッドまたはスクロールバー自体の上でスクロールしているかどうかに基づいて、いくつかの小さな違いに気付く場合があります。推論は、マウスがスクロールバーの上に置かれたときにscrollviewerがスクロールを処理し、datagridイベントがデータグリッドの上にあるときにスクロールを処理するためです。たとえば、データグリッド上でマウスホイールを1回スクロールすると、スクロールバーの上にある場合よりも、リストがさらに下に移動する場合があります。 scrollviewerプレビューイベントでそれをキャッチすると、スクロール時にすべて同じ測定値を使用します。また、この方法でキャッチした場合は、scrollviewerのPreviewMouseWheelイベントに渡された送信者オブジェクトを型キャストするだけなので、オブジェクトへの参照が不要になるため、scrollviewer要素に名前を付ける必要はありません。最後に、何らかの理由で階層のさらに下の要素でイベントをキャッチする必要がない限り、イベントの最後に処理されるイベントをマークすることをお勧めします。以下の例:

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

DataGridがスクロールする必要がないと想定しています-DataGridでVerticalScrollBar = "None"を設定します。

DataGridはマウススクロールイベントを飲み込みます。

私が見つけたのは、PreviewMouseWheelイベントを使用して、スクロールするコンテナーをスクロールすることです。垂直オフセットを変更するには、scrollviewerに名前を付ける必要があります。

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

私はドンBの解決策を試しましたが、他の内部スクロール可能なコントロールがない場合のデータグリッド上でのスクロールの問題をかなりうまく解決します。

スクロールビューアーが子として他のスクロール可能なコントロールとデータグリッドを持っている場合、メインスクロールビューアーのイベントハンドラーの終わりでイベントが処理済みとしてマークされないようにする必要があります。内側のスクロール可能なコントロール。ただし、内側のスクロール可能なコントロールでのみスクロールが発生する場合、メインスクロールビューアでもスクロールが発生するという副作用があります。

そこで、スクロールビューアの見つけ方の違いでデイブのソリューションを更新しました。スクロールビューアの名前を知る必要はありません。

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

タッチサポートを有効にするには、ScrollViewer.PanningModeNoneDataGridに設定し、同じプロパティをVerticalFirstまたは最上位のその他の値に設定ScrollViewer

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

もちろん、元のマウススクロールの問題を修正するには、Don Bの回答で示されている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;
}

または、次の添付プロパティを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;
    }
}

使用方法

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

ここで、b:はこの動作を含む名前空間です

このようにして、分離コードは不要であり、アプリは純粋にMVVMです。

2
fjch1997

Don Bのソリューションの改善点は、ScrollToVerticalOffsetの使用を避けることです。

scv.ScrollToVerticalOffset(scv.VerticalOffset - e.Delta);

VerticalOffset - Deltaはかなり不快な体験になります。 ScrollViewerは、それよりも動きをスムーズにするために多くのことを考慮しています。また、dpiやその他の要因に基づいてデルタも縮小すると思います...

PreviewMouseWheelEventをキャッチして処理し、MouseWheelEventを目的のScrollViewerに送信する方がよいことがわかりました。ドンBのソリューションの私のバージョンは次のようになります。

private void ScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
    var eventArg = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta);
    eventArg.RoutedEvent = UIElement.MouseWheelEvent;
    eventArg.Source = e.Source;

    ScrollViewer scv = (ScrollViewer)sender;
    scv.RaiseEvent(eventArg);
    e.Handled = true;
}
2
Stanley.Goldman

@ fjch1997、私はあなたのソリューションを使用しましたが、それはかなりうまくいきます。私が見つけた問題は1つだけです。 @Vadim Tofanのコメントに関連しています:

スクロールビューアーが子として他のスクロール可能なコントロールとデータグリッドを持っている場合、メインスクロールビューアーのイベントハンドラーの終わりでイベントが処理済みとしてマークされないようにする必要があります。内側のスクロール可能なコントロール。ただし、内側のスクロール可能なコントロールでのみスクロールが発生する場合、メインスクロールビューアでもスクロールが発生するという副作用があります。

私はe.Handled = trueステートメントも削除しようとしましたが、効果はナイスではなく、両方のスクロールが同時に移動しました。そこで、最後に少しイベントハンドラーメソッドを次のように拡張しました。

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

これにより、内側のScrollViewerが存在する場合に外側のスクローラーがスクロールするのを防ぎます。

1

ここに投稿されたほとんどの解決策を見た人は、基本的にそれらはすべて正しいですが、適用できる最も簡単でエレガントな構文があると思います。

データグリッドでスクロールする必要はなく、代わりにMainWindowでスクロールしたいとします

「デイブ」と「ウラディム・トファン」の答えを休む

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

申し訳ありませんが、英語が下手です。

1
dandush03