Browse Source

add a managed native menu exporter for win32 tray icon menus.

pull/6610/head
Dan Walmsley 5 years ago
parent
commit
8b410e83f9
  1. 3
      src/Avalonia.Controls/ApiCompatBaseline.txt
  2. 13
      src/Avalonia.Controls/NativeMenu.Export.cs
  3. 17
      src/Avalonia.Controls/Platform/ITopLevelNativeMenuExporter.cs
  4. 3
      src/Avalonia.Controls/Platform/ITrayIconImpl.cs
  5. 4
      src/Avalonia.Controls/TrayIcon.cs
  6. 9
      src/Avalonia.DesignerSupport/Remote/TrayIconStub.cs
  7. 17
      src/Windows/Avalonia.Win32/TrayIconImpl.cs
  8. 58
      src/Windows/Avalonia.Win32/Win32NativeToManagedMenuExporter.cs

3
src/Avalonia.Controls/ApiCompatBaseline.txt

@ -37,6 +37,7 @@ MembersMustExist : Member 'public System.Action<Avalonia.Size> Avalonia.Controls
MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.Resized.set(System.Action<Avalonia.Size>)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Notifications.NotificationCard.CloseOnClickProperty' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.Platform.ITopLevelNativeMenuExporter.SetNativeMenu(Avalonia.Controls.NativeMenu)' is present in the contract but not in the implementation.
EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints Avalonia.Platform.ExtendClientAreaChromeHints.Default' is (System.Int32)2 in the implementation but (System.Int32)1 in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Nullable<Avalonia.Size> Avalonia.Platform.ITopLevelImpl.FrameSize' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Nullable<Avalonia.Size> Avalonia.Platform.ITopLevelImpl.FrameSize.get()' is present in the implementation but not in the contract.
@ -56,4 +57,4 @@ InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platfor
MembersMustExist : Member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size, Avalonia.Platform.PlatformResizeReason)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.ITrayIconImpl Avalonia.Platform.IWindowingPlatform.CreateTrayIcon()' is present in the implementation but not in the contract.
Total Issues: 57
Total Issues: 58

13
src/Avalonia.Controls/NativeMenu.Export.cs

@ -52,15 +52,10 @@ namespace Avalonia.Controls
}
public static readonly AttachedProperty<NativeMenu> MenuProperty
= AvaloniaProperty.RegisterAttached<NativeMenu, AvaloniaObject, NativeMenu>("Menu"/*, validate:
(o, v) =>
{
if(!(o is Application || o is TopLevel))
throw new InvalidOperationException("NativeMenu.Menu property isn't valid on "+o.GetType());
return v;
}*/);
= AvaloniaProperty.RegisterAttached<NativeMenu, AvaloniaObject, NativeMenu>("Menu");
public static void SetMenu(AvaloniaObject o, NativeMenu menu) => o.SetValue(MenuProperty, menu);
public static NativeMenu GetMenu(AvaloniaObject o) => o.GetValue(MenuProperty);
static NativeMenu()
@ -79,6 +74,10 @@ namespace Avalonia.Controls
{
GetInfo(tl).Exporter?.SetNativeMenu(args.NewValue.GetValueOrDefault());
}
else if(args.Sender is INativeMenuExporterProvider provider)
{
provider.NativeMenuExporter?.SetNativeMenu(args.NewValue.GetValueOrDefault());
}
});
}
}

17
src/Avalonia.Controls/Platform/ITopLevelNativeMenuExporter.cs

@ -1,14 +1,25 @@
using System;
using System.Collections.Generic;
using Avalonia.Platform;
#nullable enable
namespace Avalonia.Controls.Platform
{
public interface ITopLevelNativeMenuExporter
public interface INativeMenuExporter
{
void SetNativeMenu(NativeMenu menu);
}
public interface ITopLevelNativeMenuExporter : INativeMenuExporter
{
bool IsNativeMenuExported { get; }
event EventHandler OnIsNativeMenuExportedChanged;
void SetNativeMenu(NativeMenu menu);
}
public interface INativeMenuExporterProvider
{
INativeMenuExporter? NativeMenuExporter { get; }
}
public interface ITopLevelImplWithNativeMenuExporter : ITopLevelImpl

3
src/Avalonia.Controls/Platform/ITrayIconImpl.cs

@ -1,5 +1,6 @@
using System;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
#nullable enable
@ -21,5 +22,7 @@ namespace Avalonia.Platform
/// Sets if the tray icon is visible or not.
/// </summary>
void SetIsVisible (bool visible);
INativeMenuExporter? MenuExporter { get; }
}
}

