Major refactor to use Popup replacing the Flyout. This will allow for future enhancements.

This commit is contained in:
Daniel Clark
2021-02-26 20:48:10 +00:00
parent c408eafdde
commit 737dbe9b32
9 changed files with 100 additions and 292 deletions
@@ -3,7 +3,12 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:TheXamlGuy.NotificationFlyout.Uwp.UI.Controls"
Placement="Right">
CornerRadius="6">
<controls:NotificationFlyout.ContextFlyout>
<MenuFlyout>
<MenuFlyoutItem Text="Close" Click="OnCloseMenuFlyoutItemClick" />
</MenuFlyout>
</controls:NotificationFlyout.ContextFlyout>
<Grid Width="500" Height="500">
<Button
HorizontalAlignment="Right"
@@ -1,7 +1,7 @@
using System;
using System.Numerics;
using TheXamlGuy.NotificationFlyout.Shared.UI;
using Windows.Foundation;
using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
@@ -21,42 +21,21 @@ namespace TheXamlGuy.NotificationFlyout.Uwp.UI.Controls
typeof(ImageSource), typeof(NotificationFlyout),
new PropertyMetadata(null));
public static DependencyProperty ContextMenuProperty =
DependencyProperty.Register(nameof(ContextMenu),
typeof(NotificationFlyoutContextMenu), typeof(NotificationFlyout),
new PropertyMetadata(null, OnContextMenuPropertyChanged));
public static DependencyProperty PlacementProperty =
DependencyProperty.Register(nameof(Placement),
typeof(NotificationFlyoutContextMenu), typeof(NotificationFlyout),
new PropertyMetadata(NotificationFlyoutPlacement.Auto, OnPlacementPropertyChanged));
private static INotificationFlyoutApplication _applicationInstance;
private UIElement _child;
private Border _container;
private Popup _popup;
public NotificationFlyout() => DefaultStyleKey = typeof(NotificationFlyout);
public event EventHandler<object> Closed;
public event TypedEventHandler<NotificationFlyout, NotificationFlyoutClosingEventArgs> Closing;
public event EventHandler<object> Opened;
public event EventHandler<object> Opening;
internal event EventHandler ContextMenuChanged;
internal event EventHandler IconSourceChanged;
internal event EventHandler PlacementChanged;
public NotificationFlyoutContextMenu ContextMenu
{
get => (NotificationFlyoutContextMenu)GetValue(ContextMenuProperty);
set => SetValue(ContextMenuProperty, value);
}
public ImageSource IconSource
{
get => (ImageSource)GetValue(IconSourceProperty);
@@ -69,24 +48,10 @@ namespace TheXamlGuy.NotificationFlyout.Uwp.UI.Controls
set => SetValue(LightIconSourceProperty, value);
}
public NotificationFlyoutPlacement Placement
{
get => (NotificationFlyoutPlacement)GetValue(PlacementProperty);
set => SetValue(PlacementProperty, value);
}
public static INotificationFlyoutApplication GetApplication() => _applicationInstance;
internal static void SetApplication(INotificationFlyoutApplication application) => _applicationInstance = application;
internal void InvokeClosedEvent(object obj) => Closed?.Invoke(this, obj);
internal void InvokeClosingEvent(NotificationFlyoutClosingEventArgs eventArgs) => Closing?.Invoke(this, eventArgs);
internal void InvokeOpenedEvent(object obj) => Opened?.Invoke(this, obj);
internal void InvokeOpeningEvent(object obj) => Opening?.Invoke(this, obj);
internal void SetPlacement(double horizontalOffset, double verticalOffset, NotificationFlyoutTaskbarPlacement flyoutTaskbarPlacement)
{
if (_popup == null)
@@ -137,19 +102,35 @@ namespace TheXamlGuy.NotificationFlyout.Uwp.UI.Controls
_popup.IsOpen = true;
}
protected override void OnApplyTemplate()
internal void ShowContextMenuAt(double x, double y)
{
if (GetTemplateChild("Container") is Border container)
{
_child = container.Child;
container.Child = null;
}
if (ContextFlyout == null) return;
ContextFlyout.ShouldConstrainToRootBounds = false;
ContextFlyout.XamlRoot = XamlRoot;
ContextFlyout.ShowAt(_container);
ContextFlyout.ShowAt(_container, new FlyoutShowOptions { Position = new Point(x, y), ShowMode = FlyoutShowMode.Standard });
}
private static void OnContextMenuPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
internal void UpdateTheme(bool isColorPrevalence) => VisualStateManager.GoToState(this, isColorPrevalence ? "ColorPrevalenceTheme" : "DefaultTheme", true);
protected override void OnApplyTemplate()
{
var sender = dependencyObject as NotificationFlyout;
sender?.OnContextMenuPropertyChanged();
_container = GetTemplateChild("Container") as Border;
if (_container != null)
{
_child = _container.Child;
_container.Child = null;
}
if (GetTemplateChild("BackgroundElement") is Border backgroundElement)
{
backgroundElement.Shadow = new ThemeShadow();
var currentTranslation = backgroundElement.Translation;
var translation = new Vector3(currentTranslation.X, currentTranslation.Y, 16.0f);
backgroundElement.Translation = translation;
}
}
private static void OnIconPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
@@ -158,18 +139,16 @@ namespace TheXamlGuy.NotificationFlyout.Uwp.UI.Controls
sender?.OnIconPropertyChanged();
}
private static void OnPlacementPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
var sender = dependencyObject as NotificationFlyout;
sender?.OnPlacementPropertyChanged();
}
private void InvokeClosedEvent(object obj) => Closed?.Invoke(this, obj);
private void OnContextMenuPropertyChanged() => ContextMenuChanged?.Invoke(this, EventArgs.Empty);
private void InvokeClosingEvent(NotificationFlyoutClosingEventArgs eventArgs) => Closing?.Invoke(this, eventArgs);
private void InvokeOpenedEvent(object obj) => Opened?.Invoke(this, obj);
private void InvokeOpeningEvent(object obj) => Opening?.Invoke(this, obj);
private void OnIconPropertyChanged() => IconSourceChanged?.Invoke(this, EventArgs.Empty);
private void OnPlacementPropertyChanged() => PlacementChanged?.Invoke(this, EventArgs.Empty);
private void PreparePopup()
{
_popup = new Popup
@@ -36,8 +36,9 @@
<Setter.Value>
<ControlTemplate TargetType="controls:NotificationFlyout">
<Border x:Name="Container">
<Grid>
<Grid x:Name="LayoutRoot">
<Border
x:Name="BackgroundElement"
Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
BackgroundSizing="OuterBorderEdge"
@@ -105,7 +106,7 @@
<VisualState x:Name="DefaultTheme" />
<VisualState x:Name="ColorPrevalenceTheme">
<VisualState.Setters>
<Setter Target="Root.Background" Value="{ThemeResource NotificationFlyoutPresenterBackgroundAccentBrush}" />
<Setter Target="BackgroundElement.Background" Value="{ThemeResource NotificationFlyoutPresenterBackgroundAccentBrush}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
@@ -1,25 +0,0 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Markup;
namespace TheXamlGuy.NotificationFlyout.Uwp.UI.Controls
{
[ContentProperty(Name = "MenuItems")]
public class NotificationFlyoutContextMenu : DependencyObject
{
public static DependencyProperty MenuItemsProperty =
DependencyProperty.Register(nameof(MenuItems),
typeof(IList<MenuFlyoutItemBase>), typeof(NotificationFlyout),
new PropertyMetadata(null));
public NotificationFlyoutContextMenu() => MenuItems = new ObservableCollection<MenuFlyoutItemBase>();
public IList<MenuFlyoutItemBase> MenuItems
{
get => (IList<MenuFlyoutItemBase>)GetValue(MenuItemsProperty);
set => SetValue(MenuItemsProperty, value);
}
}
}
@@ -1,100 +0,0 @@
using System.Collections.Specialized;
using System.Linq;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
namespace TheXamlGuy.NotificationFlyout.Uwp.UI.Controls
{
internal class NotificationFlyoutContextMenuFlyoutHost : Control
{
private NotificationFlyout _flyout;
private MenuFlyout _menuFlyout;
private Grid _root;
public NotificationFlyoutContextMenuFlyoutHost() => DefaultStyleKey = typeof(NotificationFlyoutContextMenuFlyoutHost);
public void HideFlyout()
{
if (_menuFlyout == null) return;
_menuFlyout.Hide();
}
public void ShowFlyout()
{
if (_root == null) return;
if (_menuFlyout == null) return;
_menuFlyout.ShowAt(_root, new FlyoutShowOptions
{
Placement = FlyoutPlacementMode.BottomEdgeAlignedLeft,
ShowMode = FlyoutShowMode.TransientWithDismissOnPointerMoveAway
});
}
internal void SetOwningFlyout(NotificationFlyout flyout)
{
if (_flyout != null)
{
(_flyout.ContextMenu.MenuItems as INotifyCollectionChanged).CollectionChanged -= OnContextMenuItemsChanged;
}
_flyout = flyout;
var contextMenu = _flyout.ContextMenu;
if (contextMenu == null) return;
(contextMenu.MenuItems as INotifyCollectionChanged).CollectionChanged += OnContextMenuItemsChanged;
PrepareMenuItems();
}
protected override void OnApplyTemplate()
{
_root = GetTemplateChild("Root") as Grid;
_menuFlyout = GetTemplateChild("Flyout") as MenuFlyout;
PrepareMenuItems();
}
private void OnContextMenuItemsChanged(object sender, NotifyCollectionChangedEventArgs args)
{
if (_flyout == null) return;
var contextMenu = _flyout.ContextMenu;
if (contextMenu == null) return;
var addedItems = args.NewItems.Cast<MenuFlyoutItemBase>().ToList();
var removedItems = args.NewItems.Cast<MenuFlyoutItemBase>().ToList();
if (removedItems != null)
{
foreach (var item in removedItems)
{
_menuFlyout.Items.Remove(item);
}
}
foreach (var item in addedItems)
{
_menuFlyout.Items.Add(item);
}
}
private void PrepareMenuItems()
{
if (_menuFlyout == null) return;
if (_flyout == null) return;
var contextMenu = _flyout.ContextMenu;
if (contextMenu == null) return;
_menuFlyout.Items.Clear();
var items = contextMenu.MenuItems;
foreach (var item in items)
{
_menuFlyout.Items.Add(item);
}
}
}
}
@@ -1,18 +0,0 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:TheXamlGuy.NotificationFlyout.Uwp.UI.Controls">
<Style TargetType="controls:NotificationFlyoutContextMenuFlyoutHost">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:NotificationFlyoutContextMenuFlyoutHost">
<Grid x:Name="Root">
<FlyoutBase.AttachedFlyout>
<MenuFlyout x:Name="Flyout" />
</FlyoutBase.AttachedFlyout>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
@@ -14,10 +14,6 @@
</PropertyGroup>
<ItemGroup>
<Page Include="NotificationFlyoutPresenter\NotificationFlyoutContextMenuFlyoutHost.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="NotificationFlyout\NotificationFlyout.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
@@ -7,11 +7,13 @@ using TheXamlGuy.NotificationFlyout.Uwp.UI.Controls;
using TheXamlGuy.NotificationFlyout.Wpf.UI.Extensions;
using System.Windows.Media.Imaging;
using TheXamlGuy.NotificationFlyout.Common.Extensions;
using Microsoft.Windows.Sdk;
using TheXamlGuy.NotificationFlyout.Shared.UI;
namespace TheXamlGuy.NotificationFlyout.Wpf.UI.Controls
{
[ContentProperty(nameof(Flyout))]
public class NotificationFlyoutApplication : DependencyObject
public class NotificationFlyoutApplication : DependencyObject, INotificationFlyoutApplication
{
public static DependencyProperty FlyoutProperty =
DependencyProperty.Register(nameof(Flyout),
@@ -20,12 +22,14 @@ namespace TheXamlGuy.NotificationFlyout.Wpf.UI.Controls
private const string ShellTrayHandleName = "Shell_TrayWnd";
private TransparentXamlHost<ContentControl> _notificationFlyoutXamlHost;
private NotificationIconHelper _notificationIconHelper;
private SystemPersonalisationHelper _systemPersonalisationHelper;
private TaskbarHelper _taskbarHelper;
private readonly NotificationIconHelper _notificationIconHelper;
private readonly SystemPersonalisationHelper _systemPersonalisationHelper;
private readonly TaskbarHelper _taskbarHelper;
public NotificationFlyoutApplication()
{
Uwp.UI.Controls.NotificationFlyout.SetApplication(this);
_notificationIconHelper = NotificationIconHelper.Create();
_notificationIconHelper.IconInvoked += OnIconInvoked;
@@ -36,7 +40,6 @@ namespace TheXamlGuy.NotificationFlyout.Wpf.UI.Controls
_systemPersonalisationHelper.ThemeChanged += OnThemeChanged;
PrepareFlyoutHost();
WndProcListener.Current.Start();
}
@@ -46,6 +49,14 @@ namespace TheXamlGuy.NotificationFlyout.Wpf.UI.Controls
set => SetValue(FlyoutProperty, value);
}
public void Exit() => _notificationFlyoutXamlHost.Close();
public void OpenAsWindow<TUIElement>() where TUIElement : Windows.UI.Xaml.UIElement
{
var window = new XamlHost<TUIElement>();
window.Show();
}
private static void OnFlyoutPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
var sender = dependencyObject as NotificationFlyoutApplication;
@@ -54,6 +65,35 @@ namespace TheXamlGuy.NotificationFlyout.Wpf.UI.Controls
private void OnFlyoutPropertyChanged() => PrepareFlyout();
private void OnIconInvoked(object sender, NotificationIconInvokedEventArgs args)
{
if (args.PointerButton == PointerButton.Left)
{
ShowFlyout();
}
if (args.PointerButton == PointerButton.Right)
{
var dpiX = _notificationFlyoutXamlHost.DpiX();
var dpiY = _notificationFlyoutXamlHost.DpiY();
PInvoke.GetPhysicalCursorPos(out POINT point);
Flyout.ShowContextMenuAt(point.x / dpiX, point.y / dpiY);
}
}
private void OnIconSourceChanged(object sender, EventArgs args) => UpdateIcons();
private void OnNotificationFlyoutXamlHostClosed(object sender, EventArgs args) => _notificationIconHelper?.Dispose();
private void OnTaskbarChanged(object sender, EventArgs args) => UpdateFlyoutPlacement();
private void OnThemeChanged(object sender, SystemPersonalisationChangedEventArgs args)
{
UpdateIcons();
UpdateTheme();
}
private void PrepareFlyout()
{
if (Flyout == null) return;
@@ -65,38 +105,8 @@ namespace TheXamlGuy.NotificationFlyout.Wpf.UI.Controls
content.Content = Flyout;
}
SetIcons();
SetFlyoutPlacement();
UpdateIcons();
}
private void OnIconInvoked(object sender, NotificationIconInvokedEventArgs args)
{
if (args.PointerButton == PointerButton.Left)
{
_notificationFlyoutXamlHost.Activate();
Flyout.Show();
}
if (args.PointerButton == PointerButton.Right)
{
}
}
private void OnIconSourceChanged(object sender, EventArgs args) => SetIcons();
private void OnNotificationFlyoutXamlHostClosed(object sender, EventArgs args)
{
_notificationIconHelper?.Dispose();
}
private void OnTaskbarChanged(object sender, EventArgs args) => SetFlyoutPlacement();
private void OnThemeChanged(object sender, SystemPersonalisationChangedEventArgs args)
{
}
private void PrepareFlyoutHost()
{
_notificationFlyoutXamlHost = new TransparentXamlHost<ContentControl>();
@@ -109,7 +119,14 @@ namespace TheXamlGuy.NotificationFlyout.Wpf.UI.Controls
_notificationFlyoutXamlHost.Show();
}
private void SetFlyoutPlacement()
private void ShowFlyout()
{
UpdateFlyoutPlacement();
_notificationFlyoutXamlHost.Activate();
Flyout.Show();
}
private void UpdateFlyoutPlacement()
{
var taskbarState = _taskbarHelper.GetCurrentState();
@@ -156,7 +173,7 @@ namespace TheXamlGuy.NotificationFlyout.Wpf.UI.Controls
Flyout.SetPlacement(left, top, flyoutTaskBarPlacement);
}
private async void SetIcons()
private async void UpdateIcons()
{
if (Flyout == null) return;
@@ -181,11 +198,8 @@ namespace TheXamlGuy.NotificationFlyout.Wpf.UI.Controls
private void UpdateTheme()
{
//var content = GetHostContent();
//if (content != null)
//{
// content.UpdateTheme(_systemPersonalisationHelper.IsColorPrevalence);
//}
if (Flyout == null) return;
Flyout.UpdateTheme(_systemPersonalisationHelper.IsColorPrevalence);
}
}
}
@@ -1,44 +0,0 @@
using TheXamlGuy.NotificationFlyout.Uwp.UI.Controls;
using TheXamlGuy.NotificationFlyout.Common.Helpers;
using TheXamlGuy.NotificationFlyout.Wpf.UI.Extensions;
using System;
namespace TheXamlGuy.NotificationFlyout.Wpf.UI.Controls
{
internal class NotificationFlyoutContextMenuXamlHost : TransparentXamlHost<NotificationFlyoutContextMenuFlyoutHost>
{
public NotificationFlyoutContextMenuXamlHost() => Topmost = true;
internal void SetOwningFlyout(Uwp.UI.Controls.NotificationFlyout flyout)
{
var content = GetHostContent();
if (content != null)
{
content.SetOwningFlyout(flyout);
}
}
internal void ShowContextMenuFlyout()
{
var position = CursorHelper.GetPhysicalCursorPos();
this.SetWindowPosition(position.y, position.x, WindowSize, WindowSize);
var flyoutHost = GetHostContent();
if (flyoutHost != null)
{
flyoutHost.ShowFlyout();
}
Activate();
}
protected override void OnDeactivated(EventArgs args)
{
var flyoutHost = GetHostContent();
if (flyoutHost != null)
{
flyoutHost.HideFlyout();
}
}
}
}