Frantsev M Frantsev M - 3 months ago 25
C# Question

WPF ContextMenu swallowing all mouse events

I found one bug, and I don't understand, why it's happening.
I have DataGrid. On left mouse button click I want to open ContextMenu.
I made it. It works fine until I start clicking on DataGrid's Cells on random (every time ContextMenu is closing and reappearing at new place). But at one moment something is happening and ContextMenu newer showing (and Window don't response to any Mouse events like clicking on buttons and so on)... until I move cursor out of window and return it (or clicking Alt or F10).

Here some code:

ContextMenu (inside

<DataGrid.Resources>
)

<ContextMenu Style="{StaticResource DefaultContextMenuStyle}" x:Key="cm"
DataContext="{Binding Data, Source={StaticResource WindowViewModel}}">
</ContextMenu>


DataGrid column:

<DataGridTemplateColumn Header="TestHeader" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<DockPanel ContextMenu="{StaticResource cm}" VerticalAlignment="Stretch" Background="Transparent">
<i:Interaction.Behaviors>
<local:OpenContextMenuLeftBehavior />
</i:Interaction.Behaviors>
<TextBlock Style="{StaticResource TextVCenteredCellStyle}" Text="{Binding LPU}" />
</DockPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>


And behavior to open ContextMenu:

public class OpenContextMenuLeftBehavior : Behavior<FrameworkElement>
{
protected override void OnAttached()
{
AssociatedObject.PreviewMouseUp += OpenContextMenu;
}

protected override void OnDetaching()
{
AssociatedObject.PreviewMouseUp -= OpenContextMenu;
}

void OpenContextMenu(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left) {
FrameworkElement Sender = sender as FrameworkElement;
Sender.ContextMenu.PlacementTarget = Sender;
Sender.ContextMenu.IsOpen = true;
}
}
}


When I found that bug, I tried to find some info with Snoop WPF.
And here some info from it:


  1. Before bad thing happened one click on cell was like:

    before bad thing

  2. After bad thing:

    after bad thing

    First event happened at Popup (ContextMenu part?), it doesn't belong to window VisualTree. This Popup seems to stretch all over the main Window and closing, when i move mouse out of it.



So, I lost 2 days on that bug and this is all, that I can find.

Please help me.

EDIT:
I created minimal example:


XAML:

<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid x:Name="dg1" IsReadOnly="True">
<DataGrid.Columns>
<DataGridTemplateColumn Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<DockPanel>
<i:Interaction.Behaviors>
<local:OpenContextMenuLeftBehavior />
</i:Interaction.Behaviors>
<DockPanel.ContextMenu>
<ContextMenu>
<MenuItem Header="123456"></MenuItem>
</ContextMenu>
</DockPanel.ContextMenu>
<TextBlock Text="123" />
</DockPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>


Code behind:

public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
dg1.ItemsSource = new List<int>()
{
1,2,3,4,5,6,7,8,9,0
};
}
}

public class OpenContextMenuLeftBehavior : Behavior<FrameworkElement>
{
protected override void OnAttached()
{
AssociatedObject.PreviewMouseUp += OpenContextMenu;
}

protected override void OnDetaching()
{
AssociatedObject.PreviewMouseUp -= OpenContextMenu;
}

void OpenContextMenu(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left) {
FrameworkElement Sender = sender as FrameworkElement;
if (Sender != null) {
Sender.ContextMenu.PlacementTarget = Sender;
Sender.ContextMenu.IsOpen = true;
Sender.ContextMenu.UpdateLayout();
}
}
}
}


To reproduce this bug just click rapidly on different cells

Answer

Revised Answer

After you published a full, simple example, I'd suggest the following: after you create the ContextMenu

                    Sender.ContextMenu.IsOpen = true;
                    Sender.ContextMenu.PreviewMouseUp += ContextMenu_PreviewMouseUp;

Having defined

    private void ContextMenu_PreviewMouseUp(object sender, MouseButtonEventArgs e)
    {
        var Sender = (sender as ContextMenu);
        if (Sender != null)
        {
            Sender.IsOpen = true;
            e.Handled = true;
        }
    }

(you still have to manage the grid line selection, but it's like off topic here)

Comments