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"
IconSource="/Assets/Icon.ico"
LightIconSource="/Assets/Icon-Light.ico">
<controls:NotificationFlyout.ContextMenuItems>
<MenuFlyoutItem Text="Close" />
</controls:NotificationFlyout.ContextMenuItems>
<StackPanel Margin="24">
<ComboBox
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;
namespace NotificationFlyout.Uwp.UI.Controls
{
internal class ContextMenuFlyoutHost : Control
{
private MenuFlyout _flyout;
private Grid _root;
public ContextMenuFlyoutHost()
@@ -12,23 +14,42 @@ namespace NotificationFlyout.Uwp.UI.Controls
DefaultStyleKey = typeof(ContextMenuFlyoutHost);
}
public void HideFlyout()
{
if (_flyout == null) return;
_flyout.Hide();
}
public void ShowFlyout()
{
if (_root == null) return;
var flyout = FlyoutBase.GetAttachedFlyout(_root);
flyout.ShowAt(_root, new FlyoutShowOptions { Placement = FlyoutPlacementMode.BottomEdgeAlignedLeft, ShowMode = FlyoutShowMode.TransientWithDismissOnPointerMoveAway });
}
if (_flyout == null) return;
public void HideFlyout()
{
if (_root == null) return;
var flyout = FlyoutBase.GetAttachedFlyout(_root);
flyout.Hide();
_flyout.ShowAt(_root, new FlyoutShowOptions { Placement = FlyoutPlacementMode.BottomEdgeAlignedLeft, ShowMode = FlyoutShowMode.TransientWithDismissOnPointerMoveAway });
}
protected override void OnApplyTemplate()
{
_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">
<Grid x:Name="Root">
<FlyoutBase.AttachedFlyout>
<MenuFlyout>
<MenuFlyoutItem Text="Test" />
</MenuFlyout>
<MenuFlyout x:Name="Flyout" />
</FlyoutBase.AttachedFlyout>
</Grid>
</ControlTemplate>
@@ -1,9 +1,10 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Markup;
using Windows.UI.Xaml.Media;
@@ -37,15 +38,17 @@ namespace NotificationFlyout.Uwp.UI.Controls
typeof(IList<MenuFlyoutItemBase>), typeof(NotificationFlyout),
new PropertyMetadata(null));
internal event EventHandler ContentChanged;
internal event EventHandler IconSourceChanged;
internal event EventHandler RequestedThemeChanged;
public NotificationFlyout()
{
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
{
get => (UIElement)GetValue(ContentProperty);
@@ -99,6 +102,14 @@ namespace NotificationFlyout.Uwp.UI.Controls
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()
{
IconSourceChanged?.Invoke(this, EventArgs.Empty);
@@ -109,4 +120,17 @@ namespace NotificationFlyout.Uwp.UI.Controls
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;
}
if (string.IsNullOrEmpty(placement)) return;
VisualStateManager.GoToState(this, placement, true);
}
@@ -1,4 +1,5 @@
using System.Windows;
using System;
using System.Windows;
using System.Windows.Markup;
namespace NotificationFlyout.Wpf.UI.Controls
@@ -11,12 +12,16 @@ namespace NotificationFlyout.Wpf.UI.Controls
typeof(Uwp.UI.Controls.NotificationFlyout), typeof(NotificationFlyoutApplication),
new PropertyMetadata(null, OnFlyoutPropertyChanged));
private readonly NotificationFlyoutXamlHostWindow _xamlHost;
private readonly ContextMenuXamlHost _contextMenuXamlHost;
private readonly NotificationFlyoutXamlHost _notificationFlyoutXamlHost;
public NotificationFlyoutApplication()
{
_xamlHost = new NotificationFlyoutXamlHostWindow();
_xamlHost.Show();
_notificationFlyoutXamlHost = new NotificationFlyoutXamlHost();
_notificationFlyoutXamlHost.ContextMenuRequested += OnContextMenuRequested;
_notificationFlyoutXamlHost.Show();
_contextMenuXamlHost = new ContextMenuXamlHost();
_contextMenuXamlHost.Show();
}
public Uwp.UI.Controls.NotificationFlyout Flyout
@@ -27,12 +32,12 @@ namespace NotificationFlyout.Wpf.UI.Controls
public void HideFlyout()
{
_xamlHost.HideFlyout();
_notificationFlyoutXamlHost.HideFlyout();
}
public void ShowFlyout()
{
_xamlHost.ShowFlyout();
_notificationFlyoutXamlHost.ShowFlyout();
}
private static void OnFlyoutPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
@@ -41,9 +46,15 @@ namespace NotificationFlyout.Wpf.UI.Controls
sender?.OnFlyoutPropertyChanged();
}
private void OnContextMenuRequested(object sender, EventArgs args)
{
_contextMenuXamlHost?.ShowContextMenuFlyout();
}
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.Helpers;
using System;
using System.Collections.Generic;
using Windows.UI.Xaml.Controls;
namespace NotificationFlyout.Wpf.UI.Controls
{
internal class ContextMenuXamlHost : XamlHostWindow<ContextMenuFlyoutHost>
{
private Uwp.UI.Controls.NotificationFlyout _flyout;
public ContextMenuXamlHost()
{
Topmost = true;
}
protected override void OnDeactivated(EventArgs args)
public void SetFlyout(Uwp.UI.Controls.NotificationFlyout flyout)
{
var flyoutHost = GetHostContent();
if (flyoutHost != null)
if (_flyout != null)
{
flyoutHost.HideFlyout();
_flyout.MenuItemsChanged -= OnContextMenuItemsChanged;
}
_flyout = flyout;
UpdateMenuItems();
}
public void ShowContextMenuFlyout()
@@ -34,5 +40,39 @@ namespace NotificationFlyout.Wpf.UI.Controls
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
{
internal class NotificationFlyoutXamlHostWindow : XamlHostWindow<NotificationFlyoutHost>
internal class NotificationFlyoutXamlHost : XamlHostWindow<NotificationFlyoutHost>
{
private const string ShellTrayHandleName = "Shell_TrayWnd";
private ContextMenuXamlHost _contextMenuXamlHost;
private Uwp.UI.Controls.NotificationFlyout _flyout;
private bool _isLoaded;
private NotificationIconHelper _notificationIconHelper;
private SystemPersonalisationHelper _systemPersonalisationHelper;
private TaskbarHelper _taskbarHelper;
public NotificationFlyoutXamlHostWindow()
{
Loaded += OnLoaded;
}
internal event EventHandler ContextMenuRequested;
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)
{
HideFlyout();
@@ -104,20 +105,10 @@ namespace NotificationFlyout.Wpf.UI.Controls
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)
{
UpdateWindow();
@@ -130,14 +121,13 @@ namespace NotificationFlyout.Wpf.UI.Controls
private void PrepareNotificationIcon()
{
_contextMenuXamlHost = new ContextMenuXamlHost();
_contextMenuXamlHost.Show();
_notificationIconHelper = NotificationIconHelper.Create(this);
_notificationIconHelper.IconInvoked += OnIconInvoked;
_systemPersonalisationHelper = SystemPersonalisationHelper.Create(this);
_systemPersonalisationHelper.ThemeChanged += OnThemeChanged;
UpdateIcons();
}
private void PrepareTaskbar()
@@ -145,10 +135,6 @@ namespace NotificationFlyout.Wpf.UI.Controls
_taskbarHelper = TaskbarHelper.Create(this);
_taskbarHelper.TaskbarChanged += OnTaskbarChanged;
}
private void ShowContextMenuFlyout()
{
_contextMenuXamlHost.ShowContextMenuFlyout();
}
private void UpdateFlyoutContent()
{
@@ -166,7 +152,7 @@ namespace NotificationFlyout.Wpf.UI.Controls
private async void UpdateIcons()
{
if (!_isLoaded) return;
if (!IsLoaded) return;
if (_flyout == null) return;
@@ -185,6 +171,7 @@ namespace NotificationFlyout.Wpf.UI.Controls
_notificationIconHelper.SetIcon(icon.Handle);
}
private void UpdateRequestedTheme()
{
if (_flyout == null) return;
@@ -200,7 +187,7 @@ namespace NotificationFlyout.Wpf.UI.Controls
private void UpdateWindow()
{
if (!_isLoaded) return;
if (!IsLoaded) return;
var flyoutHost = GetHostContent();
if (flyoutHost == null) return;
@@ -1,33 +1,44 @@
using Microsoft.Toolkit.Wpf.UI.XamlHost;
using NotificationFlyout.Wpf.UI.Extensions;
using System;
using System.Windows;
using System.Windows.Media;
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;
protected new bool IsLoaded;
private WindowsXamlHost _xamlHost;
public XamlHostWindow()
{
PrepareDefaultWindow();
PrepareWindowsXamlHost();
Loaded += OnLoaded;
ContentRendered += OnContentRendered;
}
internal TXamlContent GetHostContent()
{
if (_xamlHost == null) return null;
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)
{
this.Hidden();
this.Hidden();
}
private void PrepareDefaultWindow()
@@ -37,7 +48,7 @@ namespace NotificationFlyout.Wpf.UI.Controls
WindowStyle = WindowStyle.None;
ResizeMode = ResizeMode.NoResize;
AllowsTransparency = true;
Background = new SolidColorBrush(Colors.Red);
Background = new SolidColorBrush(Colors.Transparent);
Height = WindowSize;
Width = WindowSize;
}
@@ -46,14 +57,13 @@ namespace NotificationFlyout.Wpf.UI.Controls
{
_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;
}
}