dotnet / dotnet/wpf

asynchronous call to `RibbonMenuItem.UpdateDropDownPosition` throwing InvalidOperation

Open
#10,976 0 comments 1 reaction 0 assignees View on GitHub
Investigate
Dominant language
C#
Stars
7.7k
Forks
1.3k
Avg merge
1d 11h
Merged PRs (30d)
61

Description

### Description

`RibbonMenuItem.UpdateDropDownPosition` is launched asynchronously (BeginInvoke) from `RibbonMenuItem.OnIsSubmenuOpenChanged`. Suppose an event handler plugged on `SubmenuOpened` closes the sub menu. Then the effective call to UpdateDropDownPosition will happen after the menu has been closed. In that case, an InvalidOperationException will be thrown by `Visual.PointToScreen` called from UpdateDropDownPosition because it makes no sense on a window that is off screen.

I'm aware this looks like an obscure corner case, but I stumbled upon it quite naturally while making a dynamic submenu whose items are regenerated at every opening: if the generated list is empty, I just close the menu. I can work around this by closing the menu asynchronously, except I get a flickering.

Would it be possible to add a check at the start of `UpdateDropDownPosition` to do nothing if `IsSubmenuOpen` is false?

### Reproduction Steps

public void RibbonSubMenuCrash()
{
var d = new RibbonMenuButton() { Label = "DD" }; // drop down button in ribbon
var it = new RibbonMenuItem() { Header = "item with submenu" }; // menu item with a sub item inside d
d.Items.Add(it);
it.Items.Add(new RibbonMenuItem());

// event handler to close it's submenu; sounds pointless but in my real use case it is only closing in some specific circumstances
it.SubmenuOpened += (o, e) =>
{
d.IsDropDownOpen = false; // causes a crash in the last doevents
};

var w = new RibbonWindow();
var r = new System.Windows.Controls.Ribbon.Ribbon();
w.Content = r;
var t = new RibbonTab();
r.Items.Add(t);
var g = new RibbonGroup();
t.Items.Add(g);
g.Items.Add(d);

w.Show();

// simulates opening d, then attempting to open the sub menu
Action doevents = () => Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Background, new Action(delegate { }));
doevents();
d.IsDropDownOpen = true;
doevents();
it.IsSubmenuOpen = true;
doevents(); // invalidoperationexception thrown from async call to RibbonMenuItem.UpdateDropDownPosition
}

### Expected behavior

sub menu not appearing

### Actual behavior

Exception is thrown.

### Regression?

_No response_

### Known Workarounds

Closing the submenu asynchronously:
`Dispatcher.CurrentDispatcher.BeginInvoke(() => d.IsDropDownOpen = false);`

### Impact

Can't have a dynamic sub menu without flickering.

### Configuration

.Net 8
window 10 x64
Most likely not system specific as the reason for the crash is quite clear from the code.

### Other information

_No response_

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.