AvaloniaUI / Avalonia

Develop Desktop, Embedded, Mobile and WebAssembly apps with C# and XAML. The most popular .NET UI client technology
https://avaloniaui.net
MIT License
25.23k stars 2.19k forks source link

TrayIcon.Menu: NativeMenu. Opening event is never invoked #8076

Open timschneeb opened 2 years ago

timschneeb commented 2 years ago

Describe the bug I'm trying to create a tray icon menu with dynamic menu items. In order to refresh all items right before right-clicking the tray, I'm subscribing to the NeedsUpdate event of the NativeMenu that is assigned to the tray icon. However, that event is never invoked. Same issue with the Opening event; that isn't raised either.

To Reproduce Steps to reproduce the behavior:

In my App.xaml I've declared a TrayIcon:

<TrayIcon.Icons>
    <TrayIcons>
        <TrayIcon 
                  Icon="/Resources/icon_white.ico" 
                  ToolTipText="App" 
                  Clicked="TrayIcon_OnClicked">
            <TrayIcon.Menu>
                <NativeMenu Items="{Binding MenuItems}"
                            NeedsUpdate="NativeMenu_OnNeedsUpdate"/>
            </TrayIcon.Menu>
        </TrayIcon>
    </TrayIcons>
</TrayIcon.Icons>

In App.xaml.cs:

private void NativeMenu_OnNeedsUpdate(object? sender, EventArgs e)
{
    Log.Debug("This line gets never called");
}

Expected behavior Invoke events properly when opening a tray icon menu

Desktop:

maxkatz6 commented 2 years ago

In order to refresh all items right before right-clicking the tray, I'm subscribing to the NeedsUpdate event of the NativeMenu that is assigned to the tray icon

I don't think that's how it supposed to be used. From what I see that's event is raised when some property of the menu was changed. @danwalmsley

Same issue with the Opening event; that isn't raised either.

This one should be raised. @jmacato

billhenn commented 1 year ago

I'm on Windows and am trying to dynamically update my app's tray icon's menu items as the menu is opening. But the NeedsUpdate event never seems to fire for me either. Can this event be updated to fire properly? Thanks!

maxkatz6 commented 1 year ago

@billhenn have you tried to subscribe on Opening event?

billhenn commented 1 year ago

@maxkatz6 - Yes, and I see the same thing as the original poster @ThePBone. Neither event (NeedsUpdate or Opening) seems to fire, so there's no place to do any updating of menu items before the menu shows, at least on Windows.

Per the documentation, NeedsUpdate is the event we're supposed to use to alter menu items. In my scenario, I want to dynamically build menu items based on current app state each time the menu is opened.

maxkatz6 commented 1 year ago

Per the documentation, NeedsUpdate is the event we're supposed to use to alter menu items.

Can you send me this documentation? Or is it just a incode xml doc?

timschneeb commented 1 year ago

It's a xml doc: https://github.com/AvaloniaUI/Avalonia/blob/2c7ae3a50fdbf7878f8dadc72fc5ddf5b24cd546/src/Avalonia.Controls/NativeMenu.cs#L23

billhenn commented 1 year ago

Yes and the XML doc for Opening says: Do not update the menu in this event; use <see cref="NeedsUpdate"/>.

PhantomGamers commented 1 year ago

Has anyone figured out a workaround to this? This problem still exists on 11.0.0-rc1.1 and affects both NeedsUpdate and Opening

timunie commented 1 year ago

Indeed, looks like Opening-event is never called. However, I found a way to make dynamic menus work. Here is a draft that for sure needs some more love (null-checks, initial population etc.)

public ObservableCollection<string> MenuItems { get; } = new();

public App() : base()
{
    MenuItems.CollectionChanged += (sender, e) =>
    {
        // Add new Items
        foreach (var newItem in e.NewItems ?? Array.Empty<string>())
        {
            TrayIcon.GetIcons(this).First().Menu.Items.Add(new NativeMenuItem((string)newItem));
        }

        // Remove old items
        foreach (var oldItem in e.OldItems ?? Array.Empty<string>())
        {
            var itemToRemove = TrayIcon.GetIcons(this).First().Menu.Items
                .FirstOrDefault(x => ((NativeMenuItem)x).Header == (string)oldItem);

            TrayIcon.GetIcons(this).First().Menu.Items.Remove(itemToRemove);
        }
    };
}

My App.axaml looks like this:

<TrayIcon.Icons>
  <TrayIcons>
    <TrayIcon 
      x:DataType="sandbox:App"
      Icon="test_icon.ico" 
      ToolTipText="App" >
      <TrayIcon.Menu>
        <NativeMenu />
      </TrayIcon.Menu>
    </TrayIcon>
  </TrayIcons>
</TrayIcon.Icons>

And inside a Button-click I do this:

(Application.Current as App)?.MenuItems.Add($"Added Item {i++}");