diff --git a/src/Shared/HandyControlDemo_Shared/MainWindow.xaml b/src/Shared/HandyControlDemo_Shared/MainWindow.xaml index e7e26f91..65d23787 100644 --- a/src/Shared/HandyControlDemo_Shared/MainWindow.xaml +++ b/src/Shared/HandyControlDemo_Shared/MainWindow.xaml @@ -11,7 +11,7 @@ Background="{DynamicResource SecondaryRegionBrush}" ResizeMode="CanResizeWithGrip" WindowStartupLocation="CenterScreen" - d:DesignHeight="800" + d:DesignHeight="800" d:DesignWidth="1400" hc:Empty.ShowEmpty="True" Icon="/HandyControlDemo;component/Resources/Img/icon.ico"> diff --git a/src/Shared/HandyControlDemo_Shared/UserControl/Styles/NativeScrollViewerDemo.xaml b/src/Shared/HandyControlDemo_Shared/UserControl/Styles/NativeScrollViewerDemo.xaml index 354c9dbe..303bb8d1 100644 --- a/src/Shared/HandyControlDemo_Shared/UserControl/Styles/NativeScrollViewerDemo.xaml +++ b/src/Shared/HandyControlDemo_Shared/UserControl/Styles/NativeScrollViewerDemo.xaml @@ -4,7 +4,7 @@ xmlns:hc="https://handyorg.github.io/handycontrol" xmlns:langs="clr-namespace:HandyControlDemo.Properties.Langs" xmlns:ex="clr-namespace:HandyControlDemo.Tools.Extension" - Background="{DynamicResource RegionBrush}" + Background="{DynamicResource RegionBrush}" MaxWidth="550" Height="600"> @@ -45,7 +45,7 @@ - + diff --git a/src/Shared/HandyControl_Shared/Controls/ConfettiCannon/ConfettiCannon.cs b/src/Shared/HandyControl_Shared/Controls/ConfettiCannon/ConfettiCannon.cs index 075d5c5e..2c2159c6 100644 --- a/src/Shared/HandyControl_Shared/Controls/ConfettiCannon/ConfettiCannon.cs +++ b/src/Shared/HandyControl_Shared/Controls/ConfettiCannon/ConfettiCannon.cs @@ -13,8 +13,11 @@ namespace HandyControl.Controls; public class ConfettiCannon { + private static readonly ControlTokenManager TokenManager = + new(unregisterCallback: OnTokenUnregistered); + public static readonly DependencyProperty TokenProperty = DependencyProperty.RegisterAttached( - "Token", typeof(string), typeof(ConfettiCannon), new PropertyMetadata(null, OnTokenChanged)); + "Token", typeof(string), typeof(ConfettiCannon), new PropertyMetadata(null, TokenManager.OnTokenChanged)); public static void SetToken(DependencyObject element, string value) => element.SetValue(TokenProperty, value); @@ -23,7 +26,6 @@ public class ConfettiCannon => (string) element.GetValue(TokenProperty); private static readonly ConcurrentDictionary ConfettiCannons = new(); - private static readonly Dictionary ContainerDict = new(); private static readonly Lazy DefaultConfettiCannon = new(() => new ConfettiCannon(), true); private static readonly Random Random = new(); @@ -50,35 +52,8 @@ public class ConfettiCannon confettiCannon.StartAnimation(); } - public static void Register(string token, FrameworkElement element) + private static void OnTokenUnregistered(string token, FrameworkElement element) { - if (string.IsNullOrEmpty(token) || element is null) - { - return; - } - - ContainerDict[token] = element; - } - - public static void Unregister(FrameworkElement element) - { - if (element is null) - { - return; - } - - var first = ContainerDict.FirstOrDefault(item => ReferenceEquals(element, item.Value)); - Unregister(first.Key); - } - - public static void Unregister(string token) - { - if (string.IsNullOrEmpty(token)) - { - return; - } - - ContainerDict.Remove(token); ConfettiCannons.TryRemove(token, out _); } @@ -98,23 +73,6 @@ public class ConfettiCannon return ConfettiCannons[token]; } - private static void OnTokenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - if (d is not FrameworkElement element) - { - return; - } - - if (e.NewValue is null) - { - Unregister(element); - } - else - { - Register(e.NewValue.ToString(), element); - } - } - private static AdornerLayer GetAdornerLayer(string token) { AdornerDecorator decorator; @@ -125,7 +83,7 @@ public class ConfettiCannon } else { - ContainerDict.TryGetValue(token, out FrameworkElement element); + TokenManager.TryGetControl(token, out FrameworkElement element); decorator = element is System.Windows.Window ? VisualHelper.GetChild(element) : VisualHelper.GetChild(element); diff --git a/src/Shared/HandyControl_Shared/Controls/Dialog/Dialog.cs b/src/Shared/HandyControl_Shared/Controls/Dialog/Dialog.cs index 12bc8208..b46bb337 100644 --- a/src/Shared/HandyControl_Shared/Controls/Dialog/Dialog.cs +++ b/src/Shared/HandyControl_Shared/Controls/Dialog/Dialog.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; @@ -20,7 +19,7 @@ public class Dialog : ContentControl private Border _backElement; private AdornerContainer _container; - private static readonly Dictionary ContainerDict = new(); + private static readonly ControlTokenManager TokenManager = new(); private static readonly Dictionary DialogDict = new(); public static readonly DependencyProperty IsClosedProperty = DependencyProperty.Register( @@ -33,7 +32,8 @@ public class Dialog : ContentControl } public static readonly DependencyProperty MaskCanCloseProperty = DependencyProperty.RegisterAttached( - "MaskCanClose", typeof(bool), typeof(Dialog), new FrameworkPropertyMetadata(ValueBoxes.FalseBox, FrameworkPropertyMetadataOptions.Inherits)); + "MaskCanClose", typeof(bool), typeof(Dialog), + new FrameworkPropertyMetadata(ValueBoxes.FalseBox, FrameworkPropertyMetadataOptions.Inherits)); public static void SetMaskCanClose(DependencyObject element, bool value) => element.SetValue(MaskCanCloseProperty, ValueBoxes.BooleanBox(value)); @@ -51,22 +51,7 @@ public class Dialog : ContentControl } public static readonly DependencyProperty TokenProperty = DependencyProperty.RegisterAttached( - "Token", typeof(string), typeof(Dialog), new PropertyMetadata(default(string), OnTokenChanged)); - - private static void OnTokenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - if (d is FrameworkElement element) - { - if (e.NewValue == null) - { - Unregister(element); - } - else - { - Register(e.NewValue.ToString(), element); - } - } - } + "Token", typeof(string), typeof(Dialog), new PropertyMetadata(null, TokenManager.OnTokenChanged)); public static void SetToken(DependencyObject element, string value) => element.SetValue(TokenProperty, value); @@ -79,45 +64,6 @@ public class Dialog : ContentControl CommandBindings.Add(new CommandBinding(ControlCommands.Close, (s, e) => Close())); } - public static void Register(string token, FrameworkElement element) - { - if (string.IsNullOrEmpty(token) || element == null) return; - ContainerDict[token] = element; - } - - public static void Unregister(string token, FrameworkElement element) - { - if (string.IsNullOrEmpty(token) || element == null) return; - - if (ContainerDict.ContainsKey(token)) - { - if (ReferenceEquals(ContainerDict[token], element)) - { - ContainerDict.Remove(token); - } - } - } - - public static void Unregister(FrameworkElement element) - { - if (element == null) return; - var first = ContainerDict.FirstOrDefault(item => ReferenceEquals(element, item.Value)); - if (!string.IsNullOrEmpty(first.Key)) - { - ContainerDict.Remove(first.Key); - } - } - - public static void Unregister(string token) - { - if (string.IsNullOrEmpty(token)) return; - - if (ContainerDict.ContainsKey(token)) - { - ContainerDict.Remove(token); - } - } - public static Dialog Show(string token = "") where T : new() => Show(new T(), token); public static Dialog Show(object content, string token = "") @@ -140,10 +86,10 @@ public class Dialog : ContentControl { Close(token); DialogDict[token] = dialog; - ContainerDict.TryGetValue(token, out element); - decorator = element is System.Windows.Window ? - VisualHelper.GetChild(element) : - VisualHelper.GetChild(element); + TokenManager.TryGetControl(token, out element); + decorator = element is System.Windows.Window + ? VisualHelper.GetChild(element) + : VisualHelper.GetChild(element); } if (decorator != null) @@ -183,7 +129,7 @@ public class Dialog : ContentControl { Close(WindowHelper.GetActiveWindow()); } - else if (ContainerDict.TryGetValue(_token, out var element)) + else if (TokenManager.TryGetControl(_token, out var element)) { Close(element); DialogDict.Remove(_token); @@ -216,6 +162,7 @@ public class Dialog : ContentControl { decorator.Child.IsEnabled = true; } + var layer = decorator.AdornerLayer; layer?.Remove(_container); IsClosed = true; diff --git a/src/Shared/HandyControl_Shared/Controls/Growl/Growl.cs b/src/Shared/HandyControl_Shared/Controls/Growl/Growl.cs index 7a102853..af707ac9 100644 --- a/src/Shared/HandyControl_Shared/Controls/Growl/Growl.cs +++ b/src/Shared/HandyControl_Shared/Controls/Growl/Growl.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Windows; using System.Windows.Controls; @@ -28,6 +27,11 @@ public class Growl : Control private const string ElementButtonClose = "PART_ButtonClose"; private const int MinWaitTime = 2; + private static GrowlWindow GrowlWindow; + + private static readonly ControlTokenManager TokenManager = + new(registerCallback: OnTokenRegistered, unregisterCallback: OnTokenUnregistered); + public static readonly DependencyProperty GrowlParentProperty = DependencyProperty.RegisterAttached( "GrowlParent", typeof(bool), typeof(Growl), new PropertyMetadata(ValueBoxes.FalseBox, (o, args) => { @@ -36,31 +40,40 @@ public class Growl : Control SetGrowlPanel(panel); } })); + public static readonly DependencyProperty ShowModeProperty = DependencyProperty.RegisterAttached( "ShowMode", typeof(GrowlShowMode), typeof(Growl), new FrameworkPropertyMetadata(default(GrowlShowMode), FrameworkPropertyMetadataOptions.Inherits)); + public static readonly DependencyProperty ShowDateTimeProperty = DependencyProperty.Register( nameof(ShowDateTime), typeof(bool), typeof(Growl), new PropertyMetadata(ValueBoxes.TrueBox)); + public static readonly DependencyProperty MessageProperty = DependencyProperty.Register( nameof(Message), typeof(string), typeof(Growl), new PropertyMetadata(default(string))); + public static readonly DependencyProperty TimeProperty = DependencyProperty.Register( nameof(Time), typeof(DateTime), typeof(Growl), new PropertyMetadata(default(DateTime))); + public static readonly DependencyProperty IconProperty = DependencyProperty.Register( nameof(Icon), typeof(Geometry), typeof(Growl), new PropertyMetadata(default(Geometry))); + public static readonly DependencyProperty IconBrushProperty = DependencyProperty.Register( nameof(IconBrush), typeof(Brush), typeof(Growl), new PropertyMetadata(default(Brush))); + public static readonly DependencyProperty TypeProperty = DependencyProperty.Register( nameof(Type), typeof(InfoType), typeof(Growl), new PropertyMetadata(default(InfoType))); + public static readonly DependencyProperty TokenProperty = DependencyProperty.RegisterAttached( - "Token", typeof(string), typeof(Growl), new PropertyMetadata(default(string), OnTokenChanged)); + "Token", typeof(string), typeof(Growl), new PropertyMetadata(null, TokenManager.OnTokenChanged)); + internal static readonly DependencyProperty CancelStrProperty = DependencyProperty.Register( nameof(CancelStr), typeof(string), typeof(Growl), new PropertyMetadata(default(string))); + internal static readonly DependencyProperty ConfirmStrProperty = DependencyProperty.Register( nameof(ConfirmStr), typeof(string), typeof(Growl), new PropertyMetadata(default(string))); + private static readonly DependencyProperty IsCreatedAutomaticallyProperty = DependencyProperty.RegisterAttached( "IsCreatedAutomatically", typeof(bool), typeof(Growl), new PropertyMetadata(ValueBoxes.FalseBox)); - private static GrowlWindow GrowlWindow; - private static readonly Dictionary PanelDic = new(); private Panel _panelMore; private Grid _gridMain; @@ -141,51 +154,15 @@ public class Growl : Control CommandBindings.Add(new CommandBinding(ControlCommands.Confirm, ButtonOk_OnClick)); } - public static void Register(string token, Panel panel) + private static void OnTokenRegistered(string token, Panel panel) { - if (string.IsNullOrEmpty(token) || panel == null) return; - PanelDic[token] = panel; InitGrowlPanel(panel); } - public static void Unregister(string token, Panel panel) + private static void OnTokenUnregistered(string token, Panel panel) { - if (string.IsNullOrEmpty(token) || panel == null) return; - - if (PanelDic.ContainsKey(token)) - { - if (ReferenceEquals(PanelDic[token], panel)) - { - PanelDic.Remove(token); - panel.ContextMenu = null; - panel.SetCurrentValue(PanelElement.FluidMoveBehaviorProperty, DependencyProperty.UnsetValue); - } - } - } - - public static void Unregister(Panel panel) - { - if (panel == null) return; - var first = PanelDic.FirstOrDefault(item => ReferenceEquals(panel, item.Value)); - if (!string.IsNullOrEmpty(first.Key)) - { - PanelDic.Remove(first.Key); - panel.ContextMenu = null; - panel.SetCurrentValue(PanelElement.FluidMoveBehaviorProperty, DependencyProperty.UnsetValue); - } - } - - public static void Unregister(string token) - { - if (string.IsNullOrEmpty(token)) return; - - if (PanelDic.ContainsKey(token)) - { - var panel = PanelDic[token]; - PanelDic.Remove(token); - panel.ContextMenu = null; - panel.SetCurrentValue(PanelElement.FluidMoveBehaviorProperty, DependencyProperty.UnsetValue); - } + panel.ContextMenu = null; + panel.SetCurrentValue(PanelElement.FluidMoveBehaviorProperty, DependencyProperty.UnsetValue); } protected override void OnMouseEnter(MouseEventArgs e) @@ -219,36 +196,26 @@ public class Growl : Control if (_panelMore == null || _gridMain == null || _buttonClose == null) throw new Exception(); } - private static void OnTokenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - if (d is Panel panel) - { - if (e.NewValue == null) - { - Unregister(panel); - } - else - { - Register(e.NewValue.ToString(), panel); - } - } - } - public static void SetToken(DependencyObject element, string value) => element.SetValue(TokenProperty, value); public static string GetToken(DependencyObject element) => (string) element.GetValue(TokenProperty); - public static void SetShowMode(DependencyObject element, GrowlShowMode value) => element.SetValue(ShowModeProperty, value); + public static void SetShowMode(DependencyObject element, GrowlShowMode value) => + element.SetValue(ShowModeProperty, value); - public static GrowlShowMode GetShowMode(DependencyObject element) => (GrowlShowMode) element.GetValue(ShowModeProperty); + public static GrowlShowMode GetShowMode(DependencyObject element) => + (GrowlShowMode) element.GetValue(ShowModeProperty); - public static void SetGrowlParent(DependencyObject element, bool value) => element.SetValue(GrowlParentProperty, ValueBoxes.BooleanBox(value)); + public static void SetGrowlParent(DependencyObject element, bool value) => + element.SetValue(GrowlParentProperty, ValueBoxes.BooleanBox(value)); public static bool GetGrowlParent(DependencyObject element) => (bool) element.GetValue(GrowlParentProperty); - private static void SetIsCreatedAutomatically(DependencyObject element, bool value) => element.SetValue(IsCreatedAutomaticallyProperty, ValueBoxes.BooleanBox(value)); + private static void SetIsCreatedAutomatically(DependencyObject element, bool value) => + element.SetValue(IsCreatedAutomaticallyProperty, ValueBoxes.BooleanBox(value)); - private static bool GetIsCreatedAutomatically(DependencyObject element) => (bool) element.GetValue(IsCreatedAutomaticallyProperty); + private static bool GetIsCreatedAutomatically(DependencyObject element) => + (bool) element.GetValue(IsCreatedAutomaticallyProperty); /// /// 开始计时器 @@ -308,7 +275,8 @@ public class Growl : Control } }; - PanelElement.SetFluidMoveBehavior(panel, ResourceHelper.GetResourceInternal(ResourceToken.BehaviorXY400)); + PanelElement.SetFluidMoveBehavior(panel, + ResourceHelper.GetResourceInternal(ResourceToken.BehaviorXY400)); } private void Update() @@ -353,37 +321,37 @@ public class Growl : Control #if NET40 new Action( #endif - () => + () => + { + if (GrowlWindow == null) { - if (GrowlWindow == null) - { - GrowlWindow = new GrowlWindow(); - GrowlWindow.Show(); - InitGrowlPanel(GrowlWindow.GrowlPanel); - GrowlWindow.Init(); - } - - GrowlWindow.Show(true); - - var ctl = new Growl - { - Message = growlInfo.Message, - Time = DateTime.Now, - Icon = ResourceHelper.GetResource(growlInfo.IconKey) ?? growlInfo.Icon, - IconBrush = ResourceHelper.GetResource(growlInfo.IconBrushKey) ?? growlInfo.IconBrush, - _showCloseButton = growlInfo.ShowCloseButton, - ActionBeforeClose = growlInfo.ActionBeforeClose, - _staysOpen = growlInfo.StaysOpen, - ShowDateTime = growlInfo.ShowDateTime, - ConfirmStr = growlInfo.ConfirmStr, - CancelStr = growlInfo.CancelStr, - Type = growlInfo.Type, - _waitTime = Math.Max(growlInfo.WaitTime, MinWaitTime), - FlowDirection = growlInfo.FlowDirection - }; - - ShowInternal(GrowlWindow.GrowlPanel, ctl); + GrowlWindow = new GrowlWindow(); + GrowlWindow.Show(); + InitGrowlPanel(GrowlWindow.GrowlPanel); + GrowlWindow.Init(); } + + GrowlWindow.Show(true); + + var ctl = new Growl + { + Message = growlInfo.Message, + Time = DateTime.Now, + Icon = ResourceHelper.GetResource(growlInfo.IconKey) ?? growlInfo.Icon, + IconBrush = ResourceHelper.GetResource(growlInfo.IconBrushKey) ?? growlInfo.IconBrush, + _showCloseButton = growlInfo.ShowCloseButton, + ActionBeforeClose = growlInfo.ActionBeforeClose, + _staysOpen = growlInfo.StaysOpen, + ShowDateTime = growlInfo.ShowDateTime, + ConfirmStr = growlInfo.ConfirmStr, + CancelStr = growlInfo.CancelStr, + Type = growlInfo.Type, + _waitTime = Math.Max(growlInfo.WaitTime, MinWaitTime), + FlowDirection = growlInfo.FlowDirection + }; + + ShowInternal(GrowlWindow.GrowlPanel, ctl); + } #if NET40 ) #endif @@ -400,38 +368,38 @@ public class Growl : Control #if NET40 new Action( #endif - () => + () => + { + var ctl = new Growl { - var ctl = new Growl - { - Message = growlInfo.Message, - Time = DateTime.Now, - Icon = ResourceHelper.GetResource(growlInfo.IconKey) ?? growlInfo.Icon, - IconBrush = ResourceHelper.GetResource(growlInfo.IconBrushKey) ?? growlInfo.IconBrush, - _showCloseButton = growlInfo.ShowCloseButton, - ActionBeforeClose = growlInfo.ActionBeforeClose, - _staysOpen = growlInfo.StaysOpen, - ShowDateTime = growlInfo.ShowDateTime, - ConfirmStr = growlInfo.ConfirmStr, - CancelStr = growlInfo.CancelStr, - Type = growlInfo.Type, - _waitTime = Math.Max(growlInfo.WaitTime, MinWaitTime) - }; + Message = growlInfo.Message, + Time = DateTime.Now, + Icon = ResourceHelper.GetResource(growlInfo.IconKey) ?? growlInfo.Icon, + IconBrush = ResourceHelper.GetResource(growlInfo.IconBrushKey) ?? growlInfo.IconBrush, + _showCloseButton = growlInfo.ShowCloseButton, + ActionBeforeClose = growlInfo.ActionBeforeClose, + _staysOpen = growlInfo.StaysOpen, + ShowDateTime = growlInfo.ShowDateTime, + ConfirmStr = growlInfo.ConfirmStr, + CancelStr = growlInfo.CancelStr, + Type = growlInfo.Type, + _waitTime = Math.Max(growlInfo.WaitTime, MinWaitTime) + }; - if (!string.IsNullOrEmpty(growlInfo.Token)) + if (!string.IsNullOrEmpty(growlInfo.Token)) + { + if (TokenManager.TryGetControl(growlInfo.Token, out var panel)) { - if (PanelDic.TryGetValue(growlInfo.Token, out var panel)) - { - ShowInternal(panel, ctl); - } - } - else - { - // GrowlPanel is null, we create it automatically - GrowlPanel ??= CreateDefaultPanel(); - ShowInternal(GrowlPanel, ctl); + ShowInternal(panel, ctl); } } + else + { + // GrowlPanel is null, we create it automatically + GrowlPanel ??= CreateDefaultPanel(); + ShowInternal(GrowlPanel, ctl); + } + } #if NET40 ) #endif @@ -514,6 +482,7 @@ public class Growl : Control growlInfo.IconKey ??= ResourceToken.SuccessGeometry; growlInfo.IconBrushKey ??= ResourceToken.SuccessBrush; } + break; case InfoType.Info: if (!growlInfo.IsCustom) @@ -526,6 +495,7 @@ public class Growl : Control growlInfo.IconKey ??= ResourceToken.InfoGeometry; growlInfo.IconBrushKey ??= ResourceToken.InfoBrush; } + break; case InfoType.Warning: if (!growlInfo.IsCustom) @@ -538,6 +508,7 @@ public class Growl : Control growlInfo.IconKey ??= ResourceToken.WarningGeometry; growlInfo.IconBrushKey ??= ResourceToken.WarningBrush; } + break; case InfoType.Error: if (!growlInfo.IsCustom) @@ -551,6 +522,7 @@ public class Growl : Control growlInfo.IconKey ??= ResourceToken.ErrorGeometry; growlInfo.IconBrushKey ??= ResourceToken.DangerBrush; } + break; case InfoType.Fatal: if (!growlInfo.IsCustom) @@ -565,6 +537,7 @@ public class Growl : Control growlInfo.IconKey ??= ResourceToken.FatalGeometry; growlInfo.IconBrushKey ??= ResourceToken.PrimaryTextBrush; } + break; case InfoType.Ask: growlInfo.StaysOpen = true; @@ -579,6 +552,7 @@ public class Growl : Control growlInfo.IconKey ??= ResourceToken.AskGeometry; growlInfo.IconBrushKey ??= ResourceToken.AccentBrush; } + break; default: throw new ArgumentOutOfRangeException(nameof(infoType), infoType, null); @@ -844,7 +818,8 @@ public class Growl : Control _timerClose?.Stop(); var transform = new TranslateTransform(); _gridMain.RenderTransform = transform; - var animation = AnimationHelper.CreateAnimation(FlowDirection == FlowDirection.LeftToRight ? ActualWidth : -ActualWidth); + var animation = + AnimationHelper.CreateAnimation(FlowDirection == FlowDirection.LeftToRight ? ActualWidth : -ActualWidth); animation.Completed += (s, e) => { if (Parent is Panel panel) @@ -853,7 +828,7 @@ public class Growl : Control if (GrowlWindow != null) { - if (GrowlWindow.GrowlPanel != null && GrowlWindow.GrowlPanel.Children.Count == 0) + if (GrowlWindow.GrowlPanel is { Children.Count: 0 }) { GrowlWindow.Close(); GrowlWindow = null; @@ -861,9 +836,9 @@ public class Growl : Control } else { - if (GrowlPanel != null && GrowlPanel.Children.Count == 0 && GetIsCreatedAutomatically(GrowlPanel)) + if (GrowlPanel is { Children.Count: 0 } && GetIsCreatedAutomatically(GrowlPanel)) { - // If the count of children is zero, we need to remove the panel, provided that the panel was created automatically + // If the count of children is zero, we need to remove the panel, provided that the panel was created automatically RemoveDefaultPanel(GrowlPanel); GrowlPanel = null; } @@ -881,7 +856,7 @@ public class Growl : Control { if (!string.IsNullOrEmpty(token)) { - if (PanelDic.TryGetValue(token, out var panel)) + if (TokenManager.TryGetControl(token, out var panel)) { Clear(panel); } diff --git a/src/Shared/HandyControl_Shared/Controls/Other/NotifyIcon.cs b/src/Shared/HandyControl_Shared/Controls/Other/NotifyIcon.cs index 1c1b8acc..b46d0cdc 100644 --- a/src/Shared/HandyControl_Shared/Controls/Other/NotifyIcon.cs +++ b/src/Shared/HandyControl_Shared/Controls/Other/NotifyIcon.cs @@ -58,13 +58,16 @@ public class NotifyIcon : FrameworkElement, IDisposable private static int NextId; - private static readonly Dictionary NotifyIconDic = new(); + private static readonly ControlTokenManager TokenManager = new(); static NotifyIcon() { - VisibilityProperty.OverrideMetadata(typeof(NotifyIcon), new PropertyMetadata(ValueBoxes.VisibleBox, OnVisibilityChanged)); - DataContextProperty.OverrideMetadata(typeof(NotifyIcon), new FrameworkPropertyMetadata(DataContextPropertyChanged)); - ContextMenuProperty.OverrideMetadata(typeof(NotifyIcon), new FrameworkPropertyMetadata(ContextMenuPropertyChanged)); + VisibilityProperty.OverrideMetadata(typeof(NotifyIcon), + new PropertyMetadata(ValueBoxes.VisibleBox, OnVisibilityChanged)); + DataContextProperty.OverrideMetadata(typeof(NotifyIcon), + new FrameworkPropertyMetadata(DataContextPropertyChanged)); + ContextMenuProperty.OverrideMetadata(typeof(NotifyIcon), + new FrameworkPropertyMetadata(ContextMenuPropertyChanged)); } private static void OnVisibilityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) @@ -78,6 +81,7 @@ public class NotifyIcon : FrameworkElement, IDisposable { ctl.OnIconChanged(); } + ctl.UpdateIcon(true); } else if (ctl._iconCurrentHandle != IntPtr.Zero) @@ -132,48 +136,9 @@ public class NotifyIcon : FrameworkElement, IDisposable _dispatcherTimerPos.Tick += DispatcherTimerPos_Tick; } - public static void Register(string token, NotifyIcon notifyIcon) - { - if (string.IsNullOrEmpty(token) || notifyIcon == null) return; - NotifyIconDic[token] = notifyIcon; - } - - public static void Unregister(string token, NotifyIcon notifyIcon) - { - if (string.IsNullOrEmpty(token) || notifyIcon == null) return; - - if (NotifyIconDic.ContainsKey(token)) - { - if (ReferenceEquals(NotifyIconDic[token], notifyIcon)) - { - NotifyIconDic.Remove(token); - } - } - } - - public static void Unregister(NotifyIcon notifyIcon) - { - if (notifyIcon == null) return; - var first = NotifyIconDic.FirstOrDefault(item => ReferenceEquals(notifyIcon, item.Value)); - if (!string.IsNullOrEmpty(first.Key)) - { - NotifyIconDic.Remove(first.Key); - } - } - - public static void Unregister(string token) - { - if (string.IsNullOrEmpty(token)) return; - - if (NotifyIconDic.ContainsKey(token)) - { - NotifyIconDic.Remove(token); - } - } - public static void ShowBalloonTip(string title, string content, NotifyIconInfoType infoType, string token) { - if (NotifyIconDic.TryGetValue(token, out var notifyIcon)) + if (TokenManager.TryGetControl(token, out var notifyIcon)) { notifyIcon.ShowBalloonTip(title, content, infoType); } @@ -223,22 +188,7 @@ public class NotifyIcon : FrameworkElement, IDisposable } public static readonly DependencyProperty TokenProperty = DependencyProperty.Register( - nameof(Token), typeof(string), typeof(NotifyIcon), new PropertyMetadata(default(string), OnTokenChanged)); - - private static void OnTokenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - if (d is NotifyIcon notifyIcon) - { - if (e.NewValue == null) - { - Unregister(notifyIcon); - } - else - { - Register(e.NewValue.ToString(), notifyIcon); - } - } - } + nameof(Token), typeof(string), typeof(NotifyIcon), new PropertyMetadata(null, TokenManager.OnTokenChanged)); public string Token { @@ -247,7 +197,7 @@ public class NotifyIcon : FrameworkElement, IDisposable } public static readonly DependencyProperty TextProperty = DependencyProperty.Register( - nameof(Text), typeof(string), typeof(NotifyIcon), new PropertyMetadata(default(string), OnTextChanged)); + nameof(Text), typeof(string), typeof(NotifyIcon), new PropertyMetadata(null, OnTextChanged)); private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { @@ -268,7 +218,7 @@ public class NotifyIcon : FrameworkElement, IDisposable } public static readonly DependencyProperty IconProperty = DependencyProperty.Register( - nameof(Icon), typeof(ImageSource), typeof(NotifyIcon), new PropertyMetadata(default(ImageSource), OnIconChanged)); + nameof(Icon), typeof(ImageSource), typeof(NotifyIcon), new PropertyMetadata(null, OnIconChanged)); private static void OnIconChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { @@ -298,7 +248,8 @@ public class NotifyIcon : FrameworkElement, IDisposable } public static readonly DependencyProperty BlinkIntervalProperty = DependencyProperty.Register( - nameof(BlinkInterval), typeof(TimeSpan), typeof(NotifyIcon), new PropertyMetadata(TimeSpan.FromMilliseconds(500), OnBlinkIntervalChanged)); + nameof(BlinkInterval), typeof(TimeSpan), typeof(NotifyIcon), + new PropertyMetadata(TimeSpan.FromMilliseconds(500), OnBlinkIntervalChanged)); private static void OnBlinkIntervalChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { @@ -331,6 +282,7 @@ public class NotifyIcon : FrameworkElement, IDisposable }; ctl._dispatcherTimerBlink.Tick += ctl.DispatcherTimerBlinkTick; } + ctl._dispatcherTimerBlink.Start(); } else @@ -419,6 +371,7 @@ public class NotifyIcon : FrameworkElement, IDisposable { hWnd = InteropMethods.FindWindowEx(hWnd, IntPtr.Zero, "ToolbarWindow32", null); } + return hWnd; } @@ -432,6 +385,7 @@ public class NotifyIcon : FrameworkElement, IDisposable hTrayWnd = FindTrayToolbarOverFlowWindow(); isTrue = FindNotifyIcon(hTrayWnd, ref rectNotifyList); } + rectList = rectNotifyList; return isTrue; } @@ -446,8 +400,11 @@ public class NotifyIcon : FrameworkElement, IDisposable if (count > 0) { InteropMethods.GetWindowThreadProcessId(hTrayWnd, out var trayPid); - var hProcess = InteropMethods.OpenProcess(InteropValues.ProcessAccess.VMOperation | InteropValues.ProcessAccess.VMRead | InteropValues.ProcessAccess.VMWrite, false, trayPid); - var address = InteropMethods.VirtualAllocEx(hProcess, IntPtr.Zero, 1024, InteropValues.AllocationType.Commit, InteropValues.MemoryProtection.ReadWrite); + var hProcess = InteropMethods.OpenProcess( + InteropValues.ProcessAccess.VMOperation | InteropValues.ProcessAccess.VMRead | + InteropValues.ProcessAccess.VMWrite, false, trayPid); + var address = InteropMethods.VirtualAllocEx(hProcess, IntPtr.Zero, 1024, + InteropValues.AllocationType.Commit, InteropValues.MemoryProtection.ReadWrite); var btnData = new InteropValues.TBBUTTON(); var trayData = new InteropValues.TRAYDATA(); @@ -456,7 +413,8 @@ public class NotifyIcon : FrameworkElement, IDisposable for (uint i = 0; i < count; i++) { InteropMethods.SendMessage(hTrayWnd, InteropValues.TB_GETBUTTON, i, address); - var isTrue = InteropMethods.ReadProcessMemory(hProcess, address, out btnData, Marshal.SizeOf(btnData), out _); + var isTrue = + InteropMethods.ReadProcessMemory(hProcess, address, out btnData, Marshal.SizeOf(btnData), out _); if (!isTrue) continue; if (btnData.dwData == IntPtr.Zero) @@ -464,18 +422,21 @@ public class NotifyIcon : FrameworkElement, IDisposable btnData.dwData = btnData.iString; } - InteropMethods.ReadProcessMemory(hProcess, btnData.dwData, out trayData, Marshal.SizeOf(trayData), out _); + InteropMethods.ReadProcessMemory(hProcess, btnData.dwData, out trayData, Marshal.SizeOf(trayData), + out _); InteropMethods.GetWindowThreadProcessId(trayData.hwnd, out var dwProcessId); if (dwProcessId == (uint) handle) { var rect = new InteropValues.RECT(); - var lngRect = InteropMethods.VirtualAllocEx(hProcess, IntPtr.Zero, Marshal.SizeOf(typeof(Rect)), InteropValues.AllocationType.Commit, InteropValues.MemoryProtection.ReadWrite); + var lngRect = InteropMethods.VirtualAllocEx(hProcess, IntPtr.Zero, Marshal.SizeOf(typeof(Rect)), + InteropValues.AllocationType.Commit, InteropValues.MemoryProtection.ReadWrite); InteropMethods.SendMessage(hTrayWnd, InteropValues.TB_GETITEMRECT, i, lngRect); InteropMethods.ReadProcessMemory(hProcess, lngRect, out rect, Marshal.SizeOf(rect), out _); - InteropMethods.VirtualFreeEx(hProcess, lngRect, Marshal.SizeOf(rect), InteropValues.FreeType.Decommit); + InteropMethods.VirtualFreeEx(hProcess, lngRect, Marshal.SizeOf(rect), + InteropValues.FreeType.Decommit); InteropMethods.VirtualFreeEx(hProcess, lngRect, 0, InteropValues.FreeType.Release); var left = rectTray.Left + rect.Left; @@ -492,10 +453,12 @@ public class NotifyIcon : FrameworkElement, IDisposable isFind = true; } } + InteropMethods.VirtualFreeEx(hProcess, address, 0x4096, InteropValues.FreeType.Decommit); InteropMethods.VirtualFreeEx(hProcess, address, 0, InteropValues.FreeType.Release); InteropMethods.CloseHandle(hProcess); } + return isFind; } @@ -515,6 +478,7 @@ public class NotifyIcon : FrameworkElement, IDisposable IconHelper.GetDefaultIconHandles(out _, out _iconHandle); _iconDefaultHandle = _iconHandle.CriticalGetHandle(); } + _iconCurrentHandle = _iconDefaultHandle; } } @@ -610,6 +574,7 @@ public class NotifyIcon : FrameworkElement, IDisposable _dispatcherTimerPos.Interval = TimeSpan.FromMilliseconds(200); _dispatcherTimerPos.Start(); } + break; } } @@ -638,12 +603,12 @@ public class NotifyIcon : FrameworkElement, IDisposable RoutedEvent = ClickEvent }); } + _doubleClick = false; } private void ShowContextMenu() { - if (ContextContent != null) { _contextContent ??= new Popup @@ -725,6 +690,7 @@ public class NotifyIcon : FrameworkElement, IDisposable { _dispatcherTimerBlink.Stop(); } + UpdateIcon(false); } diff --git a/src/Shared/HandyControl_Shared/HandyControl_Shared.projitems b/src/Shared/HandyControl_Shared/HandyControl_Shared.projitems index 6cec2618..ccd0a1e6 100644 --- a/src/Shared/HandyControl_Shared/HandyControl_Shared.projitems +++ b/src/Shared/HandyControl_Shared/HandyControl_Shared.projitems @@ -104,6 +104,7 @@ + diff --git a/src/Shared/HandyControl_Shared/Tools/Helper/ControlTokenManager`1.cs b/src/Shared/HandyControl_Shared/Tools/Helper/ControlTokenManager`1.cs new file mode 100644 index 00000000..8ff041cd --- /dev/null +++ b/src/Shared/HandyControl_Shared/Tools/Helper/ControlTokenManager`1.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.Windows; + +namespace HandyControl.Tools; + +internal class ControlTokenManager( + Action registerCallback = null, + Action unregisterCallback = null +) + where T : FrameworkElement +{ + private static readonly Dictionary> ControlDict = new(); + + public void Register(string token, T control) + { + if (string.IsNullOrEmpty(token) || control == null) + { + return; + } + + ControlDict[token] = new WeakReference(control); + registerCallback?.Invoke(token, control); + } + + public void Unregister(string token, T control) + { + if (string.IsNullOrEmpty(token) || control == null) + { + return; + } + + if (!ControlDict.TryGetValue(token, out var reference)) + { + return; + } + + if (!reference.TryGetTarget(out var target)) + { + return; + } + + if (!ReferenceEquals(target, control)) + { + return; + } + + ControlDict.Remove(token); + unregisterCallback?.Invoke(token, control); + } + + public void Unregister(T control) + { + if (control == null) + { + return; + } + + string unregisteredToken = null; + foreach (var item in ControlDict) + { + if (!item.Value.TryGetTarget(out var target) || !ReferenceEquals(control, target)) + { + continue; + } + + unregisteredToken = item.Key; + break; + } + + if (unregisteredToken == null) + { + return; + } + + ControlDict.Remove(unregisteredToken); + unregisterCallback?.Invoke(unregisteredToken, control); + } + + public void Unregister(string token) + { + if (string.IsNullOrEmpty(token)) + { + return; + } + + if (!ControlDict.TryGetValue(token, out var reference)) + { + return; + } + + ControlDict.Remove(token); + + if (!reference.TryGetTarget(out var target)) + { + return; + } + + unregisterCallback?.Invoke(token, target); + } + + public bool TryGetControl(string token, out T control) + { + control = null; + + if (string.IsNullOrEmpty(token) || !ControlDict.TryGetValue(token, out var reference)) + { + return false; + } + + return reference.TryGetTarget(out control); + } + + public void OnTokenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is not T control) + { + return; + } + + if (e.NewValue == null) + { + Unregister(control); + } + else + { + Register(e.NewValue.ToString(), control); + } + } +}