Added ContextMenu support

This commit is contained in:
Daniel Clark
2021-02-11 00:56:52 +00:00
parent 7b2de3ddbc
commit c01af45f7f
9 changed files with 163 additions and 68 deletions
@@ -6,6 +6,9 @@
xmlns:muxc="using:Microsoft.UI.Xaml.Controls" xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
IconSource="/Assets/Icon.ico" IconSource="/Assets/Icon.ico"
LightIconSource="/Assets/Icon-Light.ico"> LightIconSource="/Assets/Icon-Light.ico">
<controls:NotificationFlyout.ContextMenuItems>
<MenuFlyoutItem Text="Close" />
</controls:NotificationFlyout.ContextMenuItems>
<StackPanel Margin="24"> <StackPanel Margin="24">
<ComboBox <ComboBox
x:Name="Theme" x:Name="Theme"
@@ -1,10 +1,12 @@
using Windows.UI.Xaml.Controls; using System.Collections.Generic;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives; using Windows.UI.Xaml.Controls.Primitives;
namespace NotificationFlyout.Uwp.UI.Controls namespace NotificationFlyout.Uwp.UI.Controls
{ {
internal class ContextMenuFlyoutHost : Control internal class ContextMenuFlyoutHost : Control
{ {
private MenuFlyout _flyout;
private Grid _root; private Grid _root;
public ContextMenuFlyoutHost() public ContextMenuFlyoutHost()
@@ -12,23 +14,42 @@ namespace NotificationFlyout.Uwp.UI.Controls
DefaultStyleKey = typeof(ContextMenuFlyoutHost); DefaultStyleKey = typeof(ContextMenuFlyoutHost);
} }
public void HideFlyout()
{
if (_flyout == null) return;
_flyout.Hide();
}
public void ShowFlyout() public void ShowFlyout()
{ {
if (_root == null) return; if (_root == null) return;
var flyout = FlyoutBase.GetAttachedFlyout(_root); if (_flyout == null) return;
flyout.ShowAt(_root, new FlyoutShowOptions { Placement = FlyoutPlacementMode.BottomEdgeAlignedLeft, ShowMode = FlyoutShowMode.TransientWithDismissOnPointerMoveAway });
}
public void HideFlyout() _flyout.ShowAt(_root, new FlyoutShowOptions { Placement = FlyoutPlacementMode.BottomEdgeAlignedLeft, ShowMode = FlyoutShowMode.TransientWithDismissOnPointerMoveAway });
{
if (_root == null) return;
var flyout = FlyoutBase.GetAttachedFlyout(_root);
flyout.Hide();
} }
protected override void OnApplyTemplate() protected override void OnApplyTemplate()
{ {
_root = GetTemplateChild("Root") as Grid; _root = GetTemplateChild("Root") as Grid;
_flyout = GetTemplateChild("Flyout") as MenuFlyout;
}
internal void SetMenuItems(IList<MenuFlyoutItemBase> addedItems, IList<MenuFlyoutItemBase> removedItems = null)
{
if (_flyout == null) return;
if (removedItems != null)
{
foreach (var item in removedItems)
{
_flyout.Items.Remove(item);
}
}
foreach (var item in addedItems)
{
_flyout.Items.Add(item);
}
} }
} }
} }
@@ -8,9 +8,7 @@
<ControlTemplate TargetType="controls:ContextMenuFlyoutHost"> <ControlTemplate TargetType="controls:ContextMenuFlyoutHost">
<Grid x:Name="Root"> <Grid x:Name="Root">
<FlyoutBase.AttachedFlyout> <FlyoutBase.AttachedFlyout>
<MenuFlyout> <MenuFlyout x:Name="Flyout" />
<MenuFlyoutItem Text="Test" />
</MenuFlyout>
</FlyoutBase.AttachedFlyout> </FlyoutBase.AttachedFlyout>
</Grid> </Grid>
</ControlTemplate> </ControlTemplate>
@@ -1,9 +1,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using Windows.UI.Xaml; using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Markup; using Windows.UI.Xaml.Markup;
using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Media;
@@ -37,15 +38,17 @@ namespace NotificationFlyout.Uwp.UI.Controls
typeof(IList<MenuFlyoutItemBase>), typeof(NotificationFlyout), typeof(IList<MenuFlyoutItemBase>), typeof(NotificationFlyout),
new PropertyMetadata(null)); new PropertyMetadata(null));
internal event EventHandler ContentChanged;
internal event EventHandler IconSourceChanged;
internal event EventHandler RequestedThemeChanged;
public NotificationFlyout() public NotificationFlyout()
{ {
ContextMenuItems = new ObservableCollection<MenuFlyoutItemBase>(); ContextMenuItems = new ObservableCollection<MenuFlyoutItemBase>();
(ContextMenuItems as INotifyCollectionChanged).CollectionChanged += OnContextMenuItemsChanged;
} }
internal event EventHandler ContentChanged;
internal event EventHandler IconSourceChanged;
internal event EventHandler<NotificationFlyoutMenuItemsChangedEventArgs> MenuItemsChanged;
internal event EventHandler RequestedThemeChanged;
public UIElement Content public UIElement Content
{ {
get => (UIElement)GetValue(ContentProperty); get => (UIElement)GetValue(ContentProperty);
@@ -99,6 +102,14 @@ namespace NotificationFlyout.Uwp.UI.Controls
ContentChanged?.Invoke(this, EventArgs.Empty); ContentChanged?.Invoke(this, EventArgs.Empty);
} }
private void OnContextMenuItemsChanged(object sender, NotifyCollectionChangedEventArgs args)
{
var addedItems = args.NewItems.Cast<MenuFlyoutItemBase>().ToList();
var removedItems = args.NewItems.Cast<MenuFlyoutItemBase>().ToList();
MenuItemsChanged?.Invoke(this, new NotificationFlyoutMenuItemsChangedEventArgs(addedItems, removedItems));
}
private void OnIconPropertyChanged() private void OnIconPropertyChanged()
{ {
IconSourceChanged?.Invoke(this, EventArgs.Empty); IconSourceChanged?.Invoke(this, EventArgs.Empty);
@@ -109,4 +120,17 @@ namespace NotificationFlyout.Uwp.UI.Controls
RequestedThemeChanged?.Invoke(this, EventArgs.Empty); RequestedThemeChanged?.Invoke(this, EventArgs.Empty);
} }
} }
internal class NotificationFlyoutMenuItemsChangedEventArgs : EventArgs
{
public NotificationFlyoutMenuItemsChangedEventArgs(IList<MenuFlyoutItemBase> addedItems, IList<MenuFlyoutItemBase> removedItems)
{
AddedItems = addedItems;
RemovedItems = removedItems;
}
public IList<MenuFlyoutItemBase> AddedItems { get; private set; }
public IList<MenuFlyoutItemBase> RemovedItems { get; private set; }
}
} }
@@ -42,6 +42,7 @@ namespace NotificationFlyout.Uwp.UI.Controls
_placement = placement; _placement = placement;
} }
if (string.IsNullOrEmpty(placement)) return;
VisualStateManager.GoToState(this, placement, true); VisualStateManager.GoToState(this, placement, true);
} }
@@ -1,4 +1,5 @@
using System.Windows; using System;
using System.Windows;
using System.Windows.Markup; using System.Windows.Markup;
namespace NotificationFlyout.Wpf.UI.Controls namespace NotificationFlyout.Wpf.UI.Controls
@@ -11,12 +12,16 @@ namespace NotificationFlyout.Wpf.UI.Controls
typeof(Uwp.UI.Controls.NotificationFlyout), typeof(NotificationFlyoutApplication), typeof(Uwp.UI.Controls.NotificationFlyout), typeof(NotificationFlyoutApplication),
new PropertyMetadata(null, OnFlyoutPropertyChanged)); new PropertyMetadata(null, OnFlyoutPropertyChanged));
private readonly NotificationFlyoutXamlHostWindow _xamlHost; private readonly ContextMenuXamlHost _contextMenuXamlHost;
private readonly NotificationFlyoutXamlHost _notificationFlyoutXamlHost;
public NotificationFlyoutApplication() public NotificationFlyoutApplication()
{ {
_xamlHost = new NotificationFlyoutXamlHostWindow(); _notificationFlyoutXamlHost = new NotificationFlyoutXamlHost();
_xamlHost.Show(); _notificationFlyoutXamlHost.ContextMenuRequested += OnContextMenuRequested;
_notificationFlyoutXamlHost.Show();
_contextMenuXamlHost = new ContextMenuXamlHost();
_contextMenuXamlHost.Show();
} }
public Uwp.UI.Controls.NotificationFlyout Flyout public Uwp.UI.Controls.NotificationFlyout Flyout
@@ -27,12 +32,12 @@ namespace NotificationFlyout.Wpf.UI.Controls
public void HideFlyout() public void HideFlyout()
{ {
_xamlHost.HideFlyout(); _notificationFlyoutXamlHost.HideFlyout();
} }
public void ShowFlyout() public void ShowFlyout()
{ {
_xamlHost.ShowFlyout(); _notificationFlyoutXamlHost.ShowFlyout();
} }
private static void OnFlyoutPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs) private static void OnFlyoutPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
@@ -41,9 +46,15 @@ namespace NotificationFlyout.Wpf.UI.Controls
sender?.OnFlyoutPropertyChanged(); sender?.OnFlyoutPropertyChanged();
} }
private void OnContextMenuRequested(object sender, EventArgs args)
{
_contextMenuXamlHost?.ShowContextMenuFlyout();
}
private void OnFlyoutPropertyChanged() private void OnFlyoutPropertyChanged()
{ {
_xamlHost.SetFlyout(Flyout); _notificationFlyoutXamlHost.SetFlyout(Flyout);
_contextMenuXamlHost.SetFlyout(Flyout);
} }
} }
} }
@@ -2,23 +2,29 @@
using NotificationFlyout.Wpf.UI.Extensions; using NotificationFlyout.Wpf.UI.Extensions;
using NotificationFlyout.Wpf.UI.Helpers; using NotificationFlyout.Wpf.UI.Helpers;
using System; using System;
using System.Collections.Generic;
using Windows.UI.Xaml.Controls;
namespace NotificationFlyout.Wpf.UI.Controls namespace NotificationFlyout.Wpf.UI.Controls
{ {
internal class ContextMenuXamlHost : XamlHostWindow<ContextMenuFlyoutHost> internal class ContextMenuXamlHost : XamlHostWindow<ContextMenuFlyoutHost>
{ {
private Uwp.UI.Controls.NotificationFlyout _flyout;
public ContextMenuXamlHost() public ContextMenuXamlHost()
{ {
Topmost = true; Topmost = true;
} }
protected override void OnDeactivated(EventArgs args) public void SetFlyout(Uwp.UI.Controls.NotificationFlyout flyout)
{ {
var flyoutHost = GetHostContent(); if (_flyout != null)
if (flyoutHost != null)
{ {
flyoutHost.HideFlyout(); _flyout.MenuItemsChanged -= OnContextMenuItemsChanged;
} }
_flyout = flyout;
UpdateMenuItems();
} }
public void ShowContextMenuFlyout() public void ShowContextMenuFlyout()
@@ -34,5 +40,39 @@ namespace NotificationFlyout.Wpf.UI.Controls
Activate(); Activate();
} }
protected override void OnContentLoaded()
{
UpdateMenuItems();
}
protected override void OnDeactivated(EventArgs args)
{
var flyoutHost = GetHostContent();
if (flyoutHost != null)
{
flyoutHost.HideFlyout();
}
}
private void OnContextMenuItemsChanged(object sender, NotificationFlyoutMenuItemsChangedEventArgs args)
{
UpdateMenuItems(args.AddedItems, args.RemovedItems);
}
private void UpdateMenuItems()
{
if (_flyout == null) return;
UpdateMenuItems(_flyout.ContextMenuItems);
}
private void UpdateMenuItems(IList<MenuFlyoutItemBase> addedItems, IList<MenuFlyoutItemBase> removedItems = default)
{
var flyoutHost = GetHostContent();
if (flyoutHost != null)
{
flyoutHost.SetMenuItems(addedItems, removedItems);
}
}
} }
} }
@@ -9,23 +9,17 @@ using System.Windows.Input;
namespace NotificationFlyout.Wpf.UI.Controls namespace NotificationFlyout.Wpf.UI.Controls
{ {
internal class NotificationFlyoutXamlHostWindow : XamlHostWindow<NotificationFlyoutHost> internal class NotificationFlyoutXamlHost : XamlHostWindow<NotificationFlyoutHost>
{ {
private const string ShellTrayHandleName = "Shell_TrayWnd"; private const string ShellTrayHandleName = "Shell_TrayWnd";
private ContextMenuXamlHost _contextMenuXamlHost;
private Uwp.UI.Controls.NotificationFlyout _flyout; private Uwp.UI.Controls.NotificationFlyout _flyout;
private bool _isLoaded;
private NotificationIconHelper _notificationIconHelper; private NotificationIconHelper _notificationIconHelper;
private SystemPersonalisationHelper _systemPersonalisationHelper; private SystemPersonalisationHelper _systemPersonalisationHelper;
private TaskbarHelper _taskbarHelper; private TaskbarHelper _taskbarHelper;
public NotificationFlyoutXamlHostWindow() internal event EventHandler ContextMenuRequested;
{
Loaded += OnLoaded;
}
public void SetFlyout(Uwp.UI.Controls.NotificationFlyout flyout) public void SetFlyout(Uwp.UI.Controls.NotificationFlyout flyout)
{ {
@@ -75,6 +69,13 @@ namespace NotificationFlyout.Wpf.UI.Controls
} }
} }
protected override void OnContentLoaded()
{
PrepareNotificationIcon();
PrepareTaskbar();
UpdateWindow();
}
protected override void OnDeactivated(EventArgs args) protected override void OnDeactivated(EventArgs args)
{ {
HideFlyout(); HideFlyout();
@@ -104,20 +105,10 @@ namespace NotificationFlyout.Wpf.UI.Controls
if (args.MouseButton == MouseButton.Right) if (args.MouseButton == MouseButton.Right)
{ {
ShowContextMenuFlyout(); ContextMenuRequested?.Invoke(this, EventArgs.Empty);
} }
} }
private void OnLoaded(object sender, RoutedEventArgs args)
{
PrepareNotificationIcon();
PrepareTaskbar();
_isLoaded = true;
UpdateWindow();
}
private void OnTaskbarChanged(object sender, EventArgs args) private void OnTaskbarChanged(object sender, EventArgs args)
{ {
UpdateWindow(); UpdateWindow();
@@ -130,14 +121,13 @@ namespace NotificationFlyout.Wpf.UI.Controls
private void PrepareNotificationIcon() private void PrepareNotificationIcon()
{ {
_contextMenuXamlHost = new ContextMenuXamlHost();
_contextMenuXamlHost.Show();
_notificationIconHelper = NotificationIconHelper.Create(this); _notificationIconHelper = NotificationIconHelper.Create(this);
_notificationIconHelper.IconInvoked += OnIconInvoked; _notificationIconHelper.IconInvoked += OnIconInvoked;
_systemPersonalisationHelper = SystemPersonalisationHelper.Create(this); _systemPersonalisationHelper = SystemPersonalisationHelper.Create(this);
_systemPersonalisationHelper.ThemeChanged += OnThemeChanged; _systemPersonalisationHelper.ThemeChanged += OnThemeChanged;
UpdateIcons();
} }
private void PrepareTaskbar() private void PrepareTaskbar()
@@ -145,10 +135,6 @@ namespace NotificationFlyout.Wpf.UI.Controls
_taskbarHelper = TaskbarHelper.Create(this); _taskbarHelper = TaskbarHelper.Create(this);
_taskbarHelper.TaskbarChanged += OnTaskbarChanged; _taskbarHelper.TaskbarChanged += OnTaskbarChanged;
} }
private void ShowContextMenuFlyout()
{
_contextMenuXamlHost.ShowContextMenuFlyout();
}
private void UpdateFlyoutContent() private void UpdateFlyoutContent()
{ {
@@ -166,7 +152,7 @@ namespace NotificationFlyout.Wpf.UI.Controls
private async void UpdateIcons() private async void UpdateIcons()
{ {
if (!_isLoaded) return; if (!IsLoaded) return;
if (_flyout == null) return; if (_flyout == null) return;
@@ -185,6 +171,7 @@ namespace NotificationFlyout.Wpf.UI.Controls
_notificationIconHelper.SetIcon(icon.Handle); _notificationIconHelper.SetIcon(icon.Handle);
} }
private void UpdateRequestedTheme() private void UpdateRequestedTheme()
{ {
if (_flyout == null) return; if (_flyout == null) return;
@@ -200,7 +187,7 @@ namespace NotificationFlyout.Wpf.UI.Controls
private void UpdateWindow() private void UpdateWindow()
{ {
if (!_isLoaded) return; if (!IsLoaded) return;
var flyoutHost = GetHostContent(); var flyoutHost = GetHostContent();
if (flyoutHost == null) return; if (flyoutHost == null) return;
@@ -1,33 +1,44 @@
using Microsoft.Toolkit.Wpf.UI.XamlHost; using Microsoft.Toolkit.Wpf.UI.XamlHost;
using NotificationFlyout.Wpf.UI.Extensions; using NotificationFlyout.Wpf.UI.Extensions;
using System;
using System.Windows; using System.Windows;
using System.Windows.Media; using System.Windows.Media;
namespace NotificationFlyout.Wpf.UI.Controls namespace NotificationFlyout.Wpf.UI.Controls
{ {
internal class XamlHostWindow<TXamlContent> : Window where TXamlContent : class internal class XamlHostWindow<TXamlContent> : Window where TXamlContent : Windows.UI.Xaml.UIElement
{ {
internal const double WindowSize = 5; internal const double WindowSize = 5;
protected new bool IsLoaded;
private WindowsXamlHost _xamlHost; private WindowsXamlHost _xamlHost;
public XamlHostWindow() public XamlHostWindow()
{ {
PrepareDefaultWindow(); PrepareDefaultWindow();
PrepareWindowsXamlHost(); PrepareWindowsXamlHost();
Loaded += OnLoaded; Loaded += OnLoaded;
ContentRendered += OnContentRendered;
} }
internal TXamlContent GetHostContent() internal TXamlContent GetHostContent()
{ {
if (_xamlHost == null) return null; if (_xamlHost == null) return null;
return _xamlHost.GetUwpInternalObject() as TXamlContent; return _xamlHost.GetUwpInternalObject() as TXamlContent;
} }
protected virtual void OnContentLoaded()
{
}
private void OnContentRendered(object sender, EventArgs args)
{
IsLoaded = true;
OnContentLoaded();
}
private void OnLoaded(object sender, RoutedEventArgs args) private void OnLoaded(object sender, RoutedEventArgs args)
{ {
this.Hidden(); this.Hidden();
} }
private void PrepareDefaultWindow() private void PrepareDefaultWindow()
@@ -37,7 +48,7 @@ namespace NotificationFlyout.Wpf.UI.Controls
WindowStyle = WindowStyle.None; WindowStyle = WindowStyle.None;
ResizeMode = ResizeMode.NoResize; ResizeMode = ResizeMode.NoResize;
AllowsTransparency = true; AllowsTransparency = true;
Background = new SolidColorBrush(Colors.Red); Background = new SolidColorBrush(Colors.Transparent);
Height = WindowSize; Height = WindowSize;
Width = WindowSize; Width = WindowSize;
} }
@@ -46,14 +57,13 @@ namespace NotificationFlyout.Wpf.UI.Controls
{ {
_xamlHost = new WindowsXamlHost _xamlHost = new WindowsXamlHost
{ {
InitialTypeName = typeof(TXamlContent).FullName InitialTypeName = typeof(TXamlContent).FullName,
Height = 0,
Width = 0,
HorizontalAlignment = HorizontalAlignment.Stretch,
VerticalAlignment = VerticalAlignment.Stretch
}; };
_xamlHost.Height = 0;
_xamlHost.Width = 0;
_xamlHost.HorizontalAlignment = HorizontalAlignment.Stretch;
_xamlHost.VerticalAlignment = VerticalAlignment.Stretch;
Content = _xamlHost; Content = _xamlHost;
} }
} }