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.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 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. 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. 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' 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. 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. 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 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. 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 public static readonly AttachedProperty<NativeMenu> MenuProperty
= AvaloniaProperty.RegisterAttached<NativeMenu, AvaloniaObject, NativeMenu>("Menu"/*, validate: = AvaloniaProperty.RegisterAttached<NativeMenu, AvaloniaObject, NativeMenu>("Menu");
(o, v) =>
{
if(!(o is Application || o is TopLevel))
throw new InvalidOperationException("NativeMenu.Menu property isn't valid on "+o.GetType());
return v;
}*/);
public static void SetMenu(AvaloniaObject o, NativeMenu menu) => o.SetValue(MenuProperty, menu); public static void SetMenu(AvaloniaObject o, NativeMenu menu) => o.SetValue(MenuProperty, menu);
public static NativeMenu GetMenu(AvaloniaObject o) => o.GetValue(MenuProperty); public static NativeMenu GetMenu(AvaloniaObject o) => o.GetValue(MenuProperty);
static NativeMenu() static NativeMenu()
@ -79,6 +74,10 @@ namespace Avalonia.Controls
{ {
GetInfo(tl).Exporter?.SetNativeMenu(args.NewValue.GetValueOrDefault()); 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;
using System.Collections.Generic;
using Avalonia.Platform; using Avalonia.Platform;
#nullable enable
namespace Avalonia.Controls.Platform namespace Avalonia.Controls.Platform
{ {
public interface ITopLevelNativeMenuExporter public interface INativeMenuExporter
{
void SetNativeMenu(NativeMenu menu);
}
public interface ITopLevelNativeMenuExporter : INativeMenuExporter
{ {
bool IsNativeMenuExported { get; } bool IsNativeMenuExported { get; }
event EventHandler OnIsNativeMenuExportedChanged; event EventHandler OnIsNativeMenuExportedChanged;
void SetNativeMenu(NativeMenu menu); }
public interface INativeMenuExporterProvider
{
INativeMenuExporter? NativeMenuExporter { get; }
} }
public interface ITopLevelImplWithNativeMenuExporter : ITopLevelImpl public interface ITopLevelImplWithNativeMenuExporter : ITopLevelImpl

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

@ -1,5 +1,6 @@
using System; using System;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Platform;
#nullable enable #nullable enable
@ -21,5 +22,7 @@ namespace Avalonia.Platform
/// Sets if the tray icon is visible or not. /// Sets if the tray icon is visible or not.
/// </summary> /// </summary>
void SetIsVisible (bool visible); 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; private readonly ITrayIconImpl _impl;
@ -141,6 +141,8 @@ namespace Avalonia.Controls
set => SetValue(IsVisibleProperty, value); set => SetValue(IsVisibleProperty, value);
} }
public INativeMenuExporter? NativeMenuExporter => _impl.MenuExporter;
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change) protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
{ {
base.OnPropertyChanged(change); base.OnPropertyChanged(change);

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

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

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

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

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