4
src/Avalonia.Controls/TrayIcon.cs

@ -12,7 +12,7 @@ namespace Avalonia.Controls
{
}
public class TrayIcon : AvaloniaObject, IDataContextProvider
public class TrayIcon : AvaloniaObject, IDataContextProvider, INativeMenuExporterProvider
{
private readonly ITrayIconImpl _impl;
@ -141,6 +141,8 @@ namespace Avalonia.Controls
set => SetValue(IsVisibleProperty, value);
}
public INativeMenuExporter? NativeMenuExporter => _impl.MenuExporter;
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
{
base.OnPropertyChanged(change);

9
src/Avalonia.DesignerSupport/Remote/TrayIconStub.cs

@ -1,4 +1,6 @@
using System;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Platform;
namespace Avalonia.DesignerSupport.Remote
@ -9,6 +11,8 @@ namespace Avalonia.DesignerSupport.Remote
public Action DoubleClicked { get; set; }
public Action RightClicked { get; set; }
public INativeMenuExporter MenuExporter => throw new NotImplementedException();
public void SetIcon(IWindowIconImpl icon)
{
}
@ -17,6 +21,11 @@ namespace Avalonia.DesignerSupport.Remote
{
}
public void SetMenu(NativeMenu menu)
{
throw new NotImplementedException();
}
public void SetToolTipText(string text)
{
}

17
src/Windows/Avalonia.Win32/TrayIconImpl.cs

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Controls.Primitives.PopupPositioning;
using Avalonia.LogicalTree;
using Avalonia.Platform;
@ -21,9 +22,12 @@ namespace Avalonia.Win32
private bool _iconAdded;
private IconImpl? _icon;
private string? _tooltipText;
private readonly Win32NativeToManagedMenuExporter _exporter;
private static Dictionary<int, TrayIconImpl> s_trayIcons = new Dictionary<int, TrayIconImpl>();
public INativeMenuExporter MenuExporter => _exporter;
internal static void ProcWnd(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{
if (msg == (int)CustomWindowsMessage.WM_TRAYMOUSE && s_trayIcons.ContainsKey(wParam.ToInt32()))
@ -34,6 +38,8 @@ namespace Avalonia.Win32
public TrayIconImpl()
{
_exporter = new Win32NativeToManagedMenuExporter();
_uniqueId = ++_nextUniqueId;
s_trayIcons.Add(_uniqueId, this);
@ -125,7 +131,7 @@ namespace Avalonia.Win32
}
}
private static void OnRightClicked()
private void OnRightClicked()
{
var _trayMenu = new TrayPopupRoot()
{
@ -135,14 +141,7 @@ namespace Avalonia.Win32
TransparencyLevelHint = WindowTransparencyLevel.Transparent,
Content = new TrayIconMenuFlyoutPresenter()
{
Items = new List<MenuItem>
{
new MenuItem { Header = "Item 1"},
new MenuItem { Header = "Item 2"},
new MenuItem { Header = "Item 3"},
new MenuItem { Header = "Item 4"},
new MenuItem { Header = "Item 5"}
}
Items = _exporter.GetMenu()
}
};

58
src/Windows/Avalonia.Win32/Win32NativeToManagedMenuExporter.cs

@ -0,0 +1,58 @@
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
#nullable enable
namespace Avalonia.Win32
{
internal class Win32NativeToManagedMenuExporter : INativeMenuExporter
{
private NativeMenu? _nativeMenu;
public void SetNativeMenu(NativeMenu nativeMenu)
{
_nativeMenu = nativeMenu;
}
private IEnumerable<MenuItem>? Populate (NativeMenu nativeMenu)
{
var items = new List<MenuItem>();
foreach (var menuItem in nativeMenu.Items)
{
if (menuItem is NativeMenuItemSeparator separator)
{
items.Add(new MenuItem { Header = "-" });
}
else if (menuItem is NativeMenuItem item)
{
var newItem = new MenuItem { Header = item.Header, Icon = item.Icon, Command = item.Command, CommandParameter = item.CommandParameter };
if(item.Menu != null)
{
newItem.ContextMenu = new ContextMenu() { Items = Populate(item.Menu) };
}
else if (item.HasClickHandlers && item is INativeMenuItemExporterEventsImplBridge bridge)
{
newItem.Click += (_,_) => bridge.RaiseClicked();
}
items.Add(newItem);
}
}
return items;
}
public IEnumerable<MenuItem>? GetMenu ()
{
if(_nativeMenu != null)
{
return Populate(_nativeMenu);
}
return null;
}
}
}
Loading…
Cancel
Save