-1

I have a window, on it, i have a button. The button has a context menu:

<Window>
 <ScrollViewer Height="500">
  <Button Height = "2000">
   <Button.ContextMenu>
    <ContextMenu>
     <MenuItem Header="Item1"></MenuItem>
     <MenuItem Header="Item2"></MenuItem>
     <MenuItem Header="Item3"></MenuItem>
    </ContextMenu>
   </Button.ContextMenu>
  </Button>
 </ScrollViewer>
</Window>

Whenever i right click on button, the context menu will be shown. When i move the mouse out of the context menu and scroll the wheel, the scrollViewer doesn't scroll at all. I have tried many ways on mouse leave or mouse enter events but nothing helps. I want the context menu is still showing but the wheel event is sent to the scrollViewer (or window), if i click outside of the contextmenu, it will close normally.

In win-form application, i have the same issue but i can solve it by using ContextMenuStrip as a replacement for ContextMenu. In WPF, looks like no ContextMenuStrip.

khoa_chung_89
  • 975
  • 9
  • 25

2 Answers2

1

Unfortunately, context menu popups are not ancestors to their hosts when it comes to the WPF logical tree. This means that event bubbling doesn't work from context menu to the scrollviewer, so the scrollviewer won't be informed of any mouse wheel activity.

However, if you just want scrolling up/down in a scrollviewer to work, you can replicate the behavior yourself pretty easily by listening to the MouseWheel event on the context menu and then scrolling the ScrollViewer manually.

eg. the xaml:

<ScrollViewer Height="500" x:Name="MyScrollViewer">
    <Button Height = "2000">
        <Button.ContextMenu>
            <ContextMenu x:Name="MyContextMenu">
                <MenuItem Header="Item1"></MenuItem>
                <MenuItem Header="Item2"></MenuItem>
                <MenuItem Header="Item3"></MenuItem>
            </ContextMenu>
        </Button.ContextMenu>
    </Button>
</ScrollViewer>

In the codebehind for your view you could do something similar to the following:

public MainWindow()
{
    InitializeComponent();
    this.MyContextMenu.MouseWheel += OnContextMenuMouseWheel;
}

private void OnContextMenuMouseWheel(object sender, MouseWheelEventArgs e)
{
    var currentOffset = MyScrollViewer.VerticalOffset;
    var newOffset = currentOffset - e.Delta;
    MyScrollViewer.ScrollToVerticalOffset(newOffset);
    e.Handled = true;
}
John
  • 500
  • 4
  • 15
  • It looks simple enough for scrolling. However, in my case, the button in your code is a custom control. It will be added to somewhere that i don't know. Forcing someone who use my custom control handle the MouseWheel event on their own or provide the MyScrollViewer to my custom control are both weird solution. – khoa_chung_89 Jan 22 '20 at 02:31
0

Instread of using a ContextMenu, you could use a Popup that looks like a ContextMenu:

<ScrollViewer Height="500">
    <Button Height="2000">
        <Popup x:Name="popup" Placement="Mouse" StaysOpen="False"
                   xmlns:theme="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero2">
            <theme:SystemDropShadowChrome Name="Shdw" Color="Transparent" SnapsToDevicePixels="true">
                <Border Name="ContextMenuBorder" Background="#F5F5F5" BorderBrush="#FF959595" BorderThickness="1" SnapsToDevicePixels="True">
                    <ScrollViewer Name="ContextMenuScrollViewer" Grid.ColumnSpan="2" Margin="1,0"
                                      Style="{DynamicResource {ComponentResourceKey TypeInTargetAssembly={x:Type FrameworkElement}, ResourceId=MenuScrollViewer}}">
                        <Grid RenderOptions.ClearTypeHint="Enabled">
                            <Canvas Height="0" Width="0" HorizontalAlignment="Left" VerticalAlignment="Top">
                                <Rectangle Name="OpaqueRect" Height="{Binding ElementName=ContextMenuBorder, Path=ActualHeight}"
                                               Width="{Binding ElementName=ContextMenuBorder, Path=ActualWidth}"
                                               Fill="{Binding ElementName=ContextMenuBorder, Path=Background}"
                                               SnapsToDevicePixels="True"/>
                            </Canvas>
                            <Rectangle Fill="#F1F1F1" HorizontalAlignment="Left" Width="28" Margin="1,2" RadiusX="2" RadiusY="2" SnapsToDevicePixels="True"/>
                            <Rectangle HorizontalAlignment="Left" Width="1" Margin="29,2,0,2" Fill="#E2E3E3" SnapsToDevicePixels="True"/>
                            <Rectangle HorizontalAlignment="Left" Width="1" Margin="30,2,0,2" Fill="White" SnapsToDevicePixels="True"/>
                            <StackPanel>
                                <MenuItem Header="Item1"></MenuItem>
                                <MenuItem Header="Item2"></MenuItem>
                                <MenuItem Header="Item3"></MenuItem>
                            </StackPanel>
                        </Grid>
                    </ScrollViewer>
                </Border>
            </theme:SystemDropShadowChrome>
        </Popup>
        <Button.Triggers>
            <EventTrigger RoutedEvent="MouseRightButtonUp">
                <BeginStoryboard>
                    <Storyboard>
                        <BooleanAnimationUsingKeyFrames Storyboard.TargetName="popup" Storyboard.TargetProperty="IsOpen">
                            <DiscreteBooleanKeyFrame KeyTime="00:00:00.1" Value="True"/>
                        </BooleanAnimationUsingKeyFrames>
                    </Storyboard>
                </BeginStoryboard>
            </EventTrigger>
        </Button.Triggers>
    </Button>
</ScrollViewer>

Remember to add a reference to PresentationFramework.Aero2.dll if you want to use SystemDropShadowChrome.

mm8
  • 163,881
  • 10
  • 57
  • 88
  • In fact, in my project, we custom the default ContextMenu alot. I have try changing our custom ContextMenu to inherit from Popup instead of ContextMenu but there are a lot of thing should be change too. But as John described above, i think your solution is the best way to do. – khoa_chung_89 Jan 22 '20 at 02:39