diff --git a/.gitremotes b/.gitremotes new file mode 100644 index 0000000..5d34203 --- /dev/null +++ b/.gitremotes @@ -0,0 +1,10 @@ +FSI.Lib http://desiaugetc7-088:3000/FSI/FSI.Lib.git (fetch) +FSI.Lib http://desiaugetc7-088:3000/FSI/FSI.Lib.git (push) +NHotkey http://desiaugetc7-088:3000/FSI/NHotkey.git (fetch) +NHotkey http://desiaugetc7-088:3000/FSI/NHotkey.git (push) +NotifyIconWpf http://desiaugetc7-088:3000/FSI/NotifyIconWpf.git (fetch) +NotifyIconWpf http://desiaugetc7-088:3000/FSI/NotifyIconWpf.git (push) +RadialMenu http://desiaugetc7-088:3000/FSI/RadialMenu.git (fetch) +RadialMenu http://desiaugetc7-088:3000/FSI/RadialMenu.git (push) +origin http://desiaugetc7-088:3000/FSI/FSI.Tools.git (fetch) +origin http://desiaugetc7-088:3000/FSI/FSI.Tools.git (push) diff --git a/FSI.BT.Tools.sln b/FSI.BT.Tools.sln new file mode 100644 index 0000000..159134b --- /dev/null +++ b/FSI.BT.Tools.sln @@ -0,0 +1,49 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.32112.339 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FSI.BT.Tools", "FSI.BT.Tools\FSI.BT.Tools.csproj", "{21041032-87C5-4842-B540-9EB3ACD8A16D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FSI.Lib", "FSI.Lib\FSI.Lib\FSI.Lib.csproj", "{3232DF9C-FAAC-4429-8E7D-E3E9F266118F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NotifyIconWpf", "NotifyIconWpf\NotifyIconWpf\NotifyIconWpf.csproj", "{C60282E9-2046-4D76-86F8-0B5CBFDE6D26}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NHotkey.Wpf", "NHotkey\NHotkey.Wpf\NHotkey.Wpf.csproj", "{B2E18BB7-BB05-4E5E-B6E0-C05EF98F5287}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RadialMenu", "RadialMenu\RadialMenu.csproj", "{9DF116EE-45B1-4297-BE75-0F6B78B33689}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {21041032-87C5-4842-B540-9EB3ACD8A16D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {21041032-87C5-4842-B540-9EB3ACD8A16D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {21041032-87C5-4842-B540-9EB3ACD8A16D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {21041032-87C5-4842-B540-9EB3ACD8A16D}.Release|Any CPU.Build.0 = Release|Any CPU + {3232DF9C-FAAC-4429-8E7D-E3E9F266118F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3232DF9C-FAAC-4429-8E7D-E3E9F266118F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3232DF9C-FAAC-4429-8E7D-E3E9F266118F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3232DF9C-FAAC-4429-8E7D-E3E9F266118F}.Release|Any CPU.Build.0 = Release|Any CPU + {C60282E9-2046-4D76-86F8-0B5CBFDE6D26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C60282E9-2046-4D76-86F8-0B5CBFDE6D26}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C60282E9-2046-4D76-86F8-0B5CBFDE6D26}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C60282E9-2046-4D76-86F8-0B5CBFDE6D26}.Release|Any CPU.Build.0 = Release|Any CPU + {B2E18BB7-BB05-4E5E-B6E0-C05EF98F5287}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B2E18BB7-BB05-4E5E-B6E0-C05EF98F5287}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B2E18BB7-BB05-4E5E-B6E0-C05EF98F5287}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B2E18BB7-BB05-4E5E-B6E0-C05EF98F5287}.Release|Any CPU.Build.0 = Release|Any CPU + {9DF116EE-45B1-4297-BE75-0F6B78B33689}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9DF116EE-45B1-4297-BE75-0F6B78B33689}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9DF116EE-45B1-4297-BE75-0F6B78B33689}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9DF116EE-45B1-4297-BE75-0F6B78B33689}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {E7CCC9AB-E9BE-4414-B73F-8387EDFEEB2D} + EndGlobalSection +EndGlobal diff --git a/FSI.BT.Tools/Admin.cs b/FSI.BT.Tools/Admin.cs new file mode 100644 index 0000000..59ad7e5 --- /dev/null +++ b/FSI.BT.Tools/Admin.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using FSI.Lib.Helpers; + +namespace FSI.BT.Tools +{ + internal class Admin + { + public static bool CheckAdminRight() + { + string adminsSettings = Lib.Settings.Setting("AdminUsers", Lib.Settings.Mode.ExeSetttings); + string[] adminsCrypt = adminsSettings.Split(";;"); + + List admins = new List(); + foreach (string admin in adminsCrypt) + { + try + { + admins.Add(Lib.DeEncryptString.DeEncrypt.DecryptString(Lib.DeEncryptString.DeEncrypt.DecryptString(admin, Lib.Settings.Setting("DeEnCryptPasswort", Lib.Settings.Mode.ExeSetttings)), Lib.Settings.Setting("DeEnCryptPasswort", Lib.Settings.Mode.ExeSetttings))); + } + catch { } + } + + System.Security.Principal.WindowsIdentity windowsIdentity = System.Security.Principal.WindowsIdentity.GetCurrent(); + + foreach (string admin in admins) + { + if (string.Equals(admin, windowsIdentity.ShortName(), StringComparison.OrdinalIgnoreCase)) + return true; + } + + return false; + } + } +} diff --git a/FSI.BT.Tools/App.config b/FSI.BT.Tools/App.config new file mode 100644 index 0000000..3dea568 --- /dev/null +++ b/FSI.BT.Tools/App.config @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/FSI.BT.Tools/App.xaml b/FSI.BT.Tools/App.xaml new file mode 100644 index 0000000..ca580b7 --- /dev/null +++ b/FSI.BT.Tools/App.xaml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + diff --git a/FSI.BT.Tools/App.xaml.cs b/FSI.BT.Tools/App.xaml.cs new file mode 100644 index 0000000..78ee390 --- /dev/null +++ b/FSI.BT.Tools/App.xaml.cs @@ -0,0 +1,51 @@ +using Hardcodet.Wpf.TaskbarNotification; +using NHotkey; +using NHotkey.Wpf; +using System.Windows; +using System.Windows.Input; +using FSI.Lib.Wpf.ExtensionMethods; +using System.IO; +using System.Diagnostics; +using System; + +namespace FSI.BT.Tools +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : System.Windows.Application + { + private static readonly KeyGesture RadialMenu = new(Key.OemBackslash, ModifierKeys.Control); + private static readonly KeyGesture TimeStamp = new(Key.C, ModifierKeys.Control | ModifierKeys.Alt); + + public void InitApplication(object sender, StartupEventArgs e) + { + Global.TaskbarIcon = (TaskbarIcon)FindResource("FSINotifyIcon"); + + Global.AdminRights = Admin.CheckAdminRight(); + + HotkeyManager.Current.AddOrReplace("RadialMenu", RadialMenu, ShowRadialMenu); + + HotkeyManager.Current.AddOrReplace("TimeStampToClipboard", TimeStamp, TimeStampToClipboard); + + Global.FrmRadialMenu = new(); + } + + private void ShowRadialMenu(object sender, HotkeyEventArgs e) + { + var cmd = new Commands.RadialMenuCommand(); + cmd.Execute(null); + + e.Handled = true; + } + + private void TimeStampToClipboard(object sender, HotkeyEventArgs e) + { + var cmd = new Commands.TimeStampToClipboardCommand(); + cmd.Execute(null); + + e.Handled = true; + } + + } +} \ No newline at end of file diff --git a/FSI.BT.Tools/Commands/CommandBase.cs b/FSI.BT.Tools/Commands/CommandBase.cs new file mode 100644 index 0000000..c17b044 --- /dev/null +++ b/FSI.BT.Tools/Commands/CommandBase.cs @@ -0,0 +1,153 @@ +using System; +using System.ComponentModel; +using System.Windows; +using System.Windows.Input; +using System.Windows.Markup; +using System.Windows.Media; +using Hardcodet.Wpf.TaskbarNotification; + +namespace FSI.BT.Tools.Commands +{ + /// + /// Basic implementation of the + /// interface, which is also accessible as a markup + /// extension. + /// + public abstract class CommandBase : MarkupExtension, ICommand + where T : class, ICommand, new() + { + /// + /// A singleton instance. + /// + private static T? command; + + /// + /// Gets a shared command instance. + /// + public override object ProvideValue(IServiceProvider serviceProvider) + { + if (command == null) command = new T(); + return command; + } + + /// + /// Fires when changes occur that affect whether + /// or not the command should execute. + /// + public event EventHandler CanExecuteChanged + { + add { CommandManager.RequerySuggested += value; } + remove { CommandManager.RequerySuggested -= value; } + } + + /// + /// Defines the method to be called when the command is invoked. + /// + /// Data used by the command. + /// If the command does not require data to be passed, + /// this object can be set to null. + /// + public abstract void Execute(object parameter); + + /// + /// Defines the method that determines whether the command + /// can execute in its current state. + /// + /// + /// This default implementation always returns true. + /// + /// Data used by the command. + /// If the command does not require data to be passed, + /// this object can be set to null. + /// + public virtual bool CanExecute(object parameter) + { + return !IsDesignMode; + } + + + public static bool IsDesignMode + { + get + { + return (bool) + DependencyPropertyDescriptor.FromProperty(DesignerProperties.IsInDesignModeProperty, + typeof(FrameworkElement)) + .Metadata.DefaultValue; + } + } + + + /// + /// Resolves the window that owns the TaskbarIcon class. + /// + /// + /// Window + protected Window? GetTaskbarWindow(object commandParameter) + { + if (IsDesignMode) + return null; + + // get the showcase window off the taskbar icon + var tb = commandParameter as TaskbarIcon; + return tb == null ? null : TryFindParent(tb); + } + + #region TryFindParent helper + + /// + /// Finds a parent of a given item on the visual tree. + /// + /// The type of the queried item. + /// A direct or indirect child of the + /// queried item. + /// The first parent item that matches the submitted + /// type parameter. If not matching item can be found, a null + /// reference is being returned. + public static TParent? TryFindParent(DependencyObject child) where TParent : DependencyObject + { + //get parent item + DependencyObject parentObject = GetParentObject(child); + + //we've reached the end of the tree + if (parentObject == null) return null; + + //check if the parent matches the type we're looking for + if (parentObject is TParent parent) + { + return parent; + } + + //use recursion to proceed with next level + return TryFindParent(parentObject); + } + + /// + /// This method is an alternative to WPF's + /// method, which also + /// supports content elements. Keep in mind that for content element, + /// this method falls back to the logical tree of the element! + /// + /// The item to be processed. + /// The submitted item's parent, if available. Otherwise + /// null. + public static DependencyObject? GetParentObject(DependencyObject child) + { + if (child == null) return null; + + if (child is ContentElement contentElement) + { + DependencyObject parent = ContentOperations.GetParent(contentElement); + if (parent != null) return parent; + + FrameworkContentElement? fce = contentElement as FrameworkContentElement; + return fce?.Parent; + } + + //if it's not a ContentElement, rely on VisualTreeHelper + return VisualTreeHelper.GetParent(child); + } + + #endregion + } +} \ No newline at end of file diff --git a/FSI.BT.Tools/Commands/ExitCommand.cs b/FSI.BT.Tools/Commands/ExitCommand.cs new file mode 100644 index 0000000..faa9524 --- /dev/null +++ b/FSI.BT.Tools/Commands/ExitCommand.cs @@ -0,0 +1,20 @@ +using System.Windows; + +namespace FSI.BT.Tools.Commands +{ + /// + /// Shows the main window. + /// + public class ExitCommand : CommandBase + { + public override void Execute(object parameter) + { + Application.Current.Shutdown(); + } + + public override bool CanExecute(object parameter) + { + return true; + } + } +} \ No newline at end of file diff --git a/FSI.BT.Tools/Commands/LoginCommand.cs b/FSI.BT.Tools/Commands/LoginCommand.cs new file mode 100644 index 0000000..ec3ceb8 --- /dev/null +++ b/FSI.BT.Tools/Commands/LoginCommand.cs @@ -0,0 +1,29 @@ +using System.Windows; + +namespace FSI.BT.Tools.Commands +{ + /// + /// Shows the main window. + /// + public class LoginCommand : CommandBase + { + public override void Execute(object parameter) + { + Lib.Guis.AutoPw.FrmMain frmMain = new() + { + CloseAtLostFocus = true, + WindowStartupLocation = WindowStartupLocation.CenterOwner, + }; + frmMain.ShowDialog(); + + Global.UserRights = + Global.AdminRights = frmMain.PwOk; + } + + + public override bool CanExecute(object parameter) + { + return true; + } + } +} \ No newline at end of file diff --git a/FSI.BT.Tools/Commands/OpenAppCommand.cs b/FSI.BT.Tools/Commands/OpenAppCommand.cs new file mode 100644 index 0000000..dcc3556 --- /dev/null +++ b/FSI.BT.Tools/Commands/OpenAppCommand.cs @@ -0,0 +1,313 @@ +using FSI.Lib; +using System; +using System.Diagnostics; +using System.IO; +using System.Windows; + +namespace FSI.BT.Tools.Commands +{ + /// + /// Shows the main window. + /// + public class OpenAppCommand : CommandBase + { + public override void Execute(object parameter) + { + string[] files = new string[] { }; + string[] pathes = new string[] { }; + string arguments = string.Empty; + + switch ((string)parameter) + { + case "SimaticManager": + files = Global.Settings.Apps.SieSimaticManagerExe.Split(";"); + break; + + case "TIAv13": + files = Global.Settings.Apps.SieTiaV13Exe.Split(";"); + break; + + case "TIAv14": + files = Global.Settings.Apps.SieTiaV14Exe.Split(";"); + break; + + case "TIAv15": + files = Global.Settings.Apps.SieTiaV15Exe.Split(";"); + break; + + case "TIAv16": + files = Global.Settings.Apps.SieTiaV16Exe.Split(";"); + break; + + case "TIAv17": + files = Global.Settings.Apps.SieTiaV17Exe.Split(";"); + break; + + case "Starter": + files = Global.Settings.Apps.SieTiaVStarterExe.Split(";"); + break; + + case "Epl": + files = Global.Settings.Apps.Epl.Exe.Split(";"); + arguments = Global.Settings.Apps.Epl.Arguments; + break; + + case "EplPrj": + Lib.Guis.Prj.Mgt.FrmMain frmMainEplPrj = new() + { + ShowPdf = false, + CloseAtLostFocus = true, + WindowStartupLocation = WindowStartupLocation.CenterOwner, + }; + frmMainEplPrj.Show(); + return; + + case "EplPdf": + Lib.Guis.Prj.Mgt.FrmMain frmMainEplPdf = new() + { + ShowPdf = true, + CloseAtLostFocus = true, + WindowStartupLocation = WindowStartupLocation.CenterOwner, + }; + frmMainEplPdf.Show(); + return; + + case "EplPdfMgt": + Lib.Guis.Pdf.Mgt.FrmMain frmMainEplPdfMgt = new() + { + CloseAtLostFocus = true + }; + frmMainEplPdfMgt.Show(); + return; + + case "Npp": + files = Global.Settings.Apps.NppExe.Split(";"); + break; + + case "TotalCmd": + files = Global.Settings.Apps.TotalCmdExe.Split(";"); + break; + + case "TeXstudio": + files = Global.Settings.Apps.TeXstudioExe.Split(";"); + pathes = Global.Settings.Apps.TeXstudioPath.Split(";"); + break; + + case "VS": + files = Global.Settings.Apps.VsExe.Split(";"); + break; + + case "VS.Code": + files = Global.Settings.Apps.VsCodeExe.Split(";"); + break; + + + case "Rdp": + files = Global.Settings.Apps.RdpExe.Split(";"); ; + break; + + case "DeEncrypt": + Lib.Guis.DeEncryptMessage.FrmMain frmMainDeEnCrypt = new() + { + Password = Global.Settings.General.DeEnCryptPasswort, + CloseAtLostFocus = true, + WindowStartupLocation = WindowStartupLocation.CenterOwner, + }; + frmMainDeEnCrypt.Show(); + return; + + case "StarterCsvExporter": + Lib.Guis.SieStarterCsvExporter.FrmMain frmMain = new(); + frmMain.Show(); + return; + + } + + string fileName = string.Empty; + string path = string.Empty; + + for (int i = 0; i <= files.Length - 1; i++) + { + if (File.Exists(Environment.ExpandEnvironmentVariables(files[i].Trim()))) + { + fileName = Environment.ExpandEnvironmentVariables(files[i].Trim()); + } + + if (pathes.Length == 0) + { + path = Path.GetDirectoryName(fileName); + } + else + { + path = Environment.ExpandEnvironmentVariables(pathes[i].Trim()); + } + } + + if (ProgramIsRunning(fileName)) + { + ProgramToFront(fileName); + } + else + { + Process process = new(); + process.StartInfo.FileName = fileName; + process.StartInfo.WorkingDirectory = path; + process.StartInfo.Arguments = arguments; + + try + { + process.Start(); + } + catch (System.ComponentModel.Win32Exception ex) when (ex.NativeErrorCode == 740) + { + try + { + process.StartInfo.UseShellExecute = true; + process.StartInfo.Verb = "runas"; + process.Start(); + } + catch { } + } + } + } + + public override bool CanExecute(object parameter) + { + string[] files = new string[] { }; + switch ((string)parameter) + { + case "SimaticManager": + files = Global.Settings.Apps.SieSimaticManagerExe.Split(";"); + break; + + case "TIAv13": + files = Global.Settings.Apps.SieTiaV13Exe.Split(";"); + break; + + case "TIAv14": + files = Global.Settings.Apps.SieTiaV14Exe.Split(";"); + break; + + case "TIAv15": + files = Global.Settings.Apps.SieTiaV15Exe.Split(";"); + break; + + case "TIAv16": + files = Global.Settings.Apps.SieTiaV16Exe.Split(";"); + break; + + case "TIAv17": + files = Global.Settings.Apps.SieTiaV17Exe.Split(";"); + break; + + case "Starter": + files = Global.Settings.Apps.SieTiaVStarterExe.Split(";"); + break; + + case "Epl": + files = Global.Settings.Apps.Epl.Exe.Split(";"); + break; + + case "EplPrj": + return true; + + case "EplPdf": + return true; + + case "EplPdfMgt": + return Global.AdminRights; + + case "Npp": + files = Global.Settings.Apps.NppExe.Split(";"); + break; + + case "TotalCmd": + files = Global.Settings.Apps.TotalCmdExe.Split(";"); + break; + + case "TeXstudio": + files = Global.Settings.Apps.TeXstudioExe.Split(";"); + break; + + case "VS": + files = Global.Settings.Apps.VsExe.Split(";"); + break; + + case "VS.Code": + files = Global.Settings.Apps.VsCodeExe.Split(";"); + break; + + case "Rdp": + files = Global.Settings.Apps.RdpExe.Split(";"); ; + break; + + case "DeEncrypt": + return Global.AdminRights; + + case "StarterCsvExporter": + return Global.AdminRights; + default: return false; + + } + + foreach (string file in files) + { + if (File.Exists(Environment.ExpandEnvironmentVariables(file.Trim()))) + { + return true; + } + } + + return false; + } + + private bool ProgramIsRunning(string FullPath) + { + string FilePath = Path.GetDirectoryName(FullPath); + string FileName = Path.GetFileNameWithoutExtension(FullPath).ToLower(); + bool isRunning = false; + + Process[] pList = Process.GetProcessesByName(FileName); + + foreach (Process p in pList) + { + if (p.MainModule.FileName.StartsWith(FilePath, StringComparison.InvariantCultureIgnoreCase)) + { + isRunning = true; + break; + } + } + + return isRunning; + } + + [System.Runtime.InteropServices.DllImport("User32.dll")] + private static extern bool SetForegroundWindow(IntPtr handle); + [System.Runtime.InteropServices.DllImport("User32.dll")] + private static extern bool ShowWindow(IntPtr handle, int nCmdShow); + [System.Runtime.InteropServices.DllImport("User32.dll")] + private static extern bool IsIconic(IntPtr handle); + + private void ProgramToFront(string FullPath) + { + string FilePath = Path.GetDirectoryName(FullPath); + string FileName = Path.GetFileNameWithoutExtension(FullPath).ToLower(); + + Process[] pList = Process.GetProcessesByName(FileName); + + foreach (Process p in pList) + { + if (p.MainModule.FileName.StartsWith(FilePath, StringComparison.InvariantCultureIgnoreCase)) + { + IntPtr handle = p.MainWindowHandle; + if (IsIconic(handle)) + { + ShowWindow(handle, 9); + } + SetForegroundWindow(handle); + break; + } + } + } + } +} \ No newline at end of file diff --git a/FSI.BT.Tools/Commands/OpenLinkCommand.cs b/FSI.BT.Tools/Commands/OpenLinkCommand.cs new file mode 100644 index 0000000..c6de297 --- /dev/null +++ b/FSI.BT.Tools/Commands/OpenLinkCommand.cs @@ -0,0 +1,79 @@ +using FSI.Lib; +using System; +using System.Diagnostics; + +namespace FSI.BT.Tools.Commands +{ + /// + /// Shows the main window. + /// + public class OpenLinkCommand : CommandBase + { + public override void Execute(object parameter) + { + string url = String.Empty; + + switch ((string)parameter) + { + case "ZentralWeb": + url = Global.Settings.Urls.ZentralWeb; + break; + + case "Schichtbuch": + url = Global.Settings.Urls.Schichtbuch; + break; + + case "SPS": + url = Global.Settings.Urls.SPS; + break; + + case "PL1.Pls": + url = Global.Settings.Urls.Pl1Pls; + break; + + case "PL2.Pls": + url = Global.Settings.Urls.Pl2Pls; + break; + + case "PL2.Als": + url = Global.Settings.Urls.Pl2Als; + break; + + case "PL3.Pls": + url = Global.Settings.Urls.Pl3Pls; + break; + + case "FSI.Gitea": + url = Global.Settings.Urls.Gitea; + break; + + case "FSI.Wiki": + url = Global.Settings.Urls.Wiki; + break; + + case "Erp": + url = Global.Settings.Urls.Erp; + break; + } + + url = url.Replace("&", "^&"); + Process.Start(new ProcessStartInfo(url) { UseShellExecute = true }); + } + + public override bool CanExecute(object parameter) + { + string url = String.Empty; + switch ((string)parameter) + { + case "FSI.Gitea": + return Global.AdminRights; + + case "FSI.Wiki": + return Global.AdminRights; + + default: + return true; + } + } + } +} \ No newline at end of file diff --git a/FSI.BT.Tools/Commands/RadialMenuCommand.cs b/FSI.BT.Tools/Commands/RadialMenuCommand.cs new file mode 100644 index 0000000..d6267de --- /dev/null +++ b/FSI.BT.Tools/Commands/RadialMenuCommand.cs @@ -0,0 +1,37 @@ +using FSI.Lib.Wpf.ExtensionMethods; +using System.Windows; + +namespace FSI.BT.Tools.Commands +{ + /// + /// Shows the main window. + /// + public class RadialMenuCommand : CommandBase + { + public override void Execute(object parameter) + { + if (Global.FrmRadialMenu.Visibility == Visibility.Collapsed) + { + Global.FrmRadialMenu.ShowCenteredToMouse(); + Global.FrmRadialMenu.ActivateCenteredToMouse(); + return; + } + + if (Global.FrmRadialMenu.Visibility == Visibility.Hidden) + { + Global.FrmRadialMenu.Visibility = Visibility.Visible; + } + else + { + Global.FrmRadialMenu.Visibility = Visibility.Hidden; + } + + Global.FrmRadialMenu.ActivateCenteredToMouse(); + } + + public override bool CanExecute(object parameter) + { + return Global.AdminRights || Global.UserRights; + } + } +} \ No newline at end of file diff --git a/FSI.BT.Tools/Commands/TimeStampToClipboardCommand.cs b/FSI.BT.Tools/Commands/TimeStampToClipboardCommand.cs new file mode 100644 index 0000000..d08b13f --- /dev/null +++ b/FSI.BT.Tools/Commands/TimeStampToClipboardCommand.cs @@ -0,0 +1,26 @@ +using System.Windows.Controls.Primitives; + +namespace FSI.BT.Tools.Commands +{ + /// + /// Shows the main window. + /// + public class TimeStampToClipboardCommand : CommandBase + { + public override void Execute(object parameter) + { + System.Windows.Forms.Clipboard.SetDataObject(Global.Settings.General.TimeStampFormat); + var balloon = new ToolTip() + { + BalloonText = "Zeitstempel", + BalloonDesc = "Der aktuelle Zeitstempel wurde in die Zwischenablage kopiert." + }; + Global.TaskbarIcon.ShowCustomBalloon(balloon, PopupAnimation.Slide, 2000); + } + + public override bool CanExecute(object parameter) + { + return true; + } + } +} \ No newline at end of file diff --git a/FSI.BT.Tools/FSI.BT.Tools.csproj b/FSI.BT.Tools/FSI.BT.Tools.csproj new file mode 100644 index 0000000..a287bc9 --- /dev/null +++ b/FSI.BT.Tools/FSI.BT.Tools.csproj @@ -0,0 +1,57 @@ + + + + WinExe + net6.0-windows + WinExe + enable + true + true + Icons\FondiumU.ico + 0.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/FSI.BT.Tools/FrmRadialMenu.xaml b/FSI.BT.Tools/FrmRadialMenu.xaml new file mode 100644 index 0000000..7942c2e --- /dev/null +++ b/FSI.BT.Tools/FrmRadialMenu.xaml @@ -0,0 +1,724 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Eplan + + + + + + + + + + + + + Tools + + + + + + + + + + + + + Siemens + + + + + + + + + + + + + Total CMD + + + + + + + + + + + + + Notepad++ + + + + + + + + + + + + + Links + + + + + + + + + + + + + + + + + + + + + + + + + + + Eplan + + + + + + + + + + + + + Projekt-Auswahl + + + + + + + + + + + + + PDF-Auswahl + + + + + + + + + + + + + PDF-Mgt. + + + + + + + + + + + + + + + + + + + + + + + + + + + Ver-/Entschlüsseln + + + + + + + + + + + + + RemoteDesktop + + + + + + + + + + + + + Visual Studio + + + + + + + + + + + + + Visual StudioCode + + + + + + + + + + + + + + TeXstudio + + + + + + + + + + + + + StarterCSV-Exporter + + + + + + + + + + + + + + + + + + + + + + + + + + + Step 7Classic + + + + + + + + + + + + + TIA V13 + + + + + + + + + + + + + TIA V14 + + + + + + + + + + + + + TIA V15 + + + + + + + + + + + + + TIA V16 + + + + + + + + + + + + + TIA V17 + + + + + + + + + + + + + Starter + + + + + + + + + + + + + + + + + + + + + + + + + + + AnlagenLinks + + + + + + + + + + + + + Infor + + + + + + + + + + + + + + ZentralerWebserver + + + + + + + + + + + + + Schichtbuch + + + + + + + + + + + + + SPSÄnderungen + + + + + + + + + + + + + FSIGitea + + + + + + + + + + + + + FSIWikipedia + + + + + + + + + + + + + + + + + + + + + + + + + + ZentralerWebserver + + + + + + + + + + + + + PL1 PLS + + + + + + + + + + + + + PL2 PLS + + + + + + + + + + + + + PL2 ALS + + + + + + + + + + + + + PL3 PLS + + + + + + + + + + diff --git a/FSI.BT.Tools/FrmRadialMenu.xaml.cs b/FSI.BT.Tools/FrmRadialMenu.xaml.cs new file mode 100644 index 0000000..7035495 --- /dev/null +++ b/FSI.BT.Tools/FrmRadialMenu.xaml.cs @@ -0,0 +1,266 @@ +using System; +using System.ComponentModel; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows; +using System.Windows.Forms; +using System.Windows.Input; + +namespace FSI.BT.Tools +{ + /// + /// Interaction logic for MainWindow.xaml + /// + public partial class FrmRadialMenu : Window, INotifyPropertyChanged + { + + public FrmRadialMenu() + { + InitializeComponent(); + DataContext = this; + _isOpenHome = true; + tbversion.Text = "v" + Assembly.GetExecutingAssembly().GetName().Version.Major + "." + Assembly.GetExecutingAssembly().GetName().Version.Minor; + } + + #region Home + private bool _isOpenHome = true; + public bool IsOpenHome + { + get + { + return _isOpenHome; + } + set + { + _isOpenHome = value; + RaisePropertyChanged(); + } + } + + public ICommand CloseRadialMenuHome + { + get + { + return new RelayCommand(() => Visibility = Visibility.Hidden); + } + } + + public ICommand OpenRadialMenuHome + { + get + { + return new RelayCommand(() => + { + IsOpenHome = true; + IsOpenEpl = + IsOpenTools = + IsOpenSie = + IsOpenLinks = false; + }); + } + } + #endregion + + #region Epl + + private bool _isOpenEpl = false; + public bool IsOpenEpl + { + get + { + return _isOpenEpl; + } + set + { + _isOpenEpl = value; + RaisePropertyChanged(); + } + } + + public ICommand OpenRadialMenuEpl + { + get + { + return new RelayCommand(() => + { + IsOpenEpl = true; + IsOpenHome = false; + }); + } + } + + #endregion + + #region Tools + + private bool _isOpenTools = false; + public bool IsOpenTools + { + get + { + return _isOpenTools; + } + set + { + _isOpenTools = value; + RaisePropertyChanged(); + } + } + + public ICommand OpenRadialMenuTools + { + get + { + return new RelayCommand(() => + { + IsOpenTools = true; + IsOpenHome = false; + }); + } + } + + #endregion + + #region Siemens + + private bool _isOpenSie = false; + public bool IsOpenSie + { + get + { + return _isOpenSie; + } + set + { + _isOpenSie = value; + RaisePropertyChanged(); + } + } + + public ICommand OpenRadialMenuSie + { + get + { + return new RelayCommand(() => + { + IsOpenSie = true; + IsOpenHome = false; + }); + } + } + + #endregion + + #region Links + + private bool _isOpenLinks = false; + public bool IsOpenLinks + { + get + { + return _isOpenLinks; + } + set + { + _isOpenLinks = value; + RaisePropertyChanged(); + } + } + + public ICommand OpenRadialMenuLinks + { + get + { + return new RelayCommand(() => + { + IsOpenLinks = true; + IsOpenPlantLinks = + IsOpenHome = false; + }); + } + } + + #endregion + + #region Anlagen Links + + private bool _isOpenPlantLinks = false; + public bool IsOpenPlantLinks + { + get + { + return _isOpenPlantLinks; + } + set + { + _isOpenPlantLinks = value; + RaisePropertyChanged(); + } + } + + public ICommand OpenRadialMenuPlantLinks + { + get + { + return new RelayCommand(() => + { + IsOpenPlantLinks = true; + IsOpenLinks = false; + }); + } + } + + #endregion + + public event PropertyChangedEventHandler PropertyChanged; + + void RaisePropertyChanged([CallerMemberName] string? propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + private void Window_Deactivated(object sender, EventArgs e) + { + Visibility = Visibility.Hidden; + + IsOpenHome = true; + IsOpenEpl = + IsOpenTools = + IsOpenSie = + IsOpenLinks = + IsOpenPlantLinks = false; + } + + + private void RadialMenuItem_Click(object sender, RoutedEventArgs e) + { + int left = Convert.ToInt32(GetActualLeft() + this.ActualWidth / 2); + int top = Convert.ToInt32(GetActuaTop() + this.ActualHeight / 2); + System.Windows.Forms.Cursor.Position = new System.Drawing.Point(left, top); + } + + + private double GetActualLeft() + { + if (this.WindowState == WindowState.Maximized) + { + var leftField = typeof(Window).GetField("_actualLeft", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + return (double)leftField.GetValue(this); + } + else + return this.Left; + } + + private double GetActuaTop() + { + if (this.WindowState == WindowState.Maximized) + { + var topField = typeof(Window).GetField("_actualTop", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + return (double)topField.GetValue(this); + } + else + return this.Top; + } + } +} diff --git a/FSI.BT.Tools/Global.cs b/FSI.BT.Tools/Global.cs new file mode 100644 index 0000000..be7c319 --- /dev/null +++ b/FSI.BT.Tools/Global.cs @@ -0,0 +1,179 @@ +using Hardcodet.Wpf.TaskbarNotification; + +namespace FSI.BT.Tools +{ + internal static class Global + { + + public static FrmRadialMenu? FrmRadialMenu { get; set; } + + public static TaskbarIcon? TaskbarIcon { get; set; } + + + public static bool UserRights { get; set; } + + public static bool AdminRights { get; set; } + + public static class Settings + { + public static class General + { + public static string DeEnCryptPasswort + { + get + { + return FSI.Lib.Settings.Setting("DeEnCryptPasswort", Lib.Settings.Mode.ExeSetttings); + } + } + public static string TimeStampFormat + { + get + { + return FSI.Lib.Settings.Setting("TimeStamp.Format", Lib.Settings.Mode.ExeSetttings); + } + } + } + + public static class Apps + { + public static string SieSimaticManagerExe + { + get { return FSI.Lib.Settings.Setting("SIE.Simatic.Manager.Exe", FSI.Lib.Settings.Mode.ExeSetttings); } + } + + public static string SieTiaV13Exe + { + get { return FSI.Lib.Settings.Setting("SIE.TIA.V13.Exe", FSI.Lib.Settings.Mode.ExeSetttings); } + } + + public static string SieTiaV14Exe + { + get { return FSI.Lib.Settings.Setting("SIE.TIA.V14.Exe", FSI.Lib.Settings.Mode.ExeSetttings); } + } + + public static string SieTiaV15Exe + { + get { return FSI.Lib.Settings.Setting("SIE.TIA.V15.Exe", FSI.Lib.Settings.Mode.ExeSetttings); } + } + + public static string SieTiaV16Exe + { + get { return FSI.Lib.Settings.Setting("SIE.TIA.V16.Exe", FSI.Lib.Settings.Mode.ExeSetttings); } + } + + public static string SieTiaV17Exe + { + get { return FSI.Lib.Settings.Setting("SIE.TIA.V17.Exe", FSI.Lib.Settings.Mode.ExeSetttings); } + } + + public static string SieTiaVStarterExe + { + get { return FSI.Lib.Settings.Setting("SIE.Starter.Exe", FSI.Lib.Settings.Mode.ExeSetttings); } + } + + public static class Epl + { + public static string Exe + { + get { return FSI.Lib.Settings.Setting("Epl.Exe", FSI.Lib.Settings.Mode.ExeSetttings); } + } + + public static string Arguments + { + get { return "/Variant:\"Electric P8\""; } + } + } + + public static string NppExe + { + get { return FSI.Lib.Settings.Setting("Npp.Exe", FSI.Lib.Settings.Mode.ExeSetttings); } + } + + public static string TotalCmdExe + { + get { return FSI.Lib.Settings.Setting("TotalCmd.Exe", FSI.Lib.Settings.Mode.ExeSetttings); } + } + + public static string TeXstudioExe + { + get { return FSI.Lib.Settings.Setting("TeXstudio.Exe", FSI.Lib.Settings.Mode.ExeSetttings); } + } + + public static string TeXstudioPath + { + get { return FSI.Lib.Settings.Setting("TeXstudio.Path", FSI.Lib.Settings.Mode.ExeSetttings); } + } + + public static string VsExe + { + get { return FSI.Lib.Settings.Setting("VS.Exe", FSI.Lib.Settings.Mode.ExeSetttings); } + } + + + public static string VsCodeExe + { + get { return FSI.Lib.Settings.Setting("VS.Code.Exe", FSI.Lib.Settings.Mode.ExeSetttings); } + } + + + public static string RdpExe + { + get { return FSI.Lib.Settings.Setting("Rdp.Exe", FSI.Lib.Settings.Mode.ExeSetttings); } + } + + } + + public static class Urls + { + public static string ZentralWeb + { + get { return FSI.Lib.Settings.Setting("ZentralWeb.Url", FSI.Lib.Settings.Mode.ExeSetttings); } + } + + public static string Schichtbuch + { + get { return FSI.Lib.Settings.Setting("Schichtbuch.Url", FSI.Lib.Settings.Mode.ExeSetttings); } + } + + public static string SPS + { + get { return FSI.Lib.Settings.Setting("SPS.Url", FSI.Lib.Settings.Mode.ExeSetttings); } + } + + public static string Pl1Pls + { + get { return FSI.Lib.Settings.Setting("PL1.Pls.Url", FSI.Lib.Settings.Mode.ExeSetttings); } + } + + public static string Pl2Pls + { + get { return FSI.Lib.Settings.Setting("PL2.Pls.Url", FSI.Lib.Settings.Mode.ExeSetttings); } + } + + public static string Pl2Als + { + get { return FSI.Lib.Settings.Setting("PL2.Als.Url", FSI.Lib.Settings.Mode.ExeSetttings); } + } + + public static string Pl3Pls + { + get { return FSI.Lib.Settings.Setting("PL3.Pls.Url", FSI.Lib.Settings.Mode.ExeSetttings); } + } + public static string Gitea + { + get { return FSI.Lib.Settings.Setting("FSI.Gitea.Url", FSI.Lib.Settings.Mode.ExeSetttings); } + } + + public static string Wiki + { + get { return FSI.Lib.Settings.Setting("FSI.Gitea.Url", FSI.Lib.Settings.Mode.ExeSetttings); } + } + + public static string Erp + { + get { return FSI.Lib.Settings.Setting("Erp.Url", FSI.Lib.Settings.Mode.ExeSetttings); } + } + } + } + } +} diff --git a/FSI.BT.Tools/Icons/1087815.png b/FSI.BT.Tools/Icons/1087815.png new file mode 100644 index 0000000..54b347d Binary files /dev/null and b/FSI.BT.Tools/Icons/1087815.png differ diff --git a/FSI.BT.Tools/Icons/Circuit.png b/FSI.BT.Tools/Icons/Circuit.png new file mode 100644 index 0000000..ac21603 Binary files /dev/null and b/FSI.BT.Tools/Icons/Circuit.png differ diff --git a/FSI.BT.Tools/Icons/Close.png b/FSI.BT.Tools/Icons/Close.png new file mode 100644 index 0000000..7b03ffc Binary files /dev/null and b/FSI.BT.Tools/Icons/Close.png differ diff --git a/FSI.BT.Tools/Icons/Crypt.jpg b/FSI.BT.Tools/Icons/Crypt.jpg new file mode 100644 index 0000000..50cd779 Binary files /dev/null and b/FSI.BT.Tools/Icons/Crypt.jpg differ diff --git a/FSI.BT.Tools/Icons/EplP8.png b/FSI.BT.Tools/Icons/EplP8.png new file mode 100644 index 0000000..23ea2fe Binary files /dev/null and b/FSI.BT.Tools/Icons/EplP8.png differ diff --git a/FSI.BT.Tools/Icons/Erp.png b/FSI.BT.Tools/Icons/Erp.png new file mode 100644 index 0000000..08a5d63 Binary files /dev/null and b/FSI.BT.Tools/Icons/Erp.png differ diff --git a/FSI.BT.Tools/Icons/FU.png b/FSI.BT.Tools/Icons/FU.png new file mode 100644 index 0000000..17867f7 Binary files /dev/null and b/FSI.BT.Tools/Icons/FU.png differ diff --git a/FSI.BT.Tools/Icons/FondiumU.ico b/FSI.BT.Tools/Icons/FondiumU.ico new file mode 100644 index 0000000..7d74de6 Binary files /dev/null and b/FSI.BT.Tools/Icons/FondiumU.ico differ diff --git a/FSI.BT.Tools/Icons/Gitea.png b/FSI.BT.Tools/Icons/Gitea.png new file mode 100644 index 0000000..99d5bad Binary files /dev/null and b/FSI.BT.Tools/Icons/Gitea.png differ diff --git a/FSI.BT.Tools/Icons/Info.png b/FSI.BT.Tools/Icons/Info.png new file mode 100644 index 0000000..351dbac Binary files /dev/null and b/FSI.BT.Tools/Icons/Info.png differ diff --git a/FSI.BT.Tools/Icons/Links.png b/FSI.BT.Tools/Icons/Links.png new file mode 100644 index 0000000..d7c42a6 Binary files /dev/null and b/FSI.BT.Tools/Icons/Links.png differ diff --git a/FSI.BT.Tools/Icons/NPP.png b/FSI.BT.Tools/Icons/NPP.png new file mode 100644 index 0000000..0eae6fd Binary files /dev/null and b/FSI.BT.Tools/Icons/NPP.png differ diff --git a/FSI.BT.Tools/Icons/Pdf.png b/FSI.BT.Tools/Icons/Pdf.png new file mode 100644 index 0000000..40ba833 Binary files /dev/null and b/FSI.BT.Tools/Icons/Pdf.png differ diff --git a/FSI.BT.Tools/Icons/Plc.jpg b/FSI.BT.Tools/Icons/Plc.jpg new file mode 100644 index 0000000..661a929 Binary files /dev/null and b/FSI.BT.Tools/Icons/Plc.jpg differ diff --git a/FSI.BT.Tools/Icons/Rdp.png b/FSI.BT.Tools/Icons/Rdp.png new file mode 100644 index 0000000..5c0dbc9 Binary files /dev/null and b/FSI.BT.Tools/Icons/Rdp.png differ diff --git a/FSI.BT.Tools/Icons/SIE.png b/FSI.BT.Tools/Icons/SIE.png new file mode 100644 index 0000000..9fc4987 Binary files /dev/null and b/FSI.BT.Tools/Icons/SIE.png differ diff --git a/FSI.BT.Tools/Icons/STEP7.png b/FSI.BT.Tools/Icons/STEP7.png new file mode 100644 index 0000000..8a8a943 Binary files /dev/null and b/FSI.BT.Tools/Icons/STEP7.png differ diff --git a/FSI.BT.Tools/Icons/TIAv13.jpg b/FSI.BT.Tools/Icons/TIAv13.jpg new file mode 100644 index 0000000..a3e24e8 Binary files /dev/null and b/FSI.BT.Tools/Icons/TIAv13.jpg differ diff --git a/FSI.BT.Tools/Icons/TIAv14.jpg b/FSI.BT.Tools/Icons/TIAv14.jpg new file mode 100644 index 0000000..7d0f710 Binary files /dev/null and b/FSI.BT.Tools/Icons/TIAv14.jpg differ diff --git a/FSI.BT.Tools/Icons/TIAv15.jpg b/FSI.BT.Tools/Icons/TIAv15.jpg new file mode 100644 index 0000000..225e3a0 Binary files /dev/null and b/FSI.BT.Tools/Icons/TIAv15.jpg differ diff --git a/FSI.BT.Tools/Icons/TIAv16.jpg b/FSI.BT.Tools/Icons/TIAv16.jpg new file mode 100644 index 0000000..8a71ca3 Binary files /dev/null and b/FSI.BT.Tools/Icons/TIAv16.jpg differ diff --git a/FSI.BT.Tools/Icons/TIAv17.jpg b/FSI.BT.Tools/Icons/TIAv17.jpg new file mode 100644 index 0000000..f249c9c Binary files /dev/null and b/FSI.BT.Tools/Icons/TIAv17.jpg differ diff --git a/FSI.BT.Tools/Icons/TeXstudio.png b/FSI.BT.Tools/Icons/TeXstudio.png new file mode 100644 index 0000000..c1ca526 Binary files /dev/null and b/FSI.BT.Tools/Icons/TeXstudio.png differ diff --git a/FSI.BT.Tools/Icons/Tools.png b/FSI.BT.Tools/Icons/Tools.png new file mode 100644 index 0000000..7b154d5 Binary files /dev/null and b/FSI.BT.Tools/Icons/Tools.png differ diff --git a/FSI.BT.Tools/Icons/TotalCmd.jfif b/FSI.BT.Tools/Icons/TotalCmd.jfif new file mode 100644 index 0000000..5df5345 Binary files /dev/null and b/FSI.BT.Tools/Icons/TotalCmd.jfif differ diff --git a/FSI.BT.Tools/Icons/Vs.png b/FSI.BT.Tools/Icons/Vs.png new file mode 100644 index 0000000..2763fc5 Binary files /dev/null and b/FSI.BT.Tools/Icons/Vs.png differ diff --git a/FSI.BT.Tools/Icons/VsCode.png b/FSI.BT.Tools/Icons/VsCode.png new file mode 100644 index 0000000..ad4dfb6 Binary files /dev/null and b/FSI.BT.Tools/Icons/VsCode.png differ diff --git a/FSI.BT.Tools/NotifyIconResources.xaml b/FSI.BT.Tools/NotifyIconResources.xaml new file mode 100644 index 0000000..e728892 --- /dev/null +++ b/FSI.BT.Tools/NotifyIconResources.xaml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/FSI.BT.Tools/RelayCommand.cs b/FSI.BT.Tools/RelayCommand.cs new file mode 100644 index 0000000..3ddc443 --- /dev/null +++ b/FSI.BT.Tools/RelayCommand.cs @@ -0,0 +1,46 @@ +using System; +using System.Windows.Input; + +namespace FSI.BT.Tools +{ + public class RelayCommand : ICommand + { + private Action action; + + private Func condition; + + public event EventHandler CanExecuteChanged + { + add { CommandManager.RequerySuggested += value; } + remove { CommandManager.RequerySuggested -= value; } + } + + public RelayCommand(Action action) + { + this.action = action; + condition = () => true; + } + + public RelayCommand(Action action, bool condition) + { + this.action = action; + this.condition = () => condition; + } + + public RelayCommand(Action action, Func condition) + { + this.action = action; + this.condition = condition; + } + + public bool CanExecute(object parameter) + { + return condition.Invoke(); + } + + void ICommand.Execute(object parameter) + { + action.Invoke(); + } + } +} diff --git a/FSI.BT.Tools/ToolTip.xaml b/FSI.BT.Tools/ToolTip.xaml new file mode 100644 index 0000000..fc8e63e --- /dev/null +++ b/FSI.BT.Tools/ToolTip.xaml @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/FSI.BT.Tools/ToolTip.xaml.cs b/FSI.BT.Tools/ToolTip.xaml.cs new file mode 100644 index 0000000..12badad --- /dev/null +++ b/FSI.BT.Tools/ToolTip.xaml.cs @@ -0,0 +1,116 @@ +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Input; +using Hardcodet.Wpf.TaskbarNotification; + +namespace FSI.BT.Tools +{ + /// + /// Interaktionslogik für ToolTip.xaml + /// + public partial class ToolTip : UserControl + { + private bool isClosing = false; + + #region BalloonText dependency property + + /// + /// Description + /// + public static readonly DependencyProperty BalloonTextProperty = + DependencyProperty.Register(nameof(BalloonText), + typeof(string), + typeof(ToolTip), + new FrameworkPropertyMetadata(string.Empty)); + + /// + /// A property wrapper for the + /// dependency property:
+ /// Description + ///
+ public string BalloonText + { + get { return (string)GetValue(BalloonTextProperty); } + set { SetValue(BalloonTextProperty, value); } + } + + /// + /// Description + /// + public static readonly DependencyProperty BalloonDescProperty = + DependencyProperty.Register(nameof(BalloonDesc), + typeof(string), + typeof(ToolTip), + new FrameworkPropertyMetadata(string.Empty)); + + /// + /// A property wrapper for the + /// dependency property:
+ /// Description + ///
+ public string BalloonDesc + { + get { return (string)GetValue(BalloonDescProperty); } + set { SetValue(BalloonDescProperty, value); } + } + + #endregion + public ToolTip() + { + InitializeComponent(); + TaskbarIcon.AddBalloonClosingHandler(this, OnBalloonClosing); + } + /// + /// By subscribing to the + /// and setting the "Handled" property to true, we suppress the popup + /// from being closed in order to display the custom fade-out animation. + /// + private void OnBalloonClosing(object sender, RoutedEventArgs e) + { + e.Handled = true; //suppresses the popup from being closed immediately + isClosing = true; + } + + + /// + /// Resolves the that displayed + /// the balloon and requests a close action. + /// + private void imgClose_MouseDown(object sender, MouseButtonEventArgs e) + { + //the tray icon assigned this attached property to simplify access + TaskbarIcon taskbarIcon = TaskbarIcon.GetParentTaskbarIcon(this); + taskbarIcon.CloseBalloon(); + } + + /// + /// If the users hovers over the balloon, we don't close it. + /// + private void grid_MouseEnter(object sender, MouseEventArgs e) + { + //if we're already running the fade-out animation, do not interrupt anymore + //(makes things too complicated for the sample) + if (isClosing) return; + + //the tray icon assigned this attached property to simplify access + TaskbarIcon taskbarIcon = TaskbarIcon.GetParentTaskbarIcon(this); + taskbarIcon.ResetBalloonCloseTimer(); + } + + /// + /// Closes the popup once the fade-out animation completed. + /// The animation was triggered in XAML through the attached + /// BalloonClosing event. + /// + private void OnFadeOutCompleted(object sender, EventArgs e) + { + Popup pp = (Popup)Parent; + pp.IsOpen = false; + } + + } + +} + diff --git a/FSI.BT.Tools/Utils/Icons.xaml b/FSI.BT.Tools/Utils/Icons.xaml new file mode 100644 index 0000000..76b5f97 --- /dev/null +++ b/FSI.BT.Tools/Utils/Icons.xaml @@ -0,0 +1,5136 @@ + + + Black + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/NHotkey/.gitignore b/NHotkey/.gitignore new file mode 100644 index 0000000..8e7a151 --- /dev/null +++ b/NHotkey/.gitignore @@ -0,0 +1,400 @@ +# ---> VisualStudio +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml + diff --git a/NHotkey/Directory.Build.props b/NHotkey/Directory.Build.props new file mode 100644 index 0000000..093c6af --- /dev/null +++ b/NHotkey/Directory.Build.props @@ -0,0 +1,22 @@ + + + + $(MSBuildThisFileDirectory)NHotkey.snk + true + + + + Thomas Levesque + https://github.com/thomaslevesque/NHotkey + Apache-2.0 + global;hotkey;windows + true + $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb + + + + + + + + diff --git a/NHotkey/NHotkey.WindowsForms/ExtensionAttribute.cs b/NHotkey/NHotkey.WindowsForms/ExtensionAttribute.cs new file mode 100644 index 0000000..47b2b59 --- /dev/null +++ b/NHotkey/NHotkey.WindowsForms/ExtensionAttribute.cs @@ -0,0 +1,10 @@ +// ReSharper disable once CheckNamespace +namespace System.Runtime.CompilerServices +{ + // Enables extension methods in assembly that targets .NET 2.0 + + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Method)] + internal sealed class ExtensionAttribute : Attribute + { + } +} \ No newline at end of file diff --git a/NHotkey/NHotkey.WindowsForms/Extensions.cs b/NHotkey/NHotkey.WindowsForms/Extensions.cs new file mode 100644 index 0000000..933ecdc --- /dev/null +++ b/NHotkey/NHotkey.WindowsForms/Extensions.cs @@ -0,0 +1,12 @@ +using System.Windows.Forms; + +namespace NHotkey.WindowsForms +{ + static class Extensions + { + public static bool HasFlag(this Keys keys, Keys flag) + { + return (keys & flag) == flag; + } + } +} diff --git a/NHotkey/NHotkey.WindowsForms/HotkeyManager.cs b/NHotkey/NHotkey.WindowsForms/HotkeyManager.cs new file mode 100644 index 0000000..445771d --- /dev/null +++ b/NHotkey/NHotkey.WindowsForms/HotkeyManager.cs @@ -0,0 +1,91 @@ +using System; +using System.Windows.Forms; + +namespace NHotkey.WindowsForms +{ + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Design", + "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", + Justification = "This is a singleton; disposing it would break it")] + public class HotkeyManager : HotkeyManagerBase + { + #region Singleton implementation + + public static HotkeyManager Current { get { return LazyInitializer.Instance; } } + + private static class LazyInitializer + { + static LazyInitializer() { } + public static readonly HotkeyManager Instance = new HotkeyManager(); + } + + #endregion + + // ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable + private readonly MessageWindow _messageWindow; + + private HotkeyManager() + { + _messageWindow = new MessageWindow(this); + SetHwnd(_messageWindow.Handle); + } + + public void AddOrReplace(string name, Keys keys, bool noRepeat, EventHandler handler) + { + var flags = GetFlags(keys, noRepeat); + var vk = unchecked((uint)(keys & ~Keys.Modifiers)); + AddOrReplace(name, vk, flags, handler); + } + + public void AddOrReplace(string name, Keys keys, EventHandler handler) + { + AddOrReplace(name, keys, false, handler); + } + + private static HotkeyFlags GetFlags(Keys hotkey, bool noRepeat) + { + var noMod = hotkey & ~Keys.Modifiers; + var flags = HotkeyFlags.None; + if (hotkey.HasFlag(Keys.Alt)) + flags |= HotkeyFlags.Alt; + if (hotkey.HasFlag(Keys.Control)) + flags |= HotkeyFlags.Control; + if (hotkey.HasFlag(Keys.Shift)) + flags |= HotkeyFlags.Shift; + if (noMod == Keys.LWin || noMod == Keys.RWin) + flags |= HotkeyFlags.Windows; + if (noRepeat) + flags |= HotkeyFlags.NoRepeat; + return flags; + } + + class MessageWindow : ContainerControl + { + private readonly HotkeyManager _hotkeyManager; + + public MessageWindow(HotkeyManager hotkeyManager) + { + _hotkeyManager = hotkeyManager; + } + + protected override CreateParams CreateParams + { + get + { + var parameters = base.CreateParams; + parameters.Parent = HwndMessage; + return parameters; + } + } + + protected override void WndProc(ref Message m) + { + bool handled = false; + Hotkey hotkey; + m.Result = _hotkeyManager.HandleHotkeyMessage(Handle, m.Msg, m.WParam, m.LParam, ref handled, out hotkey); + if (!handled) + base.WndProc(ref m); + } + } + } +} diff --git a/NHotkey/NHotkey.WindowsForms/NHotkey.WindowsForms.csproj b/NHotkey/NHotkey.WindowsForms/NHotkey.WindowsForms.csproj new file mode 100644 index 0000000..4c1fa90 --- /dev/null +++ b/NHotkey/NHotkey.WindowsForms/NHotkey.WindowsForms.csproj @@ -0,0 +1,12 @@ + + + net40;net45;netcoreapp3.0 + true + + + A managed library to handle global hotkeys in Windows Forms applications. This package contains the concrete HotkeyManager implementation for Windows Forms. + + + + + \ No newline at end of file diff --git a/NHotkey/NHotkey.Wpf/Extensions.cs b/NHotkey/NHotkey.Wpf/Extensions.cs new file mode 100644 index 0000000..41d98da --- /dev/null +++ b/NHotkey/NHotkey.Wpf/Extensions.cs @@ -0,0 +1,17 @@ +using System.Windows.Input; + +namespace NHotkey.Wpf +{ + static class Extensions + { + public static bool HasFlag(this ModifierKeys modifiers, ModifierKeys flag) + { + return (modifiers & flag) == flag; + } + + public static bool HasFlag(this HotkeyFlags flags, HotkeyFlags flag) + { + return (flags & flag) == flag; + } + } +} diff --git a/NHotkey/NHotkey.Wpf/HotkeyAlreadyRegisteredEventArgs.cs b/NHotkey/NHotkey.Wpf/HotkeyAlreadyRegisteredEventArgs.cs new file mode 100644 index 0000000..20b0308 --- /dev/null +++ b/NHotkey/NHotkey.Wpf/HotkeyAlreadyRegisteredEventArgs.cs @@ -0,0 +1,19 @@ +using System; + +namespace NHotkey.Wpf +{ + public class HotkeyAlreadyRegisteredEventArgs : EventArgs + { + private readonly string _name; + + public HotkeyAlreadyRegisteredEventArgs(string name) + { + _name = name; + } + + public string Name + { + get { return _name; } + } + } +} diff --git a/NHotkey/NHotkey.Wpf/HotkeyManager.cs b/NHotkey/NHotkey.Wpf/HotkeyManager.cs new file mode 100644 index 0000000..90f4a2e --- /dev/null +++ b/NHotkey/NHotkey.Wpf/HotkeyManager.cs @@ -0,0 +1,249 @@ +using System; +using System.ComponentModel; +using System.Windows; +using System.Windows.Input; +using System.Windows.Interop; + +namespace NHotkey.Wpf +{ + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Design", + "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", + Justification = "This is a singleton; disposing it would break it")] + public class HotkeyManager : HotkeyManagerBase + { + #region Singleton implementation + + public static HotkeyManager Current { get { return LazyInitializer.Instance; } } + + private static class LazyInitializer + { + static LazyInitializer() { } + public static readonly HotkeyManager Instance = new HotkeyManager(); + } + + public void AddOrReplace(string v, object incrementKeys, object onIncrement) + { + throw new NotImplementedException(); + } + + #endregion + + #region Attached property for KeyBindings + + [AttachedPropertyBrowsableForType(typeof(KeyBinding))] + public static bool GetRegisterGlobalHotkey(KeyBinding binding) + { + return (bool)binding.GetValue(RegisterGlobalHotkeyProperty); + } + + public static void SetRegisterGlobalHotkey(KeyBinding binding, bool value) + { + binding.SetValue(RegisterGlobalHotkeyProperty, value); + } + + public static readonly DependencyProperty RegisterGlobalHotkeyProperty = + DependencyProperty.RegisterAttached( + "RegisterGlobalHotkey", + typeof(bool), + typeof(HotkeyManager), + new PropertyMetadata( + false, + RegisterGlobalHotkeyPropertyChanged)); + + private static void RegisterGlobalHotkeyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var keyBinding = d as KeyBinding; + if (keyBinding == null) + return; + + bool oldValue = (bool) e.OldValue; + bool newValue = (bool) e.NewValue; + + if (DesignerProperties.GetIsInDesignMode(d)) + return; + + if (oldValue && !newValue) + { + Current.RemoveKeyBinding(keyBinding); + } + else if (newValue && !oldValue) + { + Current.AddKeyBinding(keyBinding); + } + } + + #endregion + + #region HotkeyAlreadyRegistered event + + public static event EventHandler HotkeyAlreadyRegistered; + + private static void OnHotkeyAlreadyRegistered(string name) + { + var handler = HotkeyAlreadyRegistered; + if (handler != null) + handler(null, new HotkeyAlreadyRegisteredEventArgs(name)); + } + + #endregion + + // ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable + private readonly HwndSource _source; + private readonly WeakReferenceCollection _keyBindings; + + private HotkeyManager() + { + _keyBindings = new WeakReferenceCollection(); + + var parameters = new HwndSourceParameters("Hotkey sink") + { + HwndSourceHook = HandleMessage, + ParentWindow = HwndMessage + }; + _source = new HwndSource(parameters); + SetHwnd(_source.Handle); + } + + public void AddOrReplace(string name, KeyGesture gesture, EventHandler handler) + { + AddOrReplace(name, gesture, false, handler); + } + + public void AddOrReplace(string name, KeyGesture gesture, bool noRepeat, EventHandler handler) + { + AddOrReplace(name, gesture.Key, gesture.Modifiers, noRepeat, handler); + } + + public void AddOrReplace(string name, Key key, ModifierKeys modifiers, EventHandler handler) + { + AddOrReplace(name, key, modifiers, false, handler); + } + + public void AddOrReplace(string name, Key key, ModifierKeys modifiers, bool noRepeat, EventHandler handler) + { + var flags = GetFlags(modifiers, noRepeat); + var vk = (uint)KeyInterop.VirtualKeyFromKey(key); + AddOrReplace(name, vk, flags, handler); + } + + private static HotkeyFlags GetFlags(ModifierKeys modifiers, bool noRepeat) + { + var flags = HotkeyFlags.None; + if (modifiers.HasFlag(ModifierKeys.Shift)) + flags |= HotkeyFlags.Shift; + if (modifiers.HasFlag(ModifierKeys.Control)) + flags |= HotkeyFlags.Control; + if (modifiers.HasFlag(ModifierKeys.Alt)) + flags |= HotkeyFlags.Alt; + if (modifiers.HasFlag(ModifierKeys.Windows)) + flags |= HotkeyFlags.Windows; + if (noRepeat) + flags |= HotkeyFlags.NoRepeat; + return flags; + } + + private static ModifierKeys GetModifiers(HotkeyFlags flags) + { + var modifiers = ModifierKeys.None; + if (flags.HasFlag(HotkeyFlags.Shift)) + modifiers |= ModifierKeys.Shift; + if (flags.HasFlag(HotkeyFlags.Control)) + modifiers |= ModifierKeys.Control; + if (flags.HasFlag(HotkeyFlags.Alt)) + modifiers |= ModifierKeys.Alt; + if (flags.HasFlag(HotkeyFlags.Windows)) + modifiers |= ModifierKeys.Windows; + return modifiers; + } + + private void AddKeyBinding(KeyBinding keyBinding) + { + var gesture = (KeyGesture)keyBinding.Gesture; + string name = GetNameForKeyBinding(gesture); + try + { + AddOrReplace(name, gesture.Key, gesture.Modifiers, null); + _keyBindings.Add(keyBinding); + } + catch (HotkeyAlreadyRegisteredException) + { + OnHotkeyAlreadyRegistered(name); + } + } + + private void RemoveKeyBinding(KeyBinding keyBinding) + { + var gesture = (KeyGesture)keyBinding.Gesture; + string name = GetNameForKeyBinding(gesture); + Remove(name); + _keyBindings.Remove(keyBinding); + } + + private readonly KeyGestureConverter _gestureConverter = new KeyGestureConverter(); + private string GetNameForKeyBinding(KeyGesture gesture) + { + string name = gesture.DisplayString; + if (string.IsNullOrEmpty(name)) + name = _gestureConverter.ConvertToString(gesture); + return name; + } + + private IntPtr HandleMessage(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam, ref bool handled) + { + Hotkey hotkey; + var result = HandleHotkeyMessage(hwnd, msg, wparam, lparam, ref handled, out hotkey); + if (handled) + return result; + + if (hotkey != null) + handled = ExecuteBoundCommand(hotkey); + + return result; + } + + private bool ExecuteBoundCommand(Hotkey hotkey) + { + var key = KeyInterop.KeyFromVirtualKey((int)hotkey.VirtualKey); + var modifiers = GetModifiers(hotkey.Flags); + bool handled = false; + foreach (var binding in _keyBindings) + { + if (binding.Key == key && binding.Modifiers == modifiers) + { + handled |= ExecuteCommand(binding); + } + } + return handled; + } + + private static bool ExecuteCommand(InputBinding binding) + { + var command = binding.Command; + var parameter = binding.CommandParameter; + var target = binding.CommandTarget; + + if (command == null) + return false; + + var routedCommand = command as RoutedCommand; + if (routedCommand != null) + { + if (routedCommand.CanExecute(parameter, target)) + { + routedCommand.Execute(parameter, target); + return true; + } + } + else + { + if (command.CanExecute(parameter)) + { + command.Execute(parameter); + return true; + } + } + return false; + } + } +} diff --git a/NHotkey/NHotkey.Wpf/NHotkey.Wpf.csproj b/NHotkey/NHotkey.Wpf/NHotkey.Wpf.csproj new file mode 100644 index 0000000..6b122cf --- /dev/null +++ b/NHotkey/NHotkey.Wpf/NHotkey.Wpf.csproj @@ -0,0 +1,12 @@ + + + net40;net45;netcoreapp3.0;net6.0-windows + true + + + A managed library to handle global hotkeys in WPF applications. This package contains the concrete HotkeyManager implementation for WPF. + + + + + \ No newline at end of file diff --git a/NHotkey/NHotkey.Wpf/Properties/AssemblyInfo.cs b/NHotkey/NHotkey.Wpf/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..75ceceb --- /dev/null +++ b/NHotkey/NHotkey.Wpf/Properties/AssemblyInfo.cs @@ -0,0 +1,7 @@ +using System.Windows.Markup; + +// Mapping a custom namespace to the standard WPF namespace is usually something to avoid, +// however in this case we're only importing one type (HotkeyManager), and it's unlikely to +// collide with another type in a future version of WPF. So in this case, we do it for the +// sake of simplicity, so that the user doesn't need to map the namespace manually. +[assembly: XmlnsDefinition("http://schemas.microsoft.com/winfx/2006/xaml/presentation", "NHotkey.Wpf")] diff --git a/NHotkey/NHotkey.Wpf/WeakReferenceCollection.cs b/NHotkey/NHotkey.Wpf/WeakReferenceCollection.cs new file mode 100644 index 0000000..18f8b5c --- /dev/null +++ b/NHotkey/NHotkey.Wpf/WeakReferenceCollection.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace NHotkey.Wpf +{ + class WeakReferenceCollection : IEnumerable + where T : class + { + private readonly List _references = new List(); + + public IEnumerator GetEnumerator() + { + var references = _references.ToList(); + foreach (var reference in references) + { + var target = reference.Target; + if (target != null) + yield return (T) target; + } + Trim(); + } + + public void Add(T item) + { + _references.Add(new WeakReference(item)); + } + + public void Remove(T item) + { + _references.RemoveAll(r => (r.Target ?? item) == item); + } + + public void Trim() + { + _references.RemoveAll(r => !r.IsAlive); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/NHotkey/NHotkey.sln b/NHotkey/NHotkey.sln new file mode 100644 index 0000000..41a87f5 --- /dev/null +++ b/NHotkey/NHotkey.sln @@ -0,0 +1,37 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.32112.339 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NHotkey", "NHotkey\NHotkey.csproj", "{6CAB99C4-6F21-4508-9160-CE4DBF540840}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NHotkey.WindowsForms", "NHotkey.WindowsForms\NHotkey.WindowsForms.csproj", "{F0D5CDB4-B74A-419B-9AD6-2A438A268837}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NHotkey.Wpf", "NHotkey.Wpf\NHotkey.Wpf.csproj", "{FC92233F-6F08-45B7-B3EE-7C557582236B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {6CAB99C4-6F21-4508-9160-CE4DBF540840}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6CAB99C4-6F21-4508-9160-CE4DBF540840}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6CAB99C4-6F21-4508-9160-CE4DBF540840}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6CAB99C4-6F21-4508-9160-CE4DBF540840}.Release|Any CPU.Build.0 = Release|Any CPU + {F0D5CDB4-B74A-419B-9AD6-2A438A268837}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F0D5CDB4-B74A-419B-9AD6-2A438A268837}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F0D5CDB4-B74A-419B-9AD6-2A438A268837}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F0D5CDB4-B74A-419B-9AD6-2A438A268837}.Release|Any CPU.Build.0 = Release|Any CPU + {FC92233F-6F08-45B7-B3EE-7C557582236B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FC92233F-6F08-45B7-B3EE-7C557582236B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FC92233F-6F08-45B7-B3EE-7C557582236B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FC92233F-6F08-45B7-B3EE-7C557582236B}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {67220BE9-A1D5-41B9-9D16-177606E9D4D5} + EndGlobalSection +EndGlobal diff --git a/NHotkey/NHotkey.snk b/NHotkey/NHotkey.snk new file mode 100644 index 0000000..a4fdff9 Binary files /dev/null and b/NHotkey/NHotkey.snk differ diff --git a/NHotkey/NHotkey/GlobalSuppressions.cs b/NHotkey/NHotkey/GlobalSuppressions.cs new file mode 100644 index 0000000..2c7734b Binary files /dev/null and b/NHotkey/NHotkey/GlobalSuppressions.cs differ diff --git a/NHotkey/NHotkey/Hotkey.cs b/NHotkey/NHotkey/Hotkey.cs new file mode 100644 index 0000000..6dd22fb --- /dev/null +++ b/NHotkey/NHotkey/Hotkey.cs @@ -0,0 +1,71 @@ +using System; +using System.Runtime.InteropServices; + +namespace NHotkey +{ + internal class Hotkey + { + private static int _nextId; + + private readonly int _id; + private readonly uint _virtualKey; + private readonly HotkeyFlags _flags; + private readonly EventHandler _handler; + + public Hotkey(uint virtualKey, HotkeyFlags flags, EventHandler handler) + { + _id = ++_nextId; + _virtualKey = virtualKey; + _flags = flags; + _handler = handler; + } + + public int Id + { + get { return _id; } + } + + public uint VirtualKey + { + get { return _virtualKey; } + } + + public HotkeyFlags Flags + { + get { return _flags; } + } + + public EventHandler Handler + { + get { return _handler; } + } + + private IntPtr _hwnd; + + public void Register(IntPtr hwnd, string name) + { + if (!NativeMethods.RegisterHotKey(hwnd, _id, _flags, _virtualKey)) + { + var hr = Marshal.GetHRForLastWin32Error(); + var ex = Marshal.GetExceptionForHR(hr); + if ((uint) hr == 0x80070581) + throw new HotkeyAlreadyRegisteredException(name, ex); + throw ex; + } + _hwnd = hwnd; + } + + public void Unregister() + { + if (_hwnd != IntPtr.Zero) + { + if (!NativeMethods.UnregisterHotKey(_hwnd, _id)) + { + var hr = Marshal.GetHRForLastWin32Error(); + throw Marshal.GetExceptionForHR(hr); + } + _hwnd = IntPtr.Zero; + } + } + } +} \ No newline at end of file diff --git a/NHotkey/NHotkey/HotkeyAlreadyRegisteredException.cs b/NHotkey/NHotkey/HotkeyAlreadyRegisteredException.cs new file mode 100644 index 0000000..4ef2903 --- /dev/null +++ b/NHotkey/NHotkey/HotkeyAlreadyRegisteredException.cs @@ -0,0 +1,36 @@ +using System; +using System.Runtime.InteropServices; +using System.Runtime.Serialization; + +namespace NHotkey +{ + [Serializable] + public class HotkeyAlreadyRegisteredException : Exception + { + private readonly string _name; + + public HotkeyAlreadyRegisteredException(string name, Exception inner) : base(inner.Message, inner) + { + _name = name; + HResult = Marshal.GetHRForException(inner); + } + + protected HotkeyAlreadyRegisteredException( + SerializationInfo info, + StreamingContext context) : base(info, context) + { + _name = (string) info.GetValue("_name", typeof (string)); + } + + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + base.GetObjectData(info, context); + info.AddValue("_name", _name); + } + + public string Name + { + get { return _name; } + } + } +} diff --git a/NHotkey/NHotkey/HotkeyEventArgs.cs b/NHotkey/NHotkey/HotkeyEventArgs.cs new file mode 100644 index 0000000..58bfff2 --- /dev/null +++ b/NHotkey/NHotkey/HotkeyEventArgs.cs @@ -0,0 +1,21 @@ +using System; + +namespace NHotkey +{ + public class HotkeyEventArgs : EventArgs + { + private readonly string _name; + + internal HotkeyEventArgs(string name) + { + _name = name; + } + + public string Name + { + get { return _name; } + } + + public bool Handled { get; set; } + } +} \ No newline at end of file diff --git a/NHotkey/NHotkey/HotkeyFlags.cs b/NHotkey/NHotkey/HotkeyFlags.cs new file mode 100644 index 0000000..e350b1f --- /dev/null +++ b/NHotkey/NHotkey/HotkeyFlags.cs @@ -0,0 +1,15 @@ +using System; + +namespace NHotkey +{ + [Flags] + internal enum HotkeyFlags : uint + { + None = 0x0000, + Alt = 0x0001, + Control = 0x0002, + Shift = 0x0004, + Windows = 0x0008, + NoRepeat = 0x4000 + } +} \ No newline at end of file diff --git a/NHotkey/NHotkey/HotkeyManagerBase.cs b/NHotkey/NHotkey/HotkeyManagerBase.cs new file mode 100644 index 0000000..15a85be --- /dev/null +++ b/NHotkey/NHotkey/HotkeyManagerBase.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; + +namespace NHotkey +{ + public abstract class HotkeyManagerBase + { + private readonly Dictionary _hotkeyNames = new Dictionary(); + private readonly Dictionary _hotkeys = new Dictionary(); + private IntPtr _hwnd; + internal static readonly IntPtr HwndMessage = (IntPtr)(-3); + + internal HotkeyManagerBase() + { + } + + internal void AddOrReplace(string name, uint virtualKey, HotkeyFlags flags, EventHandler handler) + { + var hotkey = new Hotkey(virtualKey, flags, handler); + lock (_hotkeys) + { + Remove(name); + _hotkeys.Add(name, hotkey); + _hotkeyNames.Add(hotkey.Id, name); + if (_hwnd != IntPtr.Zero) + hotkey.Register(_hwnd, name); + } + } + + public void Remove(string name) + { + lock (_hotkeys) + { + Hotkey hotkey; + if (_hotkeys.TryGetValue(name, out hotkey)) + { + _hotkeys.Remove(name); + _hotkeyNames.Remove(hotkey.Id); + if (_hwnd != IntPtr.Zero) + hotkey.Unregister(); + } + } + } + + public bool IsEnabled { get; set; } = true; + + internal void SetHwnd(IntPtr hwnd) + { + _hwnd = hwnd; + } + + private const int WmHotkey = 0x0312; + + internal IntPtr HandleHotkeyMessage( + IntPtr hwnd, + int msg, + IntPtr wParam, + IntPtr lParam, + ref bool handled, + out Hotkey hotkey) + { + hotkey = null; + if (IsEnabled && msg == WmHotkey) + { + int id = wParam.ToInt32(); + string name; + if (_hotkeyNames.TryGetValue(id, out name)) + { + hotkey = _hotkeys[name]; + var handler = hotkey.Handler; + if (handler != null) + { + var e = new HotkeyEventArgs(name); + handler(this, e); + handled = e.Handled; + } + } + } + return IntPtr.Zero; + } + } +} diff --git a/NHotkey/NHotkey/NHotkey.csproj b/NHotkey/NHotkey/NHotkey.csproj new file mode 100644 index 0000000..8714b40 --- /dev/null +++ b/NHotkey/NHotkey/NHotkey.csproj @@ -0,0 +1,10 @@ + + + net40;net45;netcoreapp3.0;net6.0-windows + + + + A managed library to handle global hotkeys in Windows Forms and WPF applications. NOTE: this package doesn't contain a concrete HotkeyManager implementation; you should add either the NHotkey.Wpf or NHotkey.WindowsForms package to get one. + + + \ No newline at end of file diff --git a/NHotkey/NHotkey/NativeMethods.cs b/NHotkey/NHotkey/NativeMethods.cs new file mode 100644 index 0000000..ea173a1 --- /dev/null +++ b/NHotkey/NHotkey/NativeMethods.cs @@ -0,0 +1,14 @@ +using System; +using System.Runtime.InteropServices; + +namespace NHotkey +{ + static class NativeMethods + { + [DllImport("user32.dll", SetLastError = true)] + internal static extern bool RegisterHotKey(IntPtr hWnd, int id, HotkeyFlags fsModifiers, uint vk); + + [DllImport("user32.dll", SetLastError = true)] + internal static extern bool UnregisterHotKey(IntPtr hWnd, int id); + } +} \ No newline at end of file diff --git a/NHotkey/NHotkey/Properties/AssemblyInfo.cs b/NHotkey/NHotkey/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..5e8ccad --- /dev/null +++ b/NHotkey/NHotkey/Properties/AssemblyInfo.cs @@ -0,0 +1,4 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("NHotkey.WindowsForms, PublicKey=00240000048000009400000006020000002400005253413100040000010001000d37f28dea0d86462d702c5ff882a20c5701fc1421107b336430c0af082910e58ff42c9030dcfe5739ab99f74b91b11dd4de06bfcd30bc9402beb15f023becb0c6b92ad9496e8e5474f3f5684ed32bf9fb69da84593b8be9bebd86542bd452c5bb77e071bbb2f0024a501ebfa897a62798fe2f2a4e72f250fc9c8277e17db1d5")] +[assembly: InternalsVisibleTo("NHotkey.Wpf, PublicKey=00240000048000009400000006020000002400005253413100040000010001000d37f28dea0d86462d702c5ff882a20c5701fc1421107b336430c0af082910e58ff42c9030dcfe5739ab99f74b91b11dd4de06bfcd30bc9402beb15f023becb0c6b92ad9496e8e5474f3f5684ed32bf9fb69da84593b8be9bebd86542bd452c5bb77e071bbb2f0024a501ebfa897a62798fe2f2a4e72f250fc9c8277e17db1d5")] diff --git a/NHotkey/README.md b/NHotkey/README.md new file mode 100644 index 0000000..975fa2d --- /dev/null +++ b/NHotkey/README.md @@ -0,0 +1,2 @@ +# NHotkey.Wpf + diff --git a/NotifyIconWpf/.gitignore b/NotifyIconWpf/.gitignore new file mode 100644 index 0000000..8e7a151 --- /dev/null +++ b/NotifyIconWpf/.gitignore @@ -0,0 +1,400 @@ +# ---> VisualStudio +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml + diff --git a/NotifyIconWpf/Directory.Build.props b/NotifyIconWpf/Directory.Build.props new file mode 100644 index 0000000..857a792 --- /dev/null +++ b/NotifyIconWpf/Directory.Build.props @@ -0,0 +1,81 @@ + + + net45;net462;net472;netcoreapp3.1;net5.0-windows + latest + true + true + true + $(MSBuildProjectName.Contains('Sample')) + True + embedded + + Copyright (c) 2009-2021 Philipp Sumi + hardcodet.net + Philipp Sumi, Robin Krom, Jan Karger + icon.png + https://github.com/hardcodet/wpf-notifyicon + git + https://github.com/hardcodet/wpf-notifyicon + LICENSE + false + This is an implementation of a NotifyIcon (aka system tray icon or taskbar icon) for the WPF platform. It does not just rely on the Windows Forms NotifyIcon component, but is a purely independent control which leverages several features of the WPF framework in order to display rich tooltips, popups, context menus, and balloon messages. It can be used directly in code or embedded in any XAML file. + +Source code and extensive sample application available at http://www.hardcodet.net/projects/wpf-notifyicon + NotifyIcon (aka system tray icon or taskbar icon) for the WPF platform. + NotifyIcon WPF Tray Notify ToolTip Popup Balloon Toast + + + + true + + + + true + + + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + true + true + true + + + + true + ..\NotifyIconWpf.snk + false + true + + + + + + + + + + + true + + true + + true + + + + + + + + + + + \ No newline at end of file diff --git a/NotifyIconWpf/NotifyIconWpf.sln b/NotifyIconWpf/NotifyIconWpf.sln new file mode 100644 index 0000000..b907537 --- /dev/null +++ b/NotifyIconWpf/NotifyIconWpf.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.32112.339 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NotifyIconWpf", "NotifyIconWpf\NotifyIconWpf.csproj", "{78E6F5E5-11F1-4840-BA37-87522806DEDF}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {78E6F5E5-11F1-4840-BA37-87522806DEDF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {78E6F5E5-11F1-4840-BA37-87522806DEDF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {78E6F5E5-11F1-4840-BA37-87522806DEDF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {78E6F5E5-11F1-4840-BA37-87522806DEDF}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {DE07A147-0D17-4667-A1F7-7076FDFF4AEA} + EndGlobalSection +EndGlobal diff --git a/NotifyIconWpf/NotifyIconWpf.snk b/NotifyIconWpf/NotifyIconWpf.snk new file mode 100644 index 0000000..30e2369 Binary files /dev/null and b/NotifyIconWpf/NotifyIconWpf.snk differ diff --git a/NotifyIconWpf/NotifyIconWpf/BalloonIcon.cs b/NotifyIconWpf/NotifyIconWpf/BalloonIcon.cs new file mode 100644 index 0000000..bda8c85 --- /dev/null +++ b/NotifyIconWpf/NotifyIconWpf/BalloonIcon.cs @@ -0,0 +1,52 @@ +// hardcodet.net NotifyIcon for WPF +// Copyright (c) 2009 - 2020 Philipp Sumi +// Contact and Information: http://www.hardcodet.net +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the Code Project Open License (CPOL); +// either version 1.0 of the License, or (at your option) any later +// version. +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// THIS COPYRIGHT NOTICE MAY NOT BE REMOVED FROM THIS FILE + + +namespace Hardcodet.Wpf.TaskbarNotification +{ + /// + /// Supported icons for the tray's balloon messages. + /// + public enum BalloonIcon + { + /// + /// The balloon message is displayed without an icon. + /// + None, + + /// + /// An information is displayed. + /// + Info, + + /// + /// A warning is displayed. + /// + Warning, + + /// + /// An error is displayed. + /// + Error + } +} \ No newline at end of file diff --git a/NotifyIconWpf/NotifyIconWpf/Diagrams/TaskbarIcon Overview.cd b/NotifyIconWpf/NotifyIconWpf/Diagrams/TaskbarIcon Overview.cd new file mode 100644 index 0000000..b43adff --- /dev/null +++ b/NotifyIconWpf/NotifyIconWpf/Diagrams/TaskbarIcon Overview.cd @@ -0,0 +1,30 @@ + + + + + + + + + + N6qdVIeUdLmQtSUbiJhEGdYRjvJYXlhbEVBDKuPRO5s= + TaskbarIcon.cs + + + + + + + ABAEAAAAAAAAAAABAAAAAAAAAAAAAAAAAIAKAIAAAAA= + PopupActivationMode.cs + + + + + + AAAAAAAAAAAAAQAAAAAAABAAAAAAAAAAAAAAAEEAAAA= + BalloonIcon.cs + + + + \ No newline at end of file diff --git a/NotifyIconWpf/NotifyIconWpf/Interop/AppBarInfo.cs b/NotifyIconWpf/NotifyIconWpf/Interop/AppBarInfo.cs new file mode 100644 index 0000000..c6b94e8 --- /dev/null +++ b/NotifyIconWpf/NotifyIconWpf/Interop/AppBarInfo.cs @@ -0,0 +1,133 @@ +// Some interop code taken from Mike Marshall's AnyForm + +using System; +using System.Drawing; +using System.Runtime.InteropServices; + +namespace Hardcodet.Wpf.TaskbarNotification.Interop +{ + /// + /// This contains the logic to access the location of the app bar and communicate with it. + /// + public class AppBarInfo + { + [DllImport("user32.dll", CharSet = CharSet.Unicode)] + private static extern IntPtr FindWindow(string lpClassName, string lpWindowName); + + [DllImport("shell32.dll")] + private static extern uint SHAppBarMessage(uint dwMessage, ref APPBARDATA data); + + [DllImport("user32.dll")] + private static extern int SystemParametersInfo(uint uiAction, uint uiParam, + IntPtr pvParam, uint fWinIni); + + private const int ABM_GETTASKBARPOS = 0x00000005; + + + private APPBARDATA m_data; + + /// + /// Get on which edge the app bar is located + /// + public ScreenEdge Edge + { + get { return (ScreenEdge) m_data.uEdge; } + } + + /// + /// Get the working area + /// + public Rectangle WorkArea + { + get { return GetRectangle(m_data.rc); } + } + + private Rectangle GetRectangle(RECT rc) + { + return new Rectangle(rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top); + } + + /// + /// Update the location of the appbar with the specified classname and window name. + /// + /// string + /// string + private void GetPosition(string strClassName, string strWindowName) + { + m_data = new APPBARDATA(); + m_data.cbSize = (uint) Marshal.SizeOf(m_data.GetType()); + + IntPtr hWnd = FindWindow(strClassName, strWindowName); + + if (hWnd != IntPtr.Zero) + { + uint uResult = SHAppBarMessage(ABM_GETTASKBARPOS, ref m_data); + + if (uResult != 1) + { + throw new Exception("Failed to communicate with the given AppBar"); + } + } + else + { + throw new Exception("Failed to find an AppBar that matched the given criteria"); + } + } + + /// + /// Updates the system taskbar position + /// + public void GetSystemTaskBarPosition() + { + GetPosition("Shell_TrayWnd", null); + } + + /// + /// A value that specifies an edge of the screen. + /// + public enum ScreenEdge + { + /// + /// Undefined + /// + Undefined = -1, + /// + /// Left edge. + /// + Left = 0, + /// + /// Top edge. + /// + Top = 1, + /// + /// Right edge. + /// + Right = 2, + /// + /// Bottom edge. + /// + Bottom = 3 + } + + [StructLayout(LayoutKind.Sequential)] + private struct APPBARDATA + { + public uint cbSize; + public IntPtr hWnd; + public uint uCallbackMessage; + public uint uEdge; + public RECT rc; + public int lParam; + } + + + [StructLayout(LayoutKind.Sequential)] + private struct RECT + { + public int left; + public int top; + public int right; + public int bottom; + } + } +} \ No newline at end of file diff --git a/NotifyIconWpf/NotifyIconWpf/Interop/BalloonFlags.cs b/NotifyIconWpf/NotifyIconWpf/Interop/BalloonFlags.cs new file mode 100644 index 0000000..c6c8e7e --- /dev/null +++ b/NotifyIconWpf/NotifyIconWpf/Interop/BalloonFlags.cs @@ -0,0 +1,63 @@ +using System; + +namespace Hardcodet.Wpf.TaskbarNotification.Interop +{ + /// + /// Flags that define the icon that is shown on a balloon + /// tooltip. + /// + public enum BalloonFlags + { + /// + /// No icon is displayed. + /// + None = 0x00, + + /// + /// An information icon is displayed. + /// + Info = 0x01, + + /// + /// A warning icon is displayed. + /// + Warning = 0x02, + + /// + /// An error icon is displayed. + /// + Error = 0x03, + + /// + /// Windows XP Service Pack 2 (SP2) and later. + /// Use a custom icon as the title icon. + /// + User = 0x04, + + /// + /// Windows XP (Shell32.dll version 6.0) and later. + /// Do not play the associated sound. Applies only to balloon ToolTips. + /// + NoSound = 0x10, + + /// + /// Windows Vista (Shell32.dll version 6.0.6) and later. The large version + /// of the icon should be used as the balloon icon. This corresponds to the + /// icon with dimensions SM_CXICON x SM_CYICON. If this flag is not set, + /// the icon with dimensions XM_CXSMICON x SM_CYSMICON is used.
+ /// - This flag can be used with all stock icons.
+ /// - Applications that use older customized icons (NIIF_USER with hIcon) must + /// provide a new SM_CXICON x SM_CYICON version in the tray icon (hIcon). These + /// icons are scaled down when they are displayed in the System Tray or + /// System Control Area (SCA).
+ /// - New customized icons (NIIF_USER with hBalloonIcon) must supply an + /// SM_CXICON x SM_CYICON version in the supplied icon (hBalloonIcon). + ///
+ LargeIcon = 0x20, + + /// + /// Windows 7 and later. + /// + RespectQuietTime = 0x80 + } +} \ No newline at end of file diff --git a/NotifyIconWpf/NotifyIconWpf/Interop/IconDataMembers.cs b/NotifyIconWpf/NotifyIconWpf/Interop/IconDataMembers.cs new file mode 100644 index 0000000..c96d478 --- /dev/null +++ b/NotifyIconWpf/NotifyIconWpf/Interop/IconDataMembers.cs @@ -0,0 +1,70 @@ +using System; + +namespace Hardcodet.Wpf.TaskbarNotification.Interop +{ + /// + /// Indicates which members of a structure + /// were set, and thus contain valid data or provide additional information + /// to the ToolTip as to how it should display. + /// + [Flags] + public enum IconDataMembers + { + /// + /// The message ID is set. + /// + Message = 0x01, + + /// + /// The notification icon is set. + /// + Icon = 0x02, + + /// + /// The tooltip is set. + /// + Tip = 0x04, + + /// + /// State information () is set. This + /// applies to both and + /// . + /// + State = 0x08, + + /// + /// The balloon ToolTip is set. Accordingly, the following + /// members are set: , + /// , , + /// and . + /// + Info = 0x10, + + // Internal identifier is set. Reserved, thus commented out. + //Guid = 0x20, + + /// + /// Windows Vista (Shell32.dll version 6.0.6) and later. If the ToolTip + /// cannot be displayed immediately, discard it.
+ /// Use this flag for ToolTips that represent real-time information which + /// would be meaningless or misleading if displayed at a later time. + /// For example, a message that states "Your telephone is ringing."
+ /// This modifies and must be combined with the flag. + ///
+ Realtime = 0x40, + + /// + /// Windows Vista (Shell32.dll version 6.0.6) and later. + /// Use the standard ToolTip. Normally, when uVersion is set + /// to NOTIFYICON_VERSION_4, the standard ToolTip is replaced + /// by the application-drawn pop-up user interface (UI). + /// If the application wants to show the standard tooltip + /// in that case, regardless of whether the on-hover UI is showing, + /// it can specify NIF_SHOWTIP to indicate the standard tooltip + /// should still be shown.
+ /// Note that the NIF_SHOWTIP flag is effective until the next call + /// to Shell_NotifyIcon. + ///
+ UseLegacyToolTips = 0x80 + } +} \ No newline at end of file diff --git a/NotifyIconWpf/NotifyIconWpf/Interop/IconState.cs b/NotifyIconWpf/NotifyIconWpf/Interop/IconState.cs new file mode 100644 index 0000000..7fb7f26 --- /dev/null +++ b/NotifyIconWpf/NotifyIconWpf/Interop/IconState.cs @@ -0,0 +1,22 @@ +namespace Hardcodet.Wpf.TaskbarNotification.Interop +{ + /// + /// The state of the icon - can be set to + /// hide the icon. + /// + public enum IconState + { + /// + /// The icon is visible. + /// + Visible = 0x00, + + /// + /// Hide the icon. + /// + Hidden = 0x01, + + // The icon is shared - currently not supported, thus commented out. + //Shared = 0x02 + } +} \ No newline at end of file diff --git a/NotifyIconWpf/NotifyIconWpf/Interop/MouseEvent.cs b/NotifyIconWpf/NotifyIconWpf/Interop/MouseEvent.cs new file mode 100644 index 0000000..fc70578 --- /dev/null +++ b/NotifyIconWpf/NotifyIconWpf/Interop/MouseEvent.cs @@ -0,0 +1,54 @@ +namespace Hardcodet.Wpf.TaskbarNotification.Interop +{ + /// + /// Event flags for clicked events. + /// + public enum MouseEvent + { + /// + /// The mouse was moved withing the + /// taskbar icon's area. + /// + MouseMove, + + /// + /// The right mouse button was clicked. + /// + IconRightMouseDown, + + /// + /// The left mouse button was clicked. + /// + IconLeftMouseDown, + + /// + /// The right mouse button was released. + /// + IconRightMouseUp, + + /// + /// The left mouse button was released. + /// + IconLeftMouseUp, + + /// + /// The middle mouse button was clicked. + /// + IconMiddleMouseDown, + + /// + /// The middle mouse button was released. + /// + IconMiddleMouseUp, + + /// + /// The taskbar icon was double clicked. + /// + IconDoubleClick, + + /// + /// The balloon tip was clicked. + /// + BalloonToolTipClicked + } +} \ No newline at end of file diff --git a/NotifyIconWpf/NotifyIconWpf/Interop/NotifyCommand.cs b/NotifyIconWpf/NotifyIconWpf/Interop/NotifyCommand.cs new file mode 100644 index 0000000..12e134c --- /dev/null +++ b/NotifyIconWpf/NotifyIconWpf/Interop/NotifyCommand.cs @@ -0,0 +1,41 @@ +namespace Hardcodet.Wpf.TaskbarNotification.Interop +{ + /// + /// Main operations performed on the + /// function. + /// + public enum NotifyCommand + { + /// + /// The taskbar icon is being created. + /// + Add = 0x00, + + /// + /// The settings of the taskbar icon are being updated. + /// + Modify = 0x01, + + /// + /// The taskbar icon is deleted. + /// + Delete = 0x02, + + /// + /// Focus is returned to the taskbar icon. Currently not in use. + /// + SetFocus = 0x03, + + /// + /// Shell32.dll version 5.0 and later only. Instructs the taskbar + /// to behave according to the version number specified in the + /// uVersion member of the structure pointed to by lpdata. + /// This message allows you to specify whether you want the version + /// 5.0 behavior found on Microsoft Windows 2000 systems, or the + /// behavior found on earlier Shell versions. The default value for + /// uVersion is zero, indicating that the original Windows 95 notify + /// icon behavior should be used. + /// + SetVersion = 0x04 + } +} \ No newline at end of file diff --git a/NotifyIconWpf/NotifyIconWpf/Interop/NotifyIconData.cs b/NotifyIconWpf/NotifyIconWpf/Interop/NotifyIconData.cs new file mode 100644 index 0000000..da8231d --- /dev/null +++ b/NotifyIconWpf/NotifyIconWpf/Interop/NotifyIconData.cs @@ -0,0 +1,168 @@ +using System; +using System.Runtime.InteropServices; + +namespace Hardcodet.Wpf.TaskbarNotification.Interop +{ + /// + /// A struct that is submitted in order to configure + /// the taskbar icon. Provides various members that + /// can be configured partially, according to the + /// values of the + /// that were defined. + /// + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct NotifyIconData + { + /// + /// Size of this structure, in bytes. + /// + public uint cbSize; + + /// + /// Handle to the window that receives notification messages associated with an icon in the + /// taskbar status area. The Shell uses hWnd and uID to identify which icon to operate on + /// when Shell_NotifyIcon is invoked. + /// + public IntPtr WindowHandle; + + /// + /// Application-defined identifier of the taskbar icon. The Shell uses hWnd and uID to identify + /// which icon to operate on when Shell_NotifyIcon is invoked. You can have multiple icons + /// associated with a single hWnd by assigning each a different uID. This feature, however + /// is currently not used. + /// + public uint TaskbarIconId; + + /// + /// Flags that indicate which of the other members contain valid data. This member can be + /// a combination of the NIF_XXX constants. + /// + public IconDataMembers ValidMembers; + + /// + /// Application-defined message identifier. The system uses this identifier to send + /// notifications to the window identified in hWnd. + /// + public uint CallbackMessageId; + + /// + /// A handle to the icon that should be displayed. Just + /// Icon.Handle. + /// + public IntPtr IconHandle; + + /// + /// String with the text for a standard ToolTip. It can have a maximum of 64 characters including + /// the terminating NULL. For Version 5.0 and later, szTip can have a maximum of + /// 128 characters, including the terminating NULL. + /// + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] + public string ToolTipText; + + + /// + /// State of the icon. Remember to also set the . + /// + public IconState IconState; + + /// + /// A value that specifies which bits of the state member are retrieved or modified. + /// For example, setting this member to + /// causes only the item's hidden + /// state to be retrieved. + /// + public IconState StateMask; + + /// + /// String with the text for a balloon ToolTip. It can have a maximum of 255 characters. + /// To remove the ToolTip, set the NIF_INFO flag in uFlags and set szInfo to an empty string. + /// + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] + public string BalloonText; + + /// + /// Mainly used to set the version when is invoked + /// with . However, for legacy operations, + /// the same member is also used to set timeouts for balloon ToolTips. + /// + public uint VersionOrTimeout; + + /// + /// String containing a title for a balloon ToolTip. This title appears in boldface + /// above the text. It can have a maximum of 63 characters. + /// + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)] + public string BalloonTitle; + + /// + /// Adds an icon to a balloon ToolTip, which is placed to the left of the title. If the + /// member is zero-length, the icon is not shown. + /// + public BalloonFlags BalloonFlags; + + /// + /// Windows XP (Shell32.dll version 6.0) and later.
+ /// - Windows 7 and later: A registered GUID that identifies the icon. + /// This value overrides uID and is the recommended method of identifying the icon.
+ /// - Windows XP through Windows Vista: Reserved. + ///
+ public Guid TaskbarIconGuid; + + /// + /// Windows Vista (Shell32.dll version 6.0.6) and later. The handle of a customized + /// balloon icon provided by the application that should be used independently + /// of the tray icon. If this member is non-NULL and the + /// flag is set, this icon is used as the balloon icon.
+ /// If this member is NULL, the legacy behavior is carried out. + ///
+ public IntPtr CustomBalloonIconHandle; + + + /// + /// Creates a default data structure that provides + /// a hidden taskbar icon without the icon being set. + /// + /// + /// NotifyIconData + public static NotifyIconData CreateDefault(IntPtr handle) + { + var data = new NotifyIconData(); + + if (Environment.OSVersion.Version.Major >= 6) + { + //use the current size + data.cbSize = (uint) Marshal.SizeOf(data); + } + else + { + //we need to set another size on xp/2003- otherwise certain + //features (e.g. balloon tooltips) don't work. + data.cbSize = 952; // NOTIFYICONDATAW_V3_SIZE + + //set to fixed timeout + data.VersionOrTimeout = 10; + } + + data.WindowHandle = handle; + data.TaskbarIconId = 0x0; + data.CallbackMessageId = WindowMessageSink.CallbackMessageId; + data.VersionOrTimeout = (uint) NotifyIconVersion.Win95; + + data.IconHandle = IntPtr.Zero; + + //hide initially + data.IconState = IconState.Hidden; + data.StateMask = IconState.Hidden; + + //set flags + data.ValidMembers = IconDataMembers.Message + | IconDataMembers.Icon + | IconDataMembers.Tip; + + //reset strings + data.ToolTipText = data.BalloonText = data.BalloonTitle = string.Empty; + + return data; + } + } +} \ No newline at end of file diff --git a/NotifyIconWpf/NotifyIconWpf/Interop/NotifyIconVersion.cs b/NotifyIconWpf/NotifyIconWpf/Interop/NotifyIconVersion.cs new file mode 100644 index 0000000..66fb482 --- /dev/null +++ b/NotifyIconWpf/NotifyIconWpf/Interop/NotifyIconVersion.cs @@ -0,0 +1,27 @@ +namespace Hardcodet.Wpf.TaskbarNotification.Interop +{ + /// + /// The notify icon version that is used. The higher + /// the version, the more capabilities are available. + /// + public enum NotifyIconVersion + { + /// + /// Default behavior (legacy Win95). Expects + /// a size of 488. + /// + Win95 = 0x0, + + /// + /// Behavior representing Win2000 an higher. Expects + /// a size of 504. + /// + Win2000 = 0x3, + + /// + /// Extended tooltip support, which is available for Vista and later. + /// Detailed information about what the different versions do, can be found here + /// + Vista = 0x4 + } +} \ No newline at end of file diff --git a/NotifyIconWpf/NotifyIconWpf/Interop/Point.cs b/NotifyIconWpf/NotifyIconWpf/Interop/Point.cs new file mode 100644 index 0000000..585344f --- /dev/null +++ b/NotifyIconWpf/NotifyIconWpf/Interop/Point.cs @@ -0,0 +1,20 @@ +using System.Runtime.InteropServices; + +namespace Hardcodet.Wpf.TaskbarNotification.Interop +{ + /// + /// Win API struct providing coordinates for a single point. + /// + [StructLayout(LayoutKind.Sequential)] + public struct Point + { + /// + /// X coordinate. + /// + public int X; + /// + /// Y coordinate. + /// + public int Y; + } +} \ No newline at end of file diff --git a/NotifyIconWpf/NotifyIconWpf/Interop/SystemInfo.cs b/NotifyIconWpf/NotifyIconWpf/Interop/SystemInfo.cs new file mode 100644 index 0000000..6edeead --- /dev/null +++ b/NotifyIconWpf/NotifyIconWpf/Interop/SystemInfo.cs @@ -0,0 +1,85 @@ +// hardcodet.net NotifyIcon for WPF +// Copyright (c) 2009 - 2020 Philipp Sumi +// Contact and Information: http://www.hardcodet.net +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the Code Project Open License (CPOL); +// either version 1.0 of the License, or (at your option) any later +// version. +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// THIS COPYRIGHT NOTICE MAY NOT BE REMOVED FROM THIS FILE + +using System.Diagnostics.Contracts; +using System.Windows.Interop; + +namespace Hardcodet.Wpf.TaskbarNotification.Interop +{ + /// + /// This class is a helper for system information, currently to get the DPI factors + /// + public static class SystemInfo + { + /// + /// Make sure the initial value is calculated at the first access + /// + static SystemInfo() + { + UpdateDpiFactors(); + } + + /// + /// This calculates the current DPI values and sets this into the DpiFactorX/DpiFactorY values + /// + internal static void UpdateDpiFactors() + { + using (var source = new HwndSource(new HwndSourceParameters())) + { + if (source.CompositionTarget?.TransformToDevice != null) + { + DpiFactorX = source.CompositionTarget.TransformToDevice.M11; + DpiFactorY = source.CompositionTarget.TransformToDevice.M22; + return; + } + } + + DpiFactorX = DpiFactorY = 1; + } + + /// + /// Returns the DPI X Factor + /// + public static double DpiFactorX { get; private set; } = 1; + + /// + /// Returns the DPI Y Factor + /// + public static double DpiFactorY { get; private set; } = 1; + + /// + /// Scale the supplied point to the current DPI settings + /// + /// + /// Point + [Pure] + public static Point ScaleWithDpi(this Point point) + { + return new Point + { + X = (int)(point.X / DpiFactorX), + Y = (int)(point.Y / DpiFactorY) + }; + } + } +} \ No newline at end of file diff --git a/NotifyIconWpf/NotifyIconWpf/Interop/TrayInfo.cs b/NotifyIconWpf/NotifyIconWpf/Interop/TrayInfo.cs new file mode 100644 index 0000000..3930a89 --- /dev/null +++ b/NotifyIconWpf/NotifyIconWpf/Interop/TrayInfo.cs @@ -0,0 +1,56 @@ +// Some interop code taken from Mike Marshall's AnyForm + +using System.Drawing; + +namespace Hardcodet.Wpf.TaskbarNotification.Interop +{ + /// + /// Resolves the current tray position. + /// + public static class TrayInfo + { + /// + /// Gets the position of the system tray. + /// + /// Tray coordinates. + public static Point GetTrayLocation() + { + int space = 2; + var info = new AppBarInfo(); + info.GetSystemTaskBarPosition(); + + Rectangle rcWorkArea = info.WorkArea; + + int x = 0, y = 0; + switch (info.Edge) + { + case AppBarInfo.ScreenEdge.Left: + x = rcWorkArea.Right + space; + y = rcWorkArea.Bottom; + break; + case AppBarInfo.ScreenEdge.Bottom: + x = rcWorkArea.Right; + y = rcWorkArea.Bottom - rcWorkArea.Height - space; + break; + case AppBarInfo.ScreenEdge.Top: + x = rcWorkArea.Right; + y = rcWorkArea.Top + rcWorkArea.Height + space; + break; + case AppBarInfo.ScreenEdge.Right: + x = rcWorkArea.Right - rcWorkArea.Width - space; + y = rcWorkArea.Bottom; + break; + } + + return GetDeviceCoordinates(new Point {X = x, Y = y}); + } + + /// + /// Recalculates OS coordinates in order to support WPFs coordinate + /// system if OS scaling (DPIs) is not 100%. + /// + /// Point + /// Point + public static Point GetDeviceCoordinates(Point point) => point.ScaleWithDpi(); + } +} \ No newline at end of file diff --git a/NotifyIconWpf/NotifyIconWpf/Interop/WinApi.cs b/NotifyIconWpf/NotifyIconWpf/Interop/WinApi.cs new file mode 100644 index 0000000..94d6db6 --- /dev/null +++ b/NotifyIconWpf/NotifyIconWpf/Interop/WinApi.cs @@ -0,0 +1,91 @@ +using System; +using System.Runtime.InteropServices; + +namespace Hardcodet.Wpf.TaskbarNotification.Interop +{ + /// + /// Win32 API imports. + /// + internal static class WinApi + { + private const string User32 = "user32.dll"; + + /// + /// Creates, updates or deletes the taskbar icon. + /// + [DllImport("shell32.Dll", CharSet = CharSet.Unicode)] + public static extern bool Shell_NotifyIcon(NotifyCommand cmd, [In] ref NotifyIconData data); + + + /// + /// Creates the helper window that receives messages from the taskar icon. + /// + [DllImport(User32, EntryPoint = "CreateWindowExW", SetLastError = true)] + public static extern IntPtr CreateWindowEx(int dwExStyle, [MarshalAs(UnmanagedType.LPWStr)] string lpClassName, + [MarshalAs(UnmanagedType.LPWStr)] string lpWindowName, int dwStyle, int x, int y, + int nWidth, int nHeight, IntPtr hWndParent, IntPtr hMenu, IntPtr hInstance, + IntPtr lpParam); + + + /// + /// Processes a default windows procedure. + /// + [DllImport(User32)] + public static extern IntPtr DefWindowProc(IntPtr hWnd, uint msg, IntPtr wparam, IntPtr lparam); + + /// + /// Registers the helper window class. + /// + [DllImport(User32, EntryPoint = "RegisterClassW", SetLastError = true)] + public static extern short RegisterClass(ref WindowClass lpWndClass); + + /// + /// Registers a listener for a window message. + /// + /// + /// uint + [DllImport(User32, EntryPoint = "RegisterWindowMessageW")] + public static extern uint RegisterWindowMessage([MarshalAs(UnmanagedType.LPWStr)] string lpString); + + /// + /// Used to destroy the hidden helper window that receives messages from the + /// taskbar icon. + /// + /// + /// bool + [DllImport(User32, SetLastError = true)] + public static extern bool DestroyWindow(IntPtr hWnd); + + + /// + /// Gives focus to a given window. + /// + /// + /// bool + [DllImport(User32)] + public static extern bool SetForegroundWindow(IntPtr hWnd); + + + /// + /// Gets the maximum number of milliseconds that can elapse between a + /// first click and a second click for the OS to consider the + /// mouse action a double-click. + /// + /// The maximum amount of time, in milliseconds, that can + /// elapse between a first click and a second click for the OS to + /// consider the mouse action a double-click. + [DllImport(User32, CharSet = CharSet.Auto, ExactSpelling = true)] + public static extern int GetDoubleClickTime(); + + + /// + /// Gets the screen coordinates of the current mouse position. + /// + [DllImport(User32, SetLastError = true)] + public static extern bool GetPhysicalCursorPos(ref Point lpPoint); + + + [DllImport(User32, SetLastError = true)] + public static extern bool GetCursorPos(ref Point lpPoint); + } +} \ No newline at end of file diff --git a/NotifyIconWpf/NotifyIconWpf/Interop/WindowClass.cs b/NotifyIconWpf/NotifyIconWpf/Interop/WindowClass.cs new file mode 100644 index 0000000..baba148 --- /dev/null +++ b/NotifyIconWpf/NotifyIconWpf/Interop/WindowClass.cs @@ -0,0 +1,34 @@ +using System; +using System.Runtime.InteropServices; + +namespace Hardcodet.Wpf.TaskbarNotification.Interop +{ + /// + /// Callback delegate which is used by the Windows API to + /// submit window messages. + /// + public delegate IntPtr WindowProcedureHandler(IntPtr hWnd, uint uMsg, IntPtr wParam, IntPtr lParam); + + /// + /// Win API WNDCLASS struct - represents a single window. + /// Used to receive window messages. + /// + [StructLayout(LayoutKind.Sequential)] + public struct WindowClass + { + #pragma warning disable 1591 + + public uint style; + public WindowProcedureHandler lpfnWndProc; + public int cbClsExtra; + public int cbWndExtra; + public IntPtr hInstance; + public IntPtr hIcon; + public IntPtr hCursor; + public IntPtr hbrBackground; + [MarshalAs(UnmanagedType.LPWStr)] public string lpszMenuName; + [MarshalAs(UnmanagedType.LPWStr)] public string lpszClassName; + + #pragma warning restore 1591 + } +} \ No newline at end of file diff --git a/NotifyIconWpf/NotifyIconWpf/Interop/WindowMessageSink.cs b/NotifyIconWpf/NotifyIconWpf/Interop/WindowMessageSink.cs new file mode 100644 index 0000000..2ad3882 --- /dev/null +++ b/NotifyIconWpf/NotifyIconWpf/Interop/WindowMessageSink.cs @@ -0,0 +1,390 @@ +// hardcodet.net NotifyIcon for WPF +// Copyright (c) 2009 - 2020 Philipp Sumi +// Contact and Information: http://www.hardcodet.net +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the Code Project Open License (CPOL); +// either version 1.0 of the License, or (at your option) any later +// version. +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// THIS COPYRIGHT NOTICE MAY NOT BE REMOVED FROM THIS FILE + + +using System; +using System.ComponentModel; +using System.Diagnostics; + +namespace Hardcodet.Wpf.TaskbarNotification.Interop +{ + /// + /// Receives messages from the taskbar icon through + /// window messages of an underlying helper window. + /// + public class WindowMessageSink : IDisposable + { + #region members + + /// + /// The ID of messages that are received from the the + /// taskbar icon. + /// + public const int CallbackMessageId = 0x400; + + /// + /// The ID of the message that is being received if the + /// taskbar is (re)started. + /// + private uint taskbarRestartMessageId; + + /// + /// Used to track whether a mouse-up event is just + /// the aftermath of a double-click and therefore needs + /// to be suppressed. + /// + private bool isDoubleClick; + + /// + /// A delegate that processes messages of the hidden + /// native window that receives window messages. Storing + /// this reference makes sure we don't loose our reference + /// to the message window. + /// + private WindowProcedureHandler messageHandler; + + /// + /// Window class ID. + /// + internal string WindowId { get; private set; } + + /// + /// Handle for the message window. + /// + internal IntPtr MessageWindowHandle { get; private set; } + + /// + /// The version of the underlying icon. Defines how + /// incoming messages are interpreted. + /// + public NotifyIconVersion Version { get; set; } + + #endregion + + #region events + + /// + /// The custom tooltip should be closed or hidden. + /// + public event Action ChangeToolTipStateRequest; + + /// + /// Fired in case the user clicked or moved within + /// the taskbar icon area. + /// + public event Action MouseEventReceived; + + /// + /// Fired if a balloon ToolTip was either displayed + /// or closed (indicated by the boolean flag). + /// + public event Action BalloonToolTipChanged; + + /// + /// Fired if the taskbar was created or restarted. Requires the taskbar + /// icon to be reset. + /// + public event Action TaskbarCreated; + + #endregion + + #region construction + + /// + /// Creates a new message sink that receives message from + /// a given taskbar icon. + /// + /// + public WindowMessageSink(NotifyIconVersion version) + { + Version = version; + CreateMessageWindow(); + } + + + private WindowMessageSink() + { + } + + /// + /// Creates a dummy instance that provides an empty + /// pointer rather than a real window handler.
+ /// Used at design time. + ///
+ /// WindowMessageSink + internal static WindowMessageSink CreateEmpty() + { + return new WindowMessageSink + { + MessageWindowHandle = IntPtr.Zero, + Version = NotifyIconVersion.Vista + }; + } + + #endregion + + #region CreateMessageWindow + + /// + /// Creates the helper message window that is used + /// to receive messages from the taskbar icon. + /// + private void CreateMessageWindow() + { + //generate a unique ID for the window + WindowId = "WPFTaskbarIcon_" + Guid.NewGuid(); + + //register window message handler + messageHandler = OnWindowMessageReceived; + + // Create a simple window class which is reference through + //the messageHandler delegate + WindowClass wc; + + wc.style = 0; + wc.lpfnWndProc = messageHandler; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = IntPtr.Zero; + wc.hIcon = IntPtr.Zero; + wc.hCursor = IntPtr.Zero; + wc.hbrBackground = IntPtr.Zero; + wc.lpszMenuName = string.Empty; + wc.lpszClassName = WindowId; + + // Register the window class + WinApi.RegisterClass(ref wc); + + // Get the message used to indicate the taskbar has been restarted + // This is used to re-add icons when the taskbar restarts + taskbarRestartMessageId = WinApi.RegisterWindowMessage("TaskbarCreated"); + + // Create the message window + MessageWindowHandle = WinApi.CreateWindowEx(0, WindowId, "", 0, 0, 0, 1, 1, IntPtr.Zero, IntPtr.Zero, + IntPtr.Zero, IntPtr.Zero); + + if (MessageWindowHandle == IntPtr.Zero) + { + throw new Win32Exception("Message window handle was not a valid pointer"); + } + } + + #endregion + + #region Handle Window Messages + + /// + /// Callback method that receives messages from the taskbar area. + /// + private IntPtr OnWindowMessageReceived(IntPtr hWnd, uint messageId, IntPtr wParam, IntPtr lParam) + { + if (messageId == taskbarRestartMessageId) + { + //recreate the icon if the taskbar was restarted (e.g. due to Win Explorer shutdown) + var listener = TaskbarCreated; + listener?.Invoke(); + } + + //forward message + ProcessWindowMessage(messageId, wParam, lParam); + + // Pass the message to the default window procedure + return WinApi.DefWindowProc(hWnd, messageId, wParam, lParam); + } + + + /// + /// Processes incoming system messages. + /// + /// Callback ID. + /// If the version is + /// or higher, this parameter can be used to resolve mouse coordinates. + /// Currently not in use. + /// Provides information about the event. + private void ProcessWindowMessage(uint msg, IntPtr wParam, IntPtr lParam) + { + // Check if it was a callback message + if (msg != CallbackMessageId) + { + // It was not a callback message, but make sure it's not something else we need to process + switch ((WindowsMessages) msg) + { + case WindowsMessages.WM_DPICHANGED: + Debug.WriteLine("DPI Change"); + SystemInfo.UpdateDpiFactors(); + break; + } + return; + } + + var message = (WindowsMessages)lParam.ToInt32(); + Debug.WriteLine("Got message " + message); + switch (message) + { + case WindowsMessages.WM_CONTEXTMENU: + // TODO: Handle WM_CONTEXTMENU, see https://docs.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shell_notifyiconw + Debug.WriteLine("Unhandled WM_CONTEXTMENU"); + break; + + case WindowsMessages.WM_MOUSEMOVE: + MouseEventReceived?.Invoke(MouseEvent.MouseMove); + break; + + case WindowsMessages.WM_LBUTTONDOWN: + MouseEventReceived?.Invoke(MouseEvent.IconLeftMouseDown); + break; + + case WindowsMessages.WM_LBUTTONUP: + if (!isDoubleClick) + { + MouseEventReceived?.Invoke(MouseEvent.IconLeftMouseUp); + } + isDoubleClick = false; + break; + + case WindowsMessages.WM_LBUTTONDBLCLK: + isDoubleClick = true; + MouseEventReceived?.Invoke(MouseEvent.IconDoubleClick); + break; + + case WindowsMessages.WM_RBUTTONDOWN: + MouseEventReceived?.Invoke(MouseEvent.IconRightMouseDown); + break; + + case WindowsMessages.WM_RBUTTONUP: + MouseEventReceived?.Invoke(MouseEvent.IconRightMouseUp); + break; + + case WindowsMessages.WM_RBUTTONDBLCLK: + //double click with right mouse button - do not trigger event + break; + + case WindowsMessages.WM_MBUTTONDOWN: + MouseEventReceived?.Invoke(MouseEvent.IconMiddleMouseDown); + break; + + case WindowsMessages.WM_MBUTTONUP: + MouseEventReceived?.Invoke(MouseEvent.IconMiddleMouseUp); + break; + + case WindowsMessages.WM_MBUTTONDBLCLK: + //double click with middle mouse button - do not trigger event + break; + + case WindowsMessages.NIN_BALLOONSHOW: + BalloonToolTipChanged?.Invoke(true); + break; + + case WindowsMessages.NIN_BALLOONHIDE: + case WindowsMessages.NIN_BALLOONTIMEOUT: + BalloonToolTipChanged?.Invoke(false); + break; + + case WindowsMessages.NIN_BALLOONUSERCLICK: + MouseEventReceived?.Invoke(MouseEvent.BalloonToolTipClicked); + break; + + case WindowsMessages.NIN_POPUPOPEN: + ChangeToolTipStateRequest?.Invoke(true); + break; + + case WindowsMessages.NIN_POPUPCLOSE: + ChangeToolTipStateRequest?.Invoke(false); + break; + + case WindowsMessages.NIN_SELECT: + // TODO: Handle NIN_SELECT see https://docs.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shell_notifyiconw + Debug.WriteLine("Unhandled NIN_SELECT"); + break; + + case WindowsMessages.NIN_KEYSELECT: + // TODO: Handle NIN_KEYSELECT see https://docs.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shell_notifyiconw + Debug.WriteLine("Unhandled NIN_KEYSELECT"); + break; + + default: + Debug.WriteLine("Unhandled NotifyIcon message ID: " + lParam); + break; + } + } + + #endregion + + #region Dispose + + /// + /// Set to true as soon as Dispose has been invoked. + /// + public bool IsDisposed { get; private set; } + + + /// + /// Disposes the object. + /// + /// This method is not virtual by design. Derived classes + /// should override . + /// + public void Dispose() + { + Dispose(true); + + // This object will be cleaned up by the Dispose method. + // Therefore, you should call GC.SuppressFinalize to + // take this object off the finalization queue + // and prevent finalization code for this object + // from executing a second time. + GC.SuppressFinalize(this); + } + + /// + /// This destructor will run only if the + /// method does not get called. This gives this base class the + /// opportunity to finalize. + /// + /// Important: Do not provide destructor in types derived from + /// this class. + /// + /// + ~WindowMessageSink() + { + Dispose(false); + } + + /// + /// Removes the windows hook that receives window + /// messages and closes the underlying helper window. + /// + private void Dispose(bool disposing) + { + //don't do anything if the component is already disposed + if (IsDisposed) return; + IsDisposed = true; + + //always destroy the unmanaged handle (even if called from the GC) + WinApi.DestroyWindow(MessageWindowHandle); + messageHandler = null; + } + + #endregion + } +} \ No newline at end of file diff --git a/NotifyIconWpf/NotifyIconWpf/Interop/WindowsMessages.cs b/NotifyIconWpf/NotifyIconWpf/Interop/WindowsMessages.cs new file mode 100644 index 0000000..506b3d3 --- /dev/null +++ b/NotifyIconWpf/NotifyIconWpf/Interop/WindowsMessages.cs @@ -0,0 +1,197 @@ +// hardcodet.net NotifyIcon for WPF +// Copyright (c) 2009 - 2020 Philipp Sumi +// Contact and Information: http://www.hardcodet.net +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the Code Project Open License (CPOL); +// either version 1.0 of the License, or (at your option) any later +// version. +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// THIS COPYRIGHT NOTICE MAY NOT BE REMOVED FROM THIS FILE + +// ReSharper disable InconsistentNaming + +using System.Diagnostics.CodeAnalysis; + +namespace Hardcodet.Wpf.TaskbarNotification.Interop +{ + /// + /// This enum defines the windows messages we respond to. + /// See more on Windows messages here + /// + [SuppressMessage("ReSharper", "IdentifierTypo")] + public enum WindowsMessages : uint + { + /// + /// Notifies a window that the user clicked the right mouse button (right-clicked) in the window. + /// See WM_CONTEXTMENU message + /// + /// In case of a notify icon: + /// If a user selects a notify icon's shortcut menu with the keyboard, the Shell now sends the associated application a WM_CONTEXTMENU message. Earlier versions send WM_RBUTTONDOWN and WM_RBUTTONUP messages. + /// See Shell_NotifyIcon function + /// + WM_CONTEXTMENU = 0x007b, + + /// + /// Posted to a window when the cursor moves. + /// If the mouse is not captured, the message is posted to the window that contains the cursor. + /// Otherwise, the message is posted to the window that has captured the mouse. + /// + /// See WM_MOUSEMOVE message + /// + WM_MOUSEMOVE = 0x0200, + + /// + /// Posted when the user presses the left mouse button while the cursor is in the client area of a window. + /// If the mouse is not captured, the message is posted to the window beneath the cursor. + /// Otherwise, the message is posted to the window that has captured the mouse. + /// + /// See WM_LBUTTONDOWN message + /// + WM_LBUTTONDOWN = 0x0201, + + /// + /// Posted when the user releases the left mouse button while the cursor is in the client area of a window. + /// If the mouse is not captured, the message is posted to the window beneath the cursor. + /// Otherwise, the message is posted to the window that has captured the mouse. + /// + /// See WM_LBUTTONUP message + /// + WM_LBUTTONUP = 0x0202, + + /// + /// Posted when the user double-clicks the left mouse button while the cursor is in the client area of a window. + /// If the mouse is not captured, the message is posted to the window beneath the cursor. + /// Otherwise, the message is posted to the window that has captured the mouse. + /// + /// See WM_LBUTTONDBLCLK message + /// + WM_LBUTTONDBLCLK = 0x0203, + + /// + /// Posted when the user presses the right mouse button while the cursor is in the client area of a window. + /// If the mouse is not captured, the message is posted to the window beneath the cursor. + /// Otherwise, the message is posted to the window that has captured the mouse. + /// + /// See WM_RBUTTONDOWN message + /// + WM_RBUTTONDOWN = 0x0204, + + /// + /// Posted when the user releases the right mouse button while the cursor is in the client area of a window. + /// If the mouse is not captured, the message is posted to the window beneath the cursor. + /// Otherwise, the message is posted to the window that has captured the mouse. + /// + /// See WM_RBUTTONUP message + /// + WM_RBUTTONUP = 0x0205, + + /// + /// Posted when the user double-clicks the right mouse button while the cursor is in the client area of a window. + /// If the mouse is not captured, the message is posted to the window beneath the cursor. + /// Otherwise, the message is posted to the window that has captured the mouse. + /// + /// See WM_RBUTTONDBLCLK message + /// + WM_RBUTTONDBLCLK = 0x0206, + + /// + /// Posted when the user presses the middle mouse button while the cursor is in the client area of a window. + /// If the mouse is not captured, the message is posted to the window beneath the cursor. + /// Otherwise, the message is posted to the window that has captured the mouse. + /// + /// See WM_MBUTTONDOWN message + /// + WM_MBUTTONDOWN = 0x0207, + + /// + /// Posted when the user releases the middle mouse button while the cursor is in the client area of a window. + /// If the mouse is not captured, the message is posted to the window beneath the cursor. + /// Otherwise, the message is posted to the window that has captured the mouse. + /// + /// See WM_MBUTTONUP message + /// + WM_MBUTTONUP = 0x0208, + + /// + /// Posted when the user double-clicks the middle mouse button while the cursor is in the client area of a window. + /// If the mouse is not captured, the message is posted to the window beneath the cursor. + /// Otherwise, the message is posted to the window that has captured the mouse. + /// + /// See WM_MBUTTONDBLCLK message + /// + WM_MBUTTONDBLCLK = 0x0209, + + /// + /// Sent when the effective dots per inch (dpi) for a window has changed. + /// The DPI is the scale factor for a window. + /// There are multiple events that can cause the DPI to change. + /// + WM_DPICHANGED = 0x02e0, + + /// + /// Used to define private messages for use by private window classes, usually of the form WM_USER+x, where x is an integer value. + /// + WM_USER = 0x0400, + + /// + /// This message is only send when using NOTIFYICON_VERSION_4, the Shell now sends the associated application an NIN_SELECT notification. + /// Send when a notify icon is activated with mouse or ENTER key. + /// Earlier versions send WM_RBUTTONDOWN and WM_RBUTTONUP messages. + /// + NIN_SELECT = WM_USER, + + /// + /// This message is only send when using NOTIFYICON_VERSION_4, the Shell now sends the associated application an NIN_SELECT notification. + /// Send when a notify icon is activated with SPACEBAR or ENTER key. + /// Earlier versions send WM_RBUTTONDOWN and WM_RBUTTONUP messages. + /// + NIN_KEYSELECT = WM_USER + 1, + + /// + /// Sent when the balloon is shown (balloons are queued). + /// + NIN_BALLOONSHOW = WM_USER + 2, + + /// + /// Sent when the balloon disappears. For example, when the icon is deleted. + /// This message is not sent if the balloon is dismissed because of a timeout or if the user clicks the mouse. + /// + /// As of Windows 7, NIN_BALLOONHIDE is also sent when a notification with the NIIF_RESPECT_QUIET_TIME flag set attempts to display during quiet time (a user's first hour on a new computer). + /// In that case, the balloon is never displayed at all. + /// + NIN_BALLOONHIDE = WM_USER + 3, + + /// + /// Sent when the balloon is dismissed because of a timeout. + /// + NIN_BALLOONTIMEOUT = WM_USER + 4, + + /// + /// Sent when the balloon is dismissed because the user clicked the mouse. + /// + NIN_BALLOONUSERCLICK = WM_USER + 5, + + /// + /// Sent when the user hovers the cursor over an icon to indicate that the richer pop-up UI should be used in place of a standard textual tooltip. + /// + NIN_POPUPOPEN = WM_USER + 6, + + /// + /// Sent when a cursor no longer hovers over an icon to indicate that the rich pop-up UI should be closed. + /// + NIN_POPUPCLOSE = WM_USER + 7 + } +} diff --git a/NotifyIconWpf/NotifyIconWpf/NotifyIconWpf.csproj b/NotifyIconWpf/NotifyIconWpf/NotifyIconWpf.csproj new file mode 100644 index 0000000..793229c --- /dev/null +++ b/NotifyIconWpf/NotifyIconWpf/NotifyIconWpf.csproj @@ -0,0 +1,29 @@ + + + + net45;net462;net472;net6.0-windows + latest + true + true + true + + True + embedded + + + + Hardcodet.Wpf.TaskbarNotification + Hardcodet.NotifyIcon.Wpf + NotifyIcon for WPF + NotifyIcon WPF + + + + + + + + + + + \ No newline at end of file diff --git a/NotifyIconWpf/NotifyIconWpf/NotifyIconWpf.snk b/NotifyIconWpf/NotifyIconWpf/NotifyIconWpf.snk new file mode 100644 index 0000000..ec0afc5 Binary files /dev/null and b/NotifyIconWpf/NotifyIconWpf/NotifyIconWpf.snk differ diff --git a/NotifyIconWpf/NotifyIconWpf/PopupActivationMode.cs b/NotifyIconWpf/NotifyIconWpf/PopupActivationMode.cs new file mode 100644 index 0000000..a8eac95 --- /dev/null +++ b/NotifyIconWpf/NotifyIconWpf/PopupActivationMode.cs @@ -0,0 +1,75 @@ +// hardcodet.net NotifyIcon for WPF +// Copyright (c) 2009 - 2020 Philipp Sumi +// Contact and Information: http://www.hardcodet.net +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the Code Project Open License (CPOL); +// either version 1.0 of the License, or (at your option) any later +// version. +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// THIS COPYRIGHT NOTICE MAY NOT BE REMOVED FROM THIS FILE + + +namespace Hardcodet.Wpf.TaskbarNotification +{ + /// + /// Defines flags that define when a popup + /// is being displyed. + /// + public enum PopupActivationMode + { + /// + /// The item is displayed if the user clicks the + /// tray icon with the left mouse button. + /// + LeftClick, + + /// + /// The item is displayed if the user clicks the + /// tray icon with the right mouse button. + /// + RightClick, + + /// + /// The item is displayed if the user double-clicks the + /// tray icon. + /// + DoubleClick, + + /// + /// The item is displayed if the user clicks the + /// tray icon with the left or the right mouse button. + /// + LeftOrRightClick, + + /// + /// The item is displayed if the user clicks the + /// tray icon with the left mouse button or if a + /// double-click is being performed. + /// + LeftOrDoubleClick, + + /// + /// The item is displayed if the user clicks the + /// tray icon with the middle mouse button. + /// + MiddleClick, + + /// + /// The item is displayed whenever a click occurs. + /// + All + } +} \ No newline at end of file diff --git a/NotifyIconWpf/NotifyIconWpf/Properties/AssemblyInfo.cs b/NotifyIconWpf/NotifyIconWpf/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..4f5b5a1 --- /dev/null +++ b/NotifyIconWpf/NotifyIconWpf/Properties/AssemblyInfo.cs @@ -0,0 +1,45 @@ +using System.Runtime.InteropServices; +using System.Windows; +using System.Windows.Markup; + + +//provides simplified declaration in XAML +[assembly: XmlnsPrefix("http://www.hardcodet.net/taskbar", "tb")] +[assembly: XmlnsDefinition("http://www.hardcodet.net/taskbar", "Hardcodet.Wpf.TaskbarNotification")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. + +[assembly: ComVisible(false)] + +//In order to begin building localizable applications, set +//CultureYouAreCodingWith in your .csproj file +//inside a . For example, if you are using US english +//in your source files, set the to en-US. Then uncomment +//the NeutralResourceLanguage attribute below. Update the "en-US" in +//the line below to match the UICulture setting in the project file. + +//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) + )] + + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] + diff --git a/NotifyIconWpf/NotifyIconWpf/RoutedEventHelper.cs b/NotifyIconWpf/NotifyIconWpf/RoutedEventHelper.cs new file mode 100644 index 0000000..a15893a --- /dev/null +++ b/NotifyIconWpf/NotifyIconWpf/RoutedEventHelper.cs @@ -0,0 +1,71 @@ +using System; +using System.Windows; + +namespace Hardcodet.Wpf.TaskbarNotification +{ + /// + /// Helper class used by routed events of the + /// class. + /// + internal static class RoutedEventHelper + { + #region RoutedEvent Helper Methods + + /// + /// A static helper method to raise a routed event on a target UIElement or ContentElement. + /// + /// UIElement or ContentElement on which to raise the event + /// RoutedEventArgs to use when raising the event + internal static void RaiseEvent(DependencyObject target, RoutedEventArgs args) + { + if (target is UIElement uiElement) + { + uiElement.RaiseEvent(args); + } + else if (target is ContentElement contentElement) + { + contentElement.RaiseEvent(args); + } + } + + /// + /// A static helper method that adds a handler for a routed event + /// to a target UIElement or ContentElement. + /// + /// UIElement or ContentElement that listens to the event + /// Event that will be handled + /// Event handler to be added + internal static void AddHandler(DependencyObject element, RoutedEvent routedEvent, Delegate handler) + { + if (element is UIElement uie) + { + uie.AddHandler(routedEvent, handler); + } + else if (element is ContentElement ce) + { + ce.AddHandler(routedEvent, handler); + } + } + + /// + /// A static helper method that removes a handler for a routed event + /// from a target UIElement or ContentElement. + /// + /// UIElement or ContentElement that listens to the event + /// Event that will no longer be handled + /// Event handler to be removed + internal static void RemoveHandler(DependencyObject element, RoutedEvent routedEvent, Delegate handler) + { + if (element is UIElement uie) + { + uie.RemoveHandler(routedEvent, handler); + } + else if (element is ContentElement ce) + { + ce.RemoveHandler(routedEvent, handler); + } + } + + #endregion + } +} \ No newline at end of file diff --git a/NotifyIconWpf/NotifyIconWpf/TaskbarIcon.Declarations.cs b/NotifyIconWpf/NotifyIconWpf/TaskbarIcon.Declarations.cs new file mode 100644 index 0000000..2e994f3 --- /dev/null +++ b/NotifyIconWpf/NotifyIconWpf/TaskbarIcon.Declarations.cs @@ -0,0 +1,1896 @@ +// hardcodet.net NotifyIcon for WPF +// Copyright (c) 2009 - 2020 Philipp Sumi +// Contact and Information: http://www.hardcodet.net +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the Code Project Open License (CPOL); +// either version 1.0 of the License, or (at your option) any later +// version. +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// THIS COPYRIGHT NOTICE MAY NOT BE REMOVED FROM THIS FILE + + +using System; +using System.ComponentModel; +using System.Drawing; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Input; +using System.Windows.Media; +using Hardcodet.Wpf.TaskbarNotification.Interop; + +namespace Hardcodet.Wpf.TaskbarNotification +{ + /// + /// Contains declarations of WPF dependency properties + /// and events. + /// + partial class TaskbarIcon + { + /// + /// Category name that is set on designer properties. + /// + public const string CategoryName = "NotifyIcon"; + + + //POPUP CONTROLS + + #region TrayPopupResolved + + /// + /// TrayPopupResolved Read-Only Dependency Property + /// + private static readonly DependencyPropertyKey TrayPopupResolvedPropertyKey + = DependencyProperty.RegisterReadOnly(nameof(TrayPopupResolved), typeof (Popup), typeof (TaskbarIcon), + new FrameworkPropertyMetadata(null)); + + + /// + /// A read-only dependency property that returns the + /// that is being displayed in the taskbar area based on a user action. + /// + public static readonly DependencyProperty TrayPopupResolvedProperty + = TrayPopupResolvedPropertyKey.DependencyProperty; + + /// + /// Gets the TrayPopupResolved property. Returns + /// a which is either the + /// control itself or a + /// control that contains the + /// . + /// + [Category(CategoryName)] + public Popup TrayPopupResolved + { + get { return (Popup) GetValue(TrayPopupResolvedProperty); } + } + + /// + /// Provides a secure method for setting the TrayPopupResolved property. + /// This dependency property indicates .... + /// + /// The new value for the property. + protected void SetTrayPopupResolved(Popup value) + { + SetValue(TrayPopupResolvedPropertyKey, value); + } + + #endregion + + #region TrayToolTipResolved + + /// + /// TrayToolTipResolved Read-Only Dependency Property + /// + private static readonly DependencyPropertyKey TrayToolTipResolvedPropertyKey + = DependencyProperty.RegisterReadOnly(nameof(TrayToolTipResolved), typeof (ToolTip), typeof (TaskbarIcon), + new FrameworkPropertyMetadata(null)); + + + /// + /// A read-only dependency property that returns the + /// that is being displayed. + /// + public static readonly DependencyProperty TrayToolTipResolvedProperty + = TrayToolTipResolvedPropertyKey.DependencyProperty; + + /// + /// Gets the TrayToolTipResolved property. Returns + /// a control that was created + /// in order to display either + /// or . + /// + [Category(CategoryName)] + [Browsable(true)] + [Bindable(true)] + public ToolTip TrayToolTipResolved + { + get { return (ToolTip) GetValue(TrayToolTipResolvedProperty); } + } + + /// + /// Provides a secure method for setting the + /// property. + /// + /// The new value for the property. + protected void SetTrayToolTipResolved(ToolTip value) + { + SetValue(TrayToolTipResolvedPropertyKey, value); + } + + #endregion + + #region CustomBalloon + + /// + /// CustomBalloon Read-Only Dependency Property + /// + private static readonly DependencyPropertyKey CustomBalloonPropertyKey + = DependencyProperty.RegisterReadOnly(nameof(CustomBalloon), typeof (Popup), typeof (TaskbarIcon), + new FrameworkPropertyMetadata(null)); + + /// + /// Maintains a currently displayed custom balloon. + /// + public static readonly DependencyProperty CustomBalloonProperty + = CustomBalloonPropertyKey.DependencyProperty; + + /// + /// A custom popup that is being displayed in the tray area in order + /// to display messages to the user. + /// + public Popup CustomBalloon + { + get { return (Popup) GetValue(CustomBalloonProperty); } + } + + /// + /// Provides a secure method for setting the property. + /// + /// The new value for the property. + protected void SetCustomBalloon(Popup value) + { + SetValue(CustomBalloonPropertyKey, value); + } + + #endregion + + //DEPENDENCY PROPERTIES + + #region Icon property / IconSource dependency property + + private Icon icon; + + /// + /// Gets or sets the icon to be displayed. This is not a + /// dependency property - if you want to assign the property + /// through XAML, please use the + /// dependency property. + /// + [Browsable(false)] + public Icon Icon + { + get { return icon; } + set + { + icon = value; + iconData.IconHandle = value == null ? IntPtr.Zero : icon.Handle; + + Util.WriteIconData(ref iconData, NotifyCommand.Modify, IconDataMembers.Icon); + } + } + + + /// + /// Resolves an image source and updates the property accordingly. + /// + public static readonly DependencyProperty IconSourceProperty = + DependencyProperty.Register(nameof(IconSource), + typeof (ImageSource), + typeof (TaskbarIcon), + new FrameworkPropertyMetadata(null, IconSourcePropertyChanged)); + + /// + /// A property wrapper for the + /// dependency property:
+ /// Resolves an image source and updates the property accordingly. + ///
+ [Category(CategoryName)] + [Description("Sets the displayed taskbar icon.")] + public ImageSource IconSource + { + get { return (ImageSource) GetValue(IconSourceProperty); } + set { SetValue(IconSourceProperty, value); } + } + + + /// + /// A static callback listener which is being invoked if the + /// dependency property has + /// been changed. Invokes the + /// instance method of the changed instance. + /// + /// The currently processed owner of the property. + /// Provides information about the updated property. + private static void IconSourcePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + TaskbarIcon owner = (TaskbarIcon) d; + owner.OnIconSourcePropertyChanged(e); + } + + + /// + /// Handles changes of the dependency property. As + /// WPF internally uses the dependency property system and bypasses the + /// property wrapper, updates of the property's value + /// should be handled here. + /// + /// Provides information about the updated property. + private void OnIconSourcePropertyChanged(DependencyPropertyChangedEventArgs e) + { + ImageSource newValue = (ImageSource) e.NewValue; + + //resolving the ImageSource at design time is unlikely to work + if (!Util.IsDesignMode) Icon = newValue.ToIcon(); + } + + #endregion + + #region ToolTipText dependency property + + /// + /// A tooltip text that is being displayed if no custom + /// was set or if custom tooltips are not supported. + /// + public static readonly DependencyProperty ToolTipTextProperty = + DependencyProperty.Register(nameof(ToolTipText), + typeof (string), + typeof (TaskbarIcon), + new FrameworkPropertyMetadata(string.Empty, ToolTipTextPropertyChanged)); + + + /// + /// A property wrapper for the + /// dependency property:
+ /// A tooltip text that is being displayed if no custom + /// was set or if custom tooltips are not supported. + ///
+ [Category(CategoryName)] + [Description("Alternative to a fully blown ToolTip, which is only displayed on Vista and above.")] + public string ToolTipText + { + get { return (string) GetValue(ToolTipTextProperty); } + set { SetValue(ToolTipTextProperty, value); } + } + + + /// + /// A static callback listener which is being invoked if the + /// dependency property has + /// been changed. Invokes the + /// instance method of the changed instance. + /// + /// The currently processed owner of the property. + /// Provides information about the updated property. + private static void ToolTipTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + TaskbarIcon owner = (TaskbarIcon) d; + owner.OnToolTipTextPropertyChanged(e); + } + + + /// + /// Handles changes of the dependency property. As + /// WPF internally uses the dependency property system and bypasses the + /// property wrapper, updates of the property's value + /// should be handled here. + /// + /// Provides information about the updated property. + private void OnToolTipTextPropertyChanged(DependencyPropertyChangedEventArgs e) + { + //do not touch tooltips if we have a custom tooltip element + if (TrayToolTip == null) + { + ToolTip currentToolTip = TrayToolTipResolved; + if (currentToolTip == null) + { + //if we don't have a wrapper tooltip for the tooltip text, create it now + CreateCustomToolTip(); + } + else + { + //if we have a wrapper tooltip that shows the old tooltip text, just update content + currentToolTip.Content = e.NewValue; + } + } + + WriteToolTipSettings(); + } + + #endregion + + #region TrayToolTip dependency property + + /// + /// A custom UI element that is displayed as a tooltip if the user hovers over the taskbar icon. + /// Works only with Vista and above. Accordingly, you should make sure that + /// the property is set as well. + /// + public static readonly DependencyProperty TrayToolTipProperty = + DependencyProperty.Register(nameof(TrayToolTip), + typeof (UIElement), + typeof (TaskbarIcon), + new FrameworkPropertyMetadata(null, TrayToolTipPropertyChanged)); + + /// + /// A property wrapper for the + /// dependency property:
+ /// A custom UI element that is displayed as a tooltip if the user hovers over the taskbar icon. + /// Works only with Vista and above. Accordingly, you should make sure that + /// the property is set as well. + ///
+ [Category(CategoryName)] + [Description("Custom UI element that is displayed as a tooltip. Only on Vista and above")] + public UIElement TrayToolTip + { + get { return (UIElement) GetValue(TrayToolTipProperty); } + set { SetValue(TrayToolTipProperty, value); } + } + + + /// + /// A static callback listener which is being invoked if the + /// dependency property has + /// been changed. Invokes the + /// instance method of the changed instance. + /// + /// The currently processed owner of the property. + /// Provides information about the updated property. + private static void TrayToolTipPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + TaskbarIcon owner = (TaskbarIcon) d; + owner.OnTrayToolTipPropertyChanged(e); + } + + + /// + /// Handles changes of the dependency property. As + /// WPF internally uses the dependency property system and bypasses the + /// property wrapper, updates of the property's value + /// should be handled here. + /// + /// Provides information about the updated property. + private void OnTrayToolTipPropertyChanged(DependencyPropertyChangedEventArgs e) + { + //recreate tooltip control + CreateCustomToolTip(); + + if (e.OldValue != null) + { + //remove the taskbar icon reference from the previously used element + SetParentTaskbarIcon((DependencyObject) e.OldValue, null); + } + + if (e.NewValue != null) + { + //set this taskbar icon as a reference to the new tooltip element + SetParentTaskbarIcon((DependencyObject) e.NewValue, this); + } + + //update tooltip settings - needed to make sure a string is set, even + //if the ToolTipText property is not set. Otherwise, the event that + //triggers tooltip display is never fired. + WriteToolTipSettings(); + } + + #endregion + + #region TrayPopup dependency property + + /// + /// A control that is displayed as a popup when the taskbar icon is clicked. + /// + public static readonly DependencyProperty TrayPopupProperty = + DependencyProperty.Register(nameof(TrayPopup), + typeof (UIElement), + typeof (TaskbarIcon), + new FrameworkPropertyMetadata(null, TrayPopupPropertyChanged)); + + /// + /// A property wrapper for the + /// dependency property:
+ /// A control that is displayed as a popup when the taskbar icon is clicked. + ///
+ [Category(CategoryName)] + [Description("Displayed as a Popup if the user clicks on the taskbar icon.")] + public UIElement TrayPopup + { + get { return (UIElement) GetValue(TrayPopupProperty); } + set { SetValue(TrayPopupProperty, value); } + } + + + /// + /// A static callback listener which is being invoked if the + /// dependency property has + /// been changed. Invokes the + /// instance method of the changed instance. + /// + /// The currently processed owner of the property. + /// Provides information about the updated property. + private static void TrayPopupPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + TaskbarIcon owner = (TaskbarIcon) d; + owner.OnTrayPopupPropertyChanged(e); + } + + + /// + /// Handles changes of the dependency property. As + /// WPF internally uses the dependency property system and bypasses the + /// property wrapper, updates of the property's value + /// should be handled here. + /// + /// Provides information about the updated property. + private void OnTrayPopupPropertyChanged(DependencyPropertyChangedEventArgs e) + { + if (e.OldValue != null) + { + //remove the taskbar icon reference from the previously used element + SetParentTaskbarIcon((DependencyObject) e.OldValue, null); + } + + + if (e.NewValue != null) + { + //set this taskbar icon as a reference to the new tooltip element + SetParentTaskbarIcon((DependencyObject) e.NewValue, this); + } + + //create a pop + CreatePopup(); + } + + #endregion + + #region MenuActivation dependency property + + /// + /// Defines what mouse events display the context menu. + /// Defaults to . + /// + public static readonly DependencyProperty MenuActivationProperty = + DependencyProperty.Register(nameof(MenuActivation), + typeof (PopupActivationMode), + typeof (TaskbarIcon), + new FrameworkPropertyMetadata(PopupActivationMode.RightClick)); + + /// + /// A property wrapper for the + /// dependency property:
+ /// Defines what mouse events display the context menu. + /// Defaults to . + ///
+ [Category(CategoryName)] + [Description("Defines what mouse events display the context menu.")] + public PopupActivationMode MenuActivation + { + get { return (PopupActivationMode) GetValue(MenuActivationProperty); } + set { SetValue(MenuActivationProperty, value); } + } + + #endregion + + #region PopupActivation dependency property + + /// + /// Defines what mouse events trigger the . + /// Default is . + /// + public static readonly DependencyProperty PopupActivationProperty = + DependencyProperty.Register(nameof(PopupActivation), + typeof (PopupActivationMode), + typeof (TaskbarIcon), + new FrameworkPropertyMetadata(PopupActivationMode.LeftClick)); + + /// + /// A property wrapper for the + /// dependency property:
+ /// Defines what mouse events trigger the . + /// Default is . + ///
+ [Category(CategoryName)] + [Description("Defines what mouse events display the TaskbarIconPopup.")] + public PopupActivationMode PopupActivation + { + get { return (PopupActivationMode) GetValue(PopupActivationProperty); } + set { SetValue(PopupActivationProperty, value); } + } + + #endregion + + #region Visibility dependency property override + + /// + /// A static callback listener which is being invoked if the + /// dependency property has + /// been changed. Invokes the + /// instance method of the changed instance. + /// + /// The currently processed owner of the property. + /// Provides information about the updated property. + private static void VisibilityPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + TaskbarIcon owner = (TaskbarIcon) d; + owner.OnVisibilityPropertyChanged(e); + } + + + /// + /// Handles changes of the dependency property. As + /// WPF internally uses the dependency property system and bypasses the + /// property wrapper, updates of the property's value + /// should be handled here. + /// + /// Provides information about the updated property. + private void OnVisibilityPropertyChanged(DependencyPropertyChangedEventArgs e) + { + Visibility newValue = (Visibility) e.NewValue; + + //update + if (newValue == Visibility.Visible) + { + CreateTaskbarIcon(); + } + else + { + RemoveTaskbarIcon(); + } + } + + #endregion + + #region DataContext dependency property override / target update + + /// + /// Updates the of a given + /// . This method only updates target elements + /// that do not already have a data context of their own, and either assigns + /// the of the NotifyIcon, or the + /// NotifyIcon itself, if no data context was assigned at all. + /// + private void UpdateDataContext(FrameworkElement target, object oldDataContextValue, object newDataContextValue) + { + //if there is no target or it's data context is determined through a binding + //of its own, keep it + if (target == null || target.IsDataContextDataBound()) return; + + //if the target's data context is the NotifyIcon's old DataContext or the NotifyIcon itself, + //update it + if (ReferenceEquals(this, target.DataContext) || Equals(oldDataContextValue, target.DataContext)) + { + //assign own data context, if available. If there is no data + //context at all, assign NotifyIcon itself. + target.DataContext = newDataContextValue ?? this; + } + } + + /// + /// A static callback listener which is being invoked if the + /// dependency property has + /// been changed. Invokes the + /// instance method of the changed instance. + /// + /// The currently processed owner of the property. + /// Provides information about the updated property. + private static void DataContextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + TaskbarIcon owner = (TaskbarIcon) d; + owner.OnDataContextPropertyChanged(e); + } + + + /// + /// Handles changes of the dependency property. As + /// WPF internally uses the dependency property system and bypasses the + /// property wrapper, updates of the property's value + /// should be handled here. + /// + /// Provides information about the updated property. + private void OnDataContextPropertyChanged(DependencyPropertyChangedEventArgs e) + { + object newValue = e.NewValue; + object oldValue = e.OldValue; + + //replace custom data context for ToolTips, Popup, and + //ContextMenu + UpdateDataContext(TrayPopupResolved, oldValue, newValue); + UpdateDataContext(TrayToolTipResolved, oldValue, newValue); + UpdateDataContext(ContextMenu, oldValue, newValue); + } + + #endregion + + #region ContextMenu dependency property override + + /// + /// A static callback listener which is being invoked if the + /// dependency property has + /// been changed. Invokes the + /// instance method of the changed instance. + /// + /// The currently processed owner of the property. + /// Provides information about the updated property. + private static void ContextMenuPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + TaskbarIcon owner = (TaskbarIcon) d; + owner.OnContextMenuPropertyChanged(e); + } + + + /// + /// Releases the old and updates the new property + /// in order to reflect both the NotifyIcon's + /// property and have the assigned. + /// + /// Provides information about the updated property. + private void OnContextMenuPropertyChanged(DependencyPropertyChangedEventArgs e) + { + if (e.OldValue != null) + { + //remove the taskbar icon reference from the previously used element + SetParentTaskbarIcon((DependencyObject) e.OldValue, null); + } + + if (e.NewValue != null) + { + //set this taskbar icon as a reference to the new tooltip element + SetParentTaskbarIcon((DependencyObject) e.NewValue, this); + } + + UpdateDataContext((ContextMenu) e.NewValue, null, DataContext); + } + + #endregion + + #region DoubleClickCommand dependency property + + /// + /// Associates a command that is being executed if the tray icon is being + /// double clicked. + /// + public static readonly DependencyProperty DoubleClickCommandProperty = + DependencyProperty.Register(nameof(DoubleClickCommand), + typeof (ICommand), + typeof (TaskbarIcon), + new FrameworkPropertyMetadata(null)); + + /// + /// A property wrapper for the + /// dependency property:
+ /// Associates a command that is being executed if the tray icon is being + /// double clicked. + ///
+ [Category(CategoryName)] + [Description("A command that is being executed if the tray icon is being double-clicked.")] + public ICommand DoubleClickCommand + { + get { return (ICommand) GetValue(DoubleClickCommandProperty); } + set { SetValue(DoubleClickCommandProperty, value); } + } + + #endregion + + #region DoubleClickCommandParameter dependency property + + /// + /// Command parameter for the . + /// + public static readonly DependencyProperty DoubleClickCommandParameterProperty = + DependencyProperty.Register(nameof(DoubleClickCommandParameter), + typeof (object), + typeof (TaskbarIcon), + new FrameworkPropertyMetadata(null)); + + /// + /// A property wrapper for the + /// dependency property:
+ /// Command parameter for the . + ///
+ [Category(CategoryName)] + [Description("Parameter to submit to the DoubleClickCommand when the user double clicks on the NotifyIcon.")] + public object DoubleClickCommandParameter + { + get { return GetValue(DoubleClickCommandParameterProperty); } + set { SetValue(DoubleClickCommandParameterProperty, value); } + } + + #endregion + + #region DoubleClickCommandTarget dependency property + + /// + /// The target of the command that is fired if the notify icon is double clicked. + /// + public static readonly DependencyProperty DoubleClickCommandTargetProperty = + DependencyProperty.Register(nameof(DoubleClickCommandTarget), + typeof (IInputElement), + typeof (TaskbarIcon), + new FrameworkPropertyMetadata(null)); + + /// + /// A property wrapper for the + /// dependency property:
+ /// The target of the command that is fired if the notify icon is double clicked. + ///
+ [Category(CategoryName)] + [Description("The target of the command that is fired if the notify icon is double clicked.")] + public IInputElement DoubleClickCommandTarget + { + get { return (IInputElement) GetValue(DoubleClickCommandTargetProperty); } + set { SetValue(DoubleClickCommandTargetProperty, value); } + } + + #endregion + + #region LeftClickCommand dependency property + + /// + /// Associates a command that is being executed if the tray icon is being + /// double clicked. + /// + public static readonly DependencyProperty LeftClickCommandProperty = + DependencyProperty.Register(nameof(LeftClickCommand), + typeof (ICommand), + typeof (TaskbarIcon), + new FrameworkPropertyMetadata(null)); + + /// + /// A property wrapper for the + /// dependency property:
+ /// Associates a command that is being executed if the tray icon is being + /// left-clicked. + ///
+ [Category(CategoryName)] + [Description("A command that is being executed if the tray icon is being left-clicked.")] + public ICommand LeftClickCommand + { + get { return (ICommand) GetValue(LeftClickCommandProperty); } + set { SetValue(LeftClickCommandProperty, value); } + } + + #endregion + + #region LeftClickCommandParameter dependency property + + /// + /// Command parameter for the . + /// + public static readonly DependencyProperty LeftClickCommandParameterProperty = + DependencyProperty.Register(nameof(LeftClickCommandParameter), + typeof (object), + typeof (TaskbarIcon), + new FrameworkPropertyMetadata(null)); + + /// + /// A property wrapper for the + /// dependency property:
+ /// Command parameter for the . + ///
+ [Category(CategoryName)] + [Description("The target of the command that is fired if the notify icon is clicked with the left mouse button." + )] + public object LeftClickCommandParameter + { + get { return GetValue(LeftClickCommandParameterProperty); } + set { SetValue(LeftClickCommandParameterProperty, value); } + } + + #endregion + + #region LeftClickCommandTarget dependency property + + /// + /// The target of the command that is fired if the notify icon is clicked. + /// + public static readonly DependencyProperty LeftClickCommandTargetProperty = + DependencyProperty.Register(nameof(LeftClickCommandTarget), + typeof (IInputElement), + typeof (TaskbarIcon), + new FrameworkPropertyMetadata(null)); + + /// + /// A property wrapper for the + /// dependency property:
+ /// The target of the command that is fired if the notify icon is clicked. + ///
+ [Category(CategoryName)] + [Description("The target of the command that is fired if the notify icon is clicked with the left mouse button." + )] + public IInputElement LeftClickCommandTarget + { + get { return (IInputElement) GetValue(LeftClickCommandTargetProperty); } + set { SetValue(LeftClickCommandTargetProperty, value); } + } + + #endregion + + #region NoLeftClickDelay dependency property + + /// + /// Set to true to make left clicks work without delay. + /// + public static readonly DependencyProperty NoLeftClickDelayProperty = + DependencyProperty.Register(nameof(NoLeftClickDelay), + typeof(bool), + typeof(TaskbarIcon), + new FrameworkPropertyMetadata(false)); + + + /// + /// A property wrapper for the + /// dependency property:
+ /// Set to true to make left clicks work without delay. + ///
+ [Category(CategoryName)] + [Description("Set to true to make left clicks work without delay.")] + public bool NoLeftClickDelay + { + get { return (bool)GetValue(NoLeftClickDelayProperty); } + set { SetValue(NoLeftClickDelayProperty, value); } + } + + #endregion + + //EVENTS + + #region TrayLeftMouseDown + + /// + /// TrayLeftMouseDown Routed Event + /// + public static readonly RoutedEvent TrayLeftMouseDownEvent = EventManager.RegisterRoutedEvent( + "TrayLeftMouseDown", + RoutingStrategy.Bubble, typeof (RoutedEventHandler), typeof (TaskbarIcon)); + + /// + /// Occurs when the user presses the left mouse button. + /// + [Category(CategoryName)] + public event RoutedEventHandler TrayLeftMouseDown + { + add { AddHandler(TrayLeftMouseDownEvent, value); } + remove { RemoveHandler(TrayLeftMouseDownEvent, value); } + } + + /// + /// A helper method to raise the TrayLeftMouseDown event. + /// + protected RoutedEventArgs RaiseTrayLeftMouseDownEvent() + { + RoutedEventArgs args = RaiseTrayLeftMouseDownEvent(this); + return args; + } + + /// + /// A static helper method to raise the TrayLeftMouseDown event on a target element. + /// + /// UIElement or ContentElement on which to raise the event + internal static RoutedEventArgs RaiseTrayLeftMouseDownEvent(DependencyObject target) + { + if (target == null) return null; + + RoutedEventArgs args = new RoutedEventArgs(TrayLeftMouseDownEvent); + RoutedEventHelper.RaiseEvent(target, args); + return args; + } + + #endregion + + #region TrayRightMouseDown + + /// + /// TrayRightMouseDown Routed Event + /// + public static readonly RoutedEvent TrayRightMouseDownEvent = + EventManager.RegisterRoutedEvent("TrayRightMouseDown", + RoutingStrategy.Bubble, typeof (RoutedEventHandler), typeof (TaskbarIcon)); + + /// + /// Occurs when the presses the right mouse button. + /// + public event RoutedEventHandler TrayRightMouseDown + { + add { AddHandler(TrayRightMouseDownEvent, value); } + remove { RemoveHandler(TrayRightMouseDownEvent, value); } + } + + /// + /// A helper method to raise the TrayRightMouseDown event. + /// + protected RoutedEventArgs RaiseTrayRightMouseDownEvent() + { + return RaiseTrayRightMouseDownEvent(this); + } + + /// + /// A static helper method to raise the TrayRightMouseDown event on a target element. + /// + /// UIElement or ContentElement on which to raise the event + internal static RoutedEventArgs RaiseTrayRightMouseDownEvent(DependencyObject target) + { + if (target == null) return null; + + RoutedEventArgs args = new RoutedEventArgs(TrayRightMouseDownEvent); + RoutedEventHelper.RaiseEvent(target, args); + return args; + } + + #endregion + + #region TrayMiddleMouseDown + + /// + /// TrayMiddleMouseDown Routed Event + /// + public static readonly RoutedEvent TrayMiddleMouseDownEvent = + EventManager.RegisterRoutedEvent("TrayMiddleMouseDown", + RoutingStrategy.Bubble, typeof (RoutedEventHandler), typeof (TaskbarIcon)); + + /// + /// Occurs when the user presses the middle mouse button. + /// + public event RoutedEventHandler TrayMiddleMouseDown + { + add { AddHandler(TrayMiddleMouseDownEvent, value); } + remove { RemoveHandler(TrayMiddleMouseDownEvent, value); } + } + + /// + /// A helper method to raise the TrayMiddleMouseDown event. + /// + protected RoutedEventArgs RaiseTrayMiddleMouseDownEvent() + { + return RaiseTrayMiddleMouseDownEvent(this); + } + + /// + /// A static helper method to raise the TrayMiddleMouseDown event on a target element. + /// + /// UIElement or ContentElement on which to raise the event + internal static RoutedEventArgs RaiseTrayMiddleMouseDownEvent(DependencyObject target) + { + if (target == null) return null; + + RoutedEventArgs args = new RoutedEventArgs(TrayMiddleMouseDownEvent); + RoutedEventHelper.RaiseEvent(target, args); + return args; + } + + #endregion + + #region TrayLeftMouseUp + + /// + /// TrayLeftMouseUp Routed Event + /// + public static readonly RoutedEvent TrayLeftMouseUpEvent = EventManager.RegisterRoutedEvent("TrayLeftMouseUp", + RoutingStrategy.Bubble, typeof (RoutedEventHandler), typeof (TaskbarIcon)); + + /// + /// Occurs when the user releases the left mouse button. + /// + public event RoutedEventHandler TrayLeftMouseUp + { + add { AddHandler(TrayLeftMouseUpEvent, value); } + remove { RemoveHandler(TrayLeftMouseUpEvent, value); } + } + + /// + /// A helper method to raise the TrayLeftMouseUp event. + /// + protected RoutedEventArgs RaiseTrayLeftMouseUpEvent() + { + return RaiseTrayLeftMouseUpEvent(this); + } + + /// + /// A static helper method to raise the TrayLeftMouseUp event on a target element. + /// + /// UIElement or ContentElement on which to raise the event + internal static RoutedEventArgs RaiseTrayLeftMouseUpEvent(DependencyObject target) + { + if (target == null) return null; + + RoutedEventArgs args = new RoutedEventArgs(TrayLeftMouseUpEvent); + RoutedEventHelper.RaiseEvent(target, args); + return args; + } + + #endregion + + #region TrayRightMouseUp + + /// + /// TrayRightMouseUp Routed Event + /// + public static readonly RoutedEvent TrayRightMouseUpEvent = EventManager.RegisterRoutedEvent("TrayRightMouseUp", + RoutingStrategy.Bubble, typeof (RoutedEventHandler), typeof (TaskbarIcon)); + + /// + /// Occurs when the user releases the right mouse button. + /// + public event RoutedEventHandler TrayRightMouseUp + { + add { AddHandler(TrayRightMouseUpEvent, value); } + remove { RemoveHandler(TrayRightMouseUpEvent, value); } + } + + /// + /// A helper method to raise the TrayRightMouseUp event. + /// + protected RoutedEventArgs RaiseTrayRightMouseUpEvent() + { + return RaiseTrayRightMouseUpEvent(this); + } + + /// + /// A static helper method to raise the TrayRightMouseUp event on a target element. + /// + /// UIElement or ContentElement on which to raise the event + internal static RoutedEventArgs RaiseTrayRightMouseUpEvent(DependencyObject target) + { + if (target == null) return null; + + RoutedEventArgs args = new RoutedEventArgs(TrayRightMouseUpEvent); + RoutedEventHelper.RaiseEvent(target, args); + return args; + } + + #endregion + + #region TrayMiddleMouseUp + + /// + /// TrayMiddleMouseUp Routed Event + /// + public static readonly RoutedEvent TrayMiddleMouseUpEvent = EventManager.RegisterRoutedEvent( + "TrayMiddleMouseUp", + RoutingStrategy.Bubble, typeof (RoutedEventHandler), typeof (TaskbarIcon)); + + /// + /// Occurs when the user releases the middle mouse button. + /// + public event RoutedEventHandler TrayMiddleMouseUp + { + add { AddHandler(TrayMiddleMouseUpEvent, value); } + remove { RemoveHandler(TrayMiddleMouseUpEvent, value); } + } + + /// + /// A helper method to raise the TrayMiddleMouseUp event. + /// + protected RoutedEventArgs RaiseTrayMiddleMouseUpEvent() + { + return RaiseTrayMiddleMouseUpEvent(this); + } + + /// + /// A static helper method to raise the TrayMiddleMouseUp event on a target element. + /// + /// UIElement or ContentElement on which to raise the event + internal static RoutedEventArgs RaiseTrayMiddleMouseUpEvent(DependencyObject target) + { + if (target == null) return null; + + RoutedEventArgs args = new RoutedEventArgs(TrayMiddleMouseUpEvent); + RoutedEventHelper.RaiseEvent(target, args); + return args; + } + + #endregion + + #region TrayMouseDoubleClick + + /// + /// TrayMouseDoubleClick Routed Event + /// + public static readonly RoutedEvent TrayMouseDoubleClickEvent = + EventManager.RegisterRoutedEvent("TrayMouseDoubleClick", + RoutingStrategy.Bubble, typeof (RoutedEventHandler), typeof (TaskbarIcon)); + + /// + /// Occurs when the user double-clicks the taskbar icon. + /// + public event RoutedEventHandler TrayMouseDoubleClick + { + add { AddHandler(TrayMouseDoubleClickEvent, value); } + remove { RemoveHandler(TrayMouseDoubleClickEvent, value); } + } + + /// + /// A helper method to raise the TrayMouseDoubleClick event. + /// + protected RoutedEventArgs RaiseTrayMouseDoubleClickEvent() + { + RoutedEventArgs args = RaiseTrayMouseDoubleClickEvent(this); + DoubleClickCommand.ExecuteIfEnabled(DoubleClickCommandParameter, DoubleClickCommandTarget ?? this); + return args; + } + + /// + /// A static helper method to raise the TrayMouseDoubleClick event on a target element. + /// + /// UIElement or ContentElement on which to raise the event + internal static RoutedEventArgs RaiseTrayMouseDoubleClickEvent(DependencyObject target) + { + if (target == null) return null; + + RoutedEventArgs args = new RoutedEventArgs(TrayMouseDoubleClickEvent); + RoutedEventHelper.RaiseEvent(target, args); + return args; + } + + #endregion + + #region TrayMouseMove + + /// + /// TrayMouseMove Routed Event + /// + public static readonly RoutedEvent TrayMouseMoveEvent = EventManager.RegisterRoutedEvent("TrayMouseMove", + RoutingStrategy.Bubble, typeof (RoutedEventHandler), typeof (TaskbarIcon)); + + /// + /// Occurs when the user moves the mouse over the taskbar icon. + /// + public event RoutedEventHandler TrayMouseMove + { + add { AddHandler(TrayMouseMoveEvent, value); } + remove { RemoveHandler(TrayMouseMoveEvent, value); } + } + + /// + /// A helper method to raise the TrayMouseMove event. + /// + protected RoutedEventArgs RaiseTrayMouseMoveEvent() + { + return RaiseTrayMouseMoveEvent(this); + } + + /// + /// A static helper method to raise the TrayMouseMove event on a target element. + /// + /// UIElement or ContentElement on which to raise the event + internal static RoutedEventArgs RaiseTrayMouseMoveEvent(DependencyObject target) + { + if (target == null) return null; + + RoutedEventArgs args = new RoutedEventArgs(TrayMouseMoveEvent); + RoutedEventHelper.RaiseEvent(target, args); + return args; + } + + #endregion + + #region TrayBalloonTipShown + + /// + /// TrayBalloonTipShown Routed Event + /// + public static readonly RoutedEvent TrayBalloonTipShownEvent = + EventManager.RegisterRoutedEvent("TrayBalloonTipShown", + RoutingStrategy.Bubble, typeof (RoutedEventHandler), typeof (TaskbarIcon)); + + /// + /// Occurs when a balloon ToolTip is displayed. + /// + public event RoutedEventHandler TrayBalloonTipShown + { + add { AddHandler(TrayBalloonTipShownEvent, value); } + remove { RemoveHandler(TrayBalloonTipShownEvent, value); } + } + + /// + /// A helper method to raise the TrayBalloonTipShown event. + /// + protected RoutedEventArgs RaiseTrayBalloonTipShownEvent() + { + return RaiseTrayBalloonTipShownEvent(this); + } + + /// + /// A static helper method to raise the TrayBalloonTipShown event on a target element. + /// + /// UIElement or ContentElement on which to raise the event + internal static RoutedEventArgs RaiseTrayBalloonTipShownEvent(DependencyObject target) + { + if (target == null) return null; + + RoutedEventArgs args = new RoutedEventArgs(TrayBalloonTipShownEvent); + RoutedEventHelper.RaiseEvent(target, args); + return args; + } + + #endregion + + #region TrayBalloonTipClosed + + /// + /// TrayBalloonTipClosed Routed Event + /// + public static readonly RoutedEvent TrayBalloonTipClosedEvent = + EventManager.RegisterRoutedEvent("TrayBalloonTipClosed", + RoutingStrategy.Bubble, typeof (RoutedEventHandler), typeof (TaskbarIcon)); + + /// + /// Occurs when a balloon ToolTip was closed. + /// + public event RoutedEventHandler TrayBalloonTipClosed + { + add { AddHandler(TrayBalloonTipClosedEvent, value); } + remove { RemoveHandler(TrayBalloonTipClosedEvent, value); } + } + + /// + /// A helper method to raise the TrayBalloonTipClosed event. + /// + protected RoutedEventArgs RaiseTrayBalloonTipClosedEvent() + { + return RaiseTrayBalloonTipClosedEvent(this); + } + + /// + /// A static helper method to raise the TrayBalloonTipClosed event on a target element. + /// + /// UIElement or ContentElement on which to raise the event + internal static RoutedEventArgs RaiseTrayBalloonTipClosedEvent(DependencyObject target) + { + if (target == null) return null; + + RoutedEventArgs args = new RoutedEventArgs(TrayBalloonTipClosedEvent); + RoutedEventHelper.RaiseEvent(target, args); + return args; + } + + #endregion + + #region TrayBalloonTipClicked + + /// + /// TrayBalloonTipClicked Routed Event + /// + public static readonly RoutedEvent TrayBalloonTipClickedEvent = + EventManager.RegisterRoutedEvent("TrayBalloonTipClicked", + RoutingStrategy.Bubble, typeof (RoutedEventHandler), typeof (TaskbarIcon)); + + /// + /// Occurs when the user clicks on a balloon ToolTip. + /// + public event RoutedEventHandler TrayBalloonTipClicked + { + add { AddHandler(TrayBalloonTipClickedEvent, value); } + remove { RemoveHandler(TrayBalloonTipClickedEvent, value); } + } + + /// + /// A helper method to raise the TrayBalloonTipClicked event. + /// + protected RoutedEventArgs RaiseTrayBalloonTipClickedEvent() + { + return RaiseTrayBalloonTipClickedEvent(this); + } + + /// + /// A static helper method to raise the TrayBalloonTipClicked event on a target element. + /// + /// UIElement or ContentElement on which to raise the event + internal static RoutedEventArgs RaiseTrayBalloonTipClickedEvent(DependencyObject target) + { + if (target == null) return null; + + RoutedEventArgs args = new RoutedEventArgs(TrayBalloonTipClickedEvent); + RoutedEventHelper.RaiseEvent(target, args); + return args; + } + + #endregion + + #region TrayContextMenuOpen (and PreviewTrayContextMenuOpen) + + /// + /// TrayContextMenuOpen Routed Event + /// + public static readonly RoutedEvent TrayContextMenuOpenEvent = + EventManager.RegisterRoutedEvent("TrayContextMenuOpen", + RoutingStrategy.Bubble, typeof (RoutedEventHandler), typeof (TaskbarIcon)); + + /// + /// Bubbled event that occurs when the context menu of the taskbar icon is being displayed. + /// + public event RoutedEventHandler TrayContextMenuOpen + { + add { AddHandler(TrayContextMenuOpenEvent, value); } + remove { RemoveHandler(TrayContextMenuOpenEvent, value); } + } + + /// + /// A helper method to raise the TrayContextMenuOpen event. + /// + protected RoutedEventArgs RaiseTrayContextMenuOpenEvent() + { + return RaiseTrayContextMenuOpenEvent(this); + } + + /// + /// A static helper method to raise the TrayContextMenuOpen event on a target element. + /// + /// UIElement or ContentElement on which to raise the event + internal static RoutedEventArgs RaiseTrayContextMenuOpenEvent(DependencyObject target) + { + if (target == null) return null; + + RoutedEventArgs args = new RoutedEventArgs(TrayContextMenuOpenEvent); + RoutedEventHelper.RaiseEvent(target, args); + return args; + } + + /// + /// PreviewTrayContextMenuOpen Routed Event + /// + public static readonly RoutedEvent PreviewTrayContextMenuOpenEvent = + EventManager.RegisterRoutedEvent("PreviewTrayContextMenuOpen", + RoutingStrategy.Tunnel, typeof (RoutedEventHandler), typeof (TaskbarIcon)); + + /// + /// Tunneled event that occurs when the context menu of the taskbar icon is being displayed. + /// + public event RoutedEventHandler PreviewTrayContextMenuOpen + { + add { AddHandler(PreviewTrayContextMenuOpenEvent, value); } + remove { RemoveHandler(PreviewTrayContextMenuOpenEvent, value); } + } + + /// + /// A helper method to raise the PreviewTrayContextMenuOpen event. + /// + protected RoutedEventArgs RaisePreviewTrayContextMenuOpenEvent() + { + return RaisePreviewTrayContextMenuOpenEvent(this); + } + + /// + /// A static helper method to raise the PreviewTrayContextMenuOpen event on a target element. + /// + /// UIElement or ContentElement on which to raise the event + internal static RoutedEventArgs RaisePreviewTrayContextMenuOpenEvent(DependencyObject target) + { + if (target == null) return null; + + RoutedEventArgs args = new RoutedEventArgs(PreviewTrayContextMenuOpenEvent); + RoutedEventHelper.RaiseEvent(target, args); + return args; + } + + #endregion + + #region TrayPopupOpen (and PreviewTrayPopupOpen) + + /// + /// TrayPopupOpen Routed Event + /// + public static readonly RoutedEvent TrayPopupOpenEvent = EventManager.RegisterRoutedEvent("TrayPopupOpen", + RoutingStrategy.Bubble, typeof (RoutedEventHandler), typeof (TaskbarIcon)); + + /// + /// Bubbled event that occurs when the custom popup is being opened. + /// + public event RoutedEventHandler TrayPopupOpen + { + add { AddHandler(TrayPopupOpenEvent, value); } + remove { RemoveHandler(TrayPopupOpenEvent, value); } + } + + /// + /// A helper method to raise the TrayPopupOpen event. + /// + protected RoutedEventArgs RaiseTrayPopupOpenEvent() + { + return RaiseTrayPopupOpenEvent(this); + } + + /// + /// A static helper method to raise the TrayPopupOpen event on a target element. + /// + /// UIElement or ContentElement on which to raise the event + internal static RoutedEventArgs RaiseTrayPopupOpenEvent(DependencyObject target) + { + if (target == null) return null; + + RoutedEventArgs args = new RoutedEventArgs(TrayPopupOpenEvent); + RoutedEventHelper.RaiseEvent(target, args); + return args; + } + + /// + /// PreviewTrayPopupOpen Routed Event + /// + public static readonly RoutedEvent PreviewTrayPopupOpenEvent = + EventManager.RegisterRoutedEvent("PreviewTrayPopupOpen", + RoutingStrategy.Tunnel, typeof (RoutedEventHandler), typeof (TaskbarIcon)); + + /// + /// Tunneled event that occurs when the custom popup is being opened. + /// + public event RoutedEventHandler PreviewTrayPopupOpen + { + add { AddHandler(PreviewTrayPopupOpenEvent, value); } + remove { RemoveHandler(PreviewTrayPopupOpenEvent, value); } + } + + /// + /// A helper method to raise the PreviewTrayPopupOpen event. + /// + protected RoutedEventArgs RaisePreviewTrayPopupOpenEvent() + { + return RaisePreviewTrayPopupOpenEvent(this); + } + + /// + /// A static helper method to raise the PreviewTrayPopupOpen event on a target element. + /// + /// UIElement or ContentElement on which to raise the event + internal static RoutedEventArgs RaisePreviewTrayPopupOpenEvent(DependencyObject target) + { + if (target == null) return null; + + RoutedEventArgs args = new RoutedEventArgs(PreviewTrayPopupOpenEvent); + RoutedEventHelper.RaiseEvent(target, args); + return args; + } + + #endregion + + #region TrayToolTipOpen (and PreviewTrayToolTipOpen) + + /// + /// TrayToolTipOpen Routed Event + /// + public static readonly RoutedEvent TrayToolTipOpenEvent = EventManager.RegisterRoutedEvent("TrayToolTipOpen", + RoutingStrategy.Bubble, typeof (RoutedEventHandler), typeof (TaskbarIcon)); + + /// + /// Bubbled event that occurs when the custom ToolTip is being displayed. + /// + public event RoutedEventHandler TrayToolTipOpen + { + add { AddHandler(TrayToolTipOpenEvent, value); } + remove { RemoveHandler(TrayToolTipOpenEvent, value); } + } + + /// + /// A helper method to raise the TrayToolTipOpen event. + /// + protected RoutedEventArgs RaiseTrayToolTipOpenEvent() + { + return RaiseTrayToolTipOpenEvent(this); + } + + /// + /// A static helper method to raise the TrayToolTipOpen event on a target element. + /// + /// UIElement or ContentElement on which to raise the event + internal static RoutedEventArgs RaiseTrayToolTipOpenEvent(DependencyObject target) + { + if (target == null) return null; + + RoutedEventArgs args = new RoutedEventArgs(TrayToolTipOpenEvent); + RoutedEventHelper.RaiseEvent(target, args); + return args; + } + + /// + /// PreviewTrayToolTipOpen Routed Event + /// + public static readonly RoutedEvent PreviewTrayToolTipOpenEvent = + EventManager.RegisterRoutedEvent("PreviewTrayToolTipOpen", + RoutingStrategy.Tunnel, typeof (RoutedEventHandler), typeof (TaskbarIcon)); + + /// + /// Tunneled event that occurs when the custom ToolTip is being displayed. + /// + public event RoutedEventHandler PreviewTrayToolTipOpen + { + add { AddHandler(PreviewTrayToolTipOpenEvent, value); } + remove { RemoveHandler(PreviewTrayToolTipOpenEvent, value); } + } + + /// + /// A helper method to raise the PreviewTrayToolTipOpen event. + /// + protected RoutedEventArgs RaisePreviewTrayToolTipOpenEvent() + { + return RaisePreviewTrayToolTipOpenEvent(this); + } + + /// + /// A static helper method to raise the PreviewTrayToolTipOpen event on a target element. + /// + /// UIElement or ContentElement on which to raise the event + internal static RoutedEventArgs RaisePreviewTrayToolTipOpenEvent(DependencyObject target) + { + if (target == null) return null; + + RoutedEventArgs args = new RoutedEventArgs(PreviewTrayToolTipOpenEvent); + RoutedEventHelper.RaiseEvent(target, args); + return args; + } + + #endregion + + #region TrayToolTipClose (and PreviewTrayToolTipClose) + + /// + /// TrayToolTipClose Routed Event + /// + public static readonly RoutedEvent TrayToolTipCloseEvent = EventManager.RegisterRoutedEvent("TrayToolTipClose", + RoutingStrategy.Bubble, typeof (RoutedEventHandler), typeof (TaskbarIcon)); + + /// + /// Bubbled event that occurs when a custom tooltip is being closed. + /// + public event RoutedEventHandler TrayToolTipClose + { + add { AddHandler(TrayToolTipCloseEvent, value); } + remove { RemoveHandler(TrayToolTipCloseEvent, value); } + } + + /// + /// A helper method to raise the TrayToolTipClose event. + /// + protected RoutedEventArgs RaiseTrayToolTipCloseEvent() + { + return RaiseTrayToolTipCloseEvent(this); + } + + /// + /// A static helper method to raise the TrayToolTipClose event on a target element. + /// + /// UIElement or ContentElement on which to raise the event + internal static RoutedEventArgs RaiseTrayToolTipCloseEvent(DependencyObject target) + { + if (target == null) return null; + + RoutedEventArgs args = new RoutedEventArgs(TrayToolTipCloseEvent); + RoutedEventHelper.RaiseEvent(target, args); + return args; + } + + /// + /// PreviewTrayToolTipClose Routed Event + /// + public static readonly RoutedEvent PreviewTrayToolTipCloseEvent = + EventManager.RegisterRoutedEvent("PreviewTrayToolTipClose", + RoutingStrategy.Tunnel, typeof (RoutedEventHandler), typeof (TaskbarIcon)); + + /// + /// Tunneled event that occurs when a custom tooltip is being closed. + /// + public event RoutedEventHandler PreviewTrayToolTipClose + { + add { AddHandler(PreviewTrayToolTipCloseEvent, value); } + remove { RemoveHandler(PreviewTrayToolTipCloseEvent, value); } + } + + /// + /// A helper method to raise the PreviewTrayToolTipClose event. + /// + protected RoutedEventArgs RaisePreviewTrayToolTipCloseEvent() + { + return RaisePreviewTrayToolTipCloseEvent(this); + } + + /// + /// A static helper method to raise the PreviewTrayToolTipClose event on a target element. + /// + /// UIElement or ContentElement on which to raise the event + internal static RoutedEventArgs RaisePreviewTrayToolTipCloseEvent(DependencyObject target) + { + if (target == null) return null; + + RoutedEventArgs args = new RoutedEventArgs(PreviewTrayToolTipCloseEvent); + RoutedEventHelper.RaiseEvent(target, args); + return args; + } + + #endregion + + //ATTACHED EVENTS + + #region PopupOpened + + /// + /// PopupOpened Attached Routed Event + /// + public static readonly RoutedEvent PopupOpenedEvent = EventManager.RegisterRoutedEvent("PopupOpened", + RoutingStrategy.Bubble, typeof (RoutedEventHandler), typeof (TaskbarIcon)); + + /// + /// Adds a handler for the PopupOpened attached event + /// + /// UIElement or ContentElement that listens to the event + /// Event handler to be added + public static void AddPopupOpenedHandler(DependencyObject element, RoutedEventHandler handler) + { + RoutedEventHelper.AddHandler(element, PopupOpenedEvent, handler); + } + + /// + /// Removes a handler for the PopupOpened attached event + /// + /// UIElement or ContentElement that listens to the event + /// Event handler to be removed + public static void RemovePopupOpenedHandler(DependencyObject element, RoutedEventHandler handler) + { + RoutedEventHelper.RemoveHandler(element, PopupOpenedEvent, handler); + } + + /// + /// A static helper method to raise the PopupOpened event on a target element. + /// + /// UIElement or ContentElement on which to raise the event + internal static RoutedEventArgs RaisePopupOpenedEvent(DependencyObject target) + { + if (target == null) return null; + + RoutedEventArgs args = new RoutedEventArgs(PopupOpenedEvent); + RoutedEventHelper.RaiseEvent(target, args); + return args; + } + + #endregion + + #region ToolTipOpened + + /// + /// ToolTipOpened Attached Routed Event + /// + public static readonly RoutedEvent ToolTipOpenedEvent = EventManager.RegisterRoutedEvent("ToolTipOpened", + RoutingStrategy.Bubble, typeof (RoutedEventHandler), typeof (TaskbarIcon)); + + /// + /// Adds a handler for the ToolTipOpened attached event + /// + /// UIElement or ContentElement that listens to the event + /// Event handler to be added + public static void AddToolTipOpenedHandler(DependencyObject element, RoutedEventHandler handler) + { + RoutedEventHelper.AddHandler(element, ToolTipOpenedEvent, handler); + } + + /// + /// Removes a handler for the ToolTipOpened attached event + /// + /// UIElement or ContentElement that listens to the event + /// Event handler to be removed + public static void RemoveToolTipOpenedHandler(DependencyObject element, RoutedEventHandler handler) + { + RoutedEventHelper.RemoveHandler(element, ToolTipOpenedEvent, handler); + } + + /// + /// A static helper method to raise the ToolTipOpened event on a target element. + /// + /// UIElement or ContentElement on which to raise the event + internal static RoutedEventArgs RaiseToolTipOpenedEvent(DependencyObject target) + { + if (target == null) return null; + + RoutedEventArgs args = new RoutedEventArgs(ToolTipOpenedEvent); + RoutedEventHelper.RaiseEvent(target, args); + return args; + } + + #endregion + + #region ToolTipClose + + /// + /// ToolTipClose Attached Routed Event + /// + public static readonly RoutedEvent ToolTipCloseEvent = EventManager.RegisterRoutedEvent("ToolTipClose", + RoutingStrategy.Bubble, typeof (RoutedEventHandler), typeof (TaskbarIcon)); + + /// + /// Adds a handler for the ToolTipClose attached event + /// + /// UIElement or ContentElement that listens to the event + /// Event handler to be added + public static void AddToolTipCloseHandler(DependencyObject element, RoutedEventHandler handler) + { + RoutedEventHelper.AddHandler(element, ToolTipCloseEvent, handler); + } + + /// + /// Removes a handler for the ToolTipClose attached event + /// + /// UIElement or ContentElement that listens to the event + /// Event handler to be removed + public static void RemoveToolTipCloseHandler(DependencyObject element, RoutedEventHandler handler) + { + RoutedEventHelper.RemoveHandler(element, ToolTipCloseEvent, handler); + } + + /// + /// A static helper method to raise the ToolTipClose event on a target element. + /// + /// UIElement or ContentElement on which to raise the event + internal static RoutedEventArgs RaiseToolTipCloseEvent(DependencyObject target) + { + if (target == null) return null; + + RoutedEventArgs args = new RoutedEventArgs(ToolTipCloseEvent); + RoutedEventHelper.RaiseEvent(target, args); + return args; + } + + #endregion + + #region BalloonShowing + + /// + /// BalloonShowing Attached Routed Event + /// + public static readonly RoutedEvent BalloonShowingEvent = EventManager.RegisterRoutedEvent("BalloonShowing", + RoutingStrategy.Bubble, typeof (RoutedEventHandler), typeof (TaskbarIcon)); + + /// + /// Adds a handler for the BalloonShowing attached event + /// + /// UIElement or ContentElement that listens to the event + /// Event handler to be added + public static void AddBalloonShowingHandler(DependencyObject element, RoutedEventHandler handler) + { + RoutedEventHelper.AddHandler(element, BalloonShowingEvent, handler); + } + + /// + /// Removes a handler for the BalloonShowing attached event + /// + /// UIElement or ContentElement that listens to the event + /// Event handler to be removed + public static void RemoveBalloonShowingHandler(DependencyObject element, RoutedEventHandler handler) + { + RoutedEventHelper.RemoveHandler(element, BalloonShowingEvent, handler); + } + + /// + /// A static helper method to raise the BalloonShowing event on a target element. + /// + /// UIElement or ContentElement on which to raise the event + /// The instance that manages the balloon. + internal static RoutedEventArgs RaiseBalloonShowingEvent(DependencyObject target, TaskbarIcon source) + { + if (target == null) return null; + + RoutedEventArgs args = new RoutedEventArgs(BalloonShowingEvent, source); + RoutedEventHelper.RaiseEvent(target, args); + return args; + } + + #endregion + + #region BalloonClosing + + /// + /// BalloonClosing Attached Routed Event + /// + public static readonly RoutedEvent BalloonClosingEvent = EventManager.RegisterRoutedEvent("BalloonClosing", + RoutingStrategy.Bubble, typeof (RoutedEventHandler), typeof (TaskbarIcon)); + + /// + /// Adds a handler for the BalloonClosing attached event + /// + /// UIElement or ContentElement that listens to the event + /// Event handler to be added + public static void AddBalloonClosingHandler(DependencyObject element, RoutedEventHandler handler) + { + RoutedEventHelper.AddHandler(element, BalloonClosingEvent, handler); + } + + /// + /// Removes a handler for the BalloonClosing attached event + /// + /// UIElement or ContentElement that listens to the event + /// Event handler to be removed + public static void RemoveBalloonClosingHandler(DependencyObject element, RoutedEventHandler handler) + { + RoutedEventHelper.RemoveHandler(element, BalloonClosingEvent, handler); + } + + /// + /// A static helper method to raise the BalloonClosing event on a target element. + /// + /// UIElement or ContentElement on which to raise the event + /// The instance that manages the balloon. + internal static RoutedEventArgs RaiseBalloonClosingEvent(DependencyObject target, TaskbarIcon source) + { + if (target == null) return null; + + RoutedEventArgs args = new RoutedEventArgs(BalloonClosingEvent, source); + RoutedEventHelper.RaiseEvent(target, args); + return args; + } + + #endregion + + //ATTACHED PROPERTIES + + #region ParentTaskbarIcon + + /// + /// An attached property that is assigned to displayed UI elements (balloons, tooltips, context menus), and + /// that can be used to bind to this control. The attached property is being derived, so binding is + /// quite straightforward: + /// + /// + /// + /// + public static readonly DependencyProperty ParentTaskbarIconProperty = + DependencyProperty.RegisterAttached("ParentTaskbarIcon", typeof (TaskbarIcon), typeof (TaskbarIcon), + new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits)); + + /// + /// Gets the ParentTaskbarIcon property. This dependency property + /// indicates .... + /// + public static TaskbarIcon GetParentTaskbarIcon(DependencyObject d) + { + return (TaskbarIcon) d.GetValue(ParentTaskbarIconProperty); + } + + /// + /// Sets the ParentTaskbarIcon property. This dependency property + /// indicates .... + /// + public static void SetParentTaskbarIcon(DependencyObject d, TaskbarIcon value) + { + d.SetValue(ParentTaskbarIconProperty, value); + } + + #endregion + + //BASE CLASS PROPERTY OVERRIDES + + /// + /// Registers properties. + /// + static TaskbarIcon() + { + //register change listener for the Visibility property + var md = new PropertyMetadata(Visibility.Visible, VisibilityPropertyChanged); + VisibilityProperty.OverrideMetadata(typeof (TaskbarIcon), md); + + //register change listener for the DataContext property + md = new FrameworkPropertyMetadata(DataContextPropertyChanged); + DataContextProperty.OverrideMetadata(typeof (TaskbarIcon), md); + + //register change listener for the ContextMenu property + md = new FrameworkPropertyMetadata(ContextMenuPropertyChanged); + ContextMenuProperty.OverrideMetadata(typeof (TaskbarIcon), md); + } + } +} \ No newline at end of file diff --git a/NotifyIconWpf/NotifyIconWpf/TaskbarIcon.cs b/NotifyIconWpf/NotifyIconWpf/TaskbarIcon.cs new file mode 100644 index 0000000..33bf8e3 --- /dev/null +++ b/NotifyIconWpf/NotifyIconWpf/TaskbarIcon.cs @@ -0,0 +1,1103 @@ +// hardcodet.net NotifyIcon for WPF +// Copyright (c) 2009 - 2020 Philipp Sumi +// Contact and Information: http://www.hardcodet.net +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the Code Project Open License (CPOL); +// either version 1.0 of the License, or (at your option) any later +// version. +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// THIS COPYRIGHT NOTICE MAY NOT BE REMOVED FROM THIS FILE + + +using System; +using System.Diagnostics; +using System.Drawing; +using System.Threading; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Interop; +using System.Windows.Threading; +using Hardcodet.Wpf.TaskbarNotification.Interop; +using Point = Hardcodet.Wpf.TaskbarNotification.Interop.Point; + + +namespace Hardcodet.Wpf.TaskbarNotification +{ + /// + /// A WPF proxy to for a taskbar icon (NotifyIcon) that sits in the system's + /// taskbar notification area ("system tray"). + /// + public partial class TaskbarIcon : FrameworkElement, IDisposable + { + private readonly object lockObject = new object(); + + #region Members + + /// + /// Represents the current icon data. + /// + private NotifyIconData iconData; + + /// + /// Receives messages from the taskbar icon. + /// + private readonly WindowMessageSink messageSink; + + /// + /// An action that is being invoked if the + /// fires. + /// + private Action singleClickTimerAction; + + /// + /// A timer that is used to differentiate between single + /// and double clicks. + /// + private readonly Timer singleClickTimer; + + /// + /// The time we should wait for a double click. + /// + private int DoubleClickWaitTime => NoLeftClickDelay ? 0 : WinApi.GetDoubleClickTime(); + + /// + /// A timer that is used to close open balloon tooltips. + /// + private readonly Timer balloonCloseTimer; + + /// + /// Indicates whether the taskbar icon has been created or not. + /// + public bool IsTaskbarIconCreated { get; private set; } + + /// + /// Indicates whether custom tooltips are supported, which depends + /// on the OS. Windows Vista or higher is required in order to + /// support this feature. + /// + public bool SupportsCustomToolTips => messageSink.Version == NotifyIconVersion.Vista; + + + /// + /// Checks whether a non-tooltip popup is currently opened. + /// + private bool IsPopupOpen + { + get + { + var popup = TrayPopupResolved; + var menu = ContextMenu; + var balloon = CustomBalloon; + + return popup != null && popup.IsOpen || + menu != null && menu.IsOpen || + balloon != null && balloon.IsOpen; + } + } + + #endregion + + #region Construction + + /// + /// Initializes the taskbar icon and registers a message listener + /// in order to receive events from the taskbar area. + /// + public TaskbarIcon() + { + // using dummy sink in design mode + messageSink = Util.IsDesignMode + ? WindowMessageSink.CreateEmpty() + : new WindowMessageSink(NotifyIconVersion.Win95); + + // init icon data structure + iconData = NotifyIconData.CreateDefault(messageSink.MessageWindowHandle); + + // create the taskbar icon + CreateTaskbarIcon(); + + // register event listeners + messageSink.MouseEventReceived += OnMouseEvent; + messageSink.TaskbarCreated += OnTaskbarCreated; + messageSink.ChangeToolTipStateRequest += OnToolTipChange; + messageSink.BalloonToolTipChanged += OnBalloonToolTipChanged; + + // init single click / balloon timers + singleClickTimer = new Timer(DoSingleClickAction); + balloonCloseTimer = new Timer(CloseBalloonCallback); + + // register listener in order to get notified when the application closes + if (Application.Current != null) + { + Application.Current.Exit += OnExit; + } + } + + #endregion + + #region Custom Balloons + /// + /// A delegate to handle customer popup positions. + /// + public delegate Point GetCustomPopupPosition(); + + /// + /// Specify a custom popup position + /// + public GetCustomPopupPosition CustomPopupPosition { get; set; } + + /// + /// Returns the location of the system tray + /// + /// Point + public Point GetPopupTrayPosition() + { + return TrayInfo.GetTrayLocation(); + } + + /// + /// Shows a custom control as a tooltip in the tray location. + /// + /// + /// An optional animation for the popup. + /// The time after which the popup is being closed. + /// Submit null in order to keep the balloon open indefinitely + /// + /// If + /// is a null reference. + public void ShowCustomBalloon(UIElement balloon, PopupAnimation animation, int? timeout) + { + var dispatcher = this.GetDispatcher(); + if (!dispatcher.CheckAccess()) + { + var action = new Action(() => ShowCustomBalloon(balloon, animation, timeout)); + dispatcher.Invoke(DispatcherPriority.Normal, action); + return; + } + + if (balloon == null) throw new ArgumentNullException(nameof(balloon)); + if (timeout.HasValue && timeout < 500) + { + string msg = "Invalid timeout of {0} milliseconds. Timeout must be at least 500 ms"; + msg = string.Format(msg, timeout); + throw new ArgumentOutOfRangeException(nameof(timeout), msg); + } + + EnsureNotDisposed(); + + // make sure we don't have an open balloon + lock (lockObject) + { + CloseBalloon(); + } + + // create an invisible popup that hosts the UIElement + Popup popup = new Popup + { + AllowsTransparency = true + }; + + // provide the popup with the taskbar icon's data context + UpdateDataContext(popup, null, DataContext); + + // don't animate by default - developers can use attached events or override + popup.PopupAnimation = animation; + + // in case the balloon is cleaned up through routed events, the + // control didn't remove the balloon from its parent popup when + // if was closed the last time - just make sure it doesn't have + // a parent that is a popup + var parent = LogicalTreeHelper.GetParent(balloon) as Popup; + if (parent != null) parent.Child = null; + + if (parent != null) + { + string msg = "Cannot display control [{0}] in a new balloon popup - that control already has a parent. You may consider creating new balloons every time you want to show one."; + msg = string.Format(msg, balloon); + throw new InvalidOperationException(msg); + } + + popup.Child = balloon; + + //don't set the PlacementTarget as it causes the popup to become hidden if the + //TaskbarIcon's parent is hidden, too... + //popup.PlacementTarget = this; + + popup.Placement = PlacementMode.AbsolutePoint; + popup.StaysOpen = true; + + + Point position = CustomPopupPosition != null ? CustomPopupPosition() : GetPopupTrayPosition(); + popup.HorizontalOffset = position.X - 1; + popup.VerticalOffset = position.Y - 1; + + //store reference + lock (lockObject) + { + SetCustomBalloon(popup); + } + + // assign this instance as an attached property + SetParentTaskbarIcon(balloon, this); + + // fire attached event + RaiseBalloonShowingEvent(balloon, this); + + // display item + popup.IsOpen = true; + + if (timeout.HasValue) + { + // register timer to close the popup + balloonCloseTimer.Change(timeout.Value, Timeout.Infinite); + } + } + + + /// + /// Resets the closing timeout, which effectively + /// keeps a displayed balloon message open until + /// it is either closed programmatically through + /// or due to a new + /// message being displayed. + /// + public void ResetBalloonCloseTimer() + { + if (IsDisposed) return; + + lock (lockObject) + { + //reset timer in any case + balloonCloseTimer.Change(Timeout.Infinite, Timeout.Infinite); + } + } + + + /// + /// Closes the current , if the + /// property is set. + /// + public void CloseBalloon() + { + if (IsDisposed) return; + + Dispatcher dispatcher = this.GetDispatcher(); + if (!dispatcher.CheckAccess()) + { + Action action = CloseBalloon; + dispatcher.Invoke(DispatcherPriority.Normal, action); + return; + } + + lock (lockObject) + { + // reset timer in any case + balloonCloseTimer.Change(Timeout.Infinite, Timeout.Infinite); + + // reset old popup, if we still have one + Popup popup = CustomBalloon; + if (popup == null) + { + return; + } + + UIElement element = popup.Child; + + // announce closing + RoutedEventArgs eventArgs = RaiseBalloonClosingEvent(element, this); + if (!eventArgs.Handled) + { + // if the event was handled, clear the reference to the popup, + // but don't close it - the handling code has to manage this stuff now + + // close the popup + popup.IsOpen = false; + + // remove the reference of the popup to the balloon in case we want to reuse + // the balloon (then added to a new popup) + popup.Child = null; + + // reset attached property + if (element != null) SetParentTaskbarIcon(element, null); + } + + // remove custom balloon anyway + SetCustomBalloon(null); + } + } + + + /// + /// Timer-invoke event which closes the currently open balloon and + /// resets the dependency property. + /// + private void CloseBalloonCallback(object state) + { + if (IsDisposed) return; + + // switch to UI thread + Action action = CloseBalloon; + this.GetDispatcher().Invoke(action); + } + + #endregion + + #region Process Incoming Mouse Events + + /// + /// Processes mouse events, which are bubbled + /// through the class' routed events, trigger + /// certain actions (e.g. show a popup), or + /// both. + /// + /// Event flag. + private void OnMouseEvent(MouseEvent me) + { + if (IsDisposed) return; + + switch (me) + { + case MouseEvent.MouseMove: + RaiseTrayMouseMoveEvent(); + // immediately return - there's nothing left to evaluate + return; + case MouseEvent.IconRightMouseDown: + RaiseTrayRightMouseDownEvent(); + break; + case MouseEvent.IconLeftMouseDown: + RaiseTrayLeftMouseDownEvent(); + break; + case MouseEvent.IconRightMouseUp: + RaiseTrayRightMouseUpEvent(); + break; + case MouseEvent.IconLeftMouseUp: + RaiseTrayLeftMouseUpEvent(); + break; + case MouseEvent.IconMiddleMouseDown: + RaiseTrayMiddleMouseDownEvent(); + break; + case MouseEvent.IconMiddleMouseUp: + RaiseTrayMiddleMouseUpEvent(); + break; + case MouseEvent.IconDoubleClick: + // cancel single click timer + singleClickTimer.Change(Timeout.Infinite, Timeout.Infinite); + // bubble event + RaiseTrayMouseDoubleClickEvent(); + break; + case MouseEvent.BalloonToolTipClicked: + RaiseTrayBalloonTipClickedEvent(); + break; + default: + throw new ArgumentOutOfRangeException(nameof(me), "Missing handler for mouse event flag: " + me); + } + + + // get mouse coordinates + Point cursorPosition = new Point(); + if (messageSink.Version == NotifyIconVersion.Vista) + { + // physical cursor position is supported for Vista and above + WinApi.GetPhysicalCursorPos(ref cursorPosition); + } + else + { + WinApi.GetCursorPos(ref cursorPosition); + } + + cursorPosition = TrayInfo.GetDeviceCoordinates(cursorPosition); + + bool isLeftClickCommandInvoked = false; + + // show popup, if requested + if (me.IsMatch(PopupActivation)) + { + if (me == MouseEvent.IconLeftMouseUp) + { + // show popup once we are sure it's not a double click + singleClickTimerAction = () => + { + LeftClickCommand.ExecuteIfEnabled(LeftClickCommandParameter, LeftClickCommandTarget ?? this); + ShowTrayPopup(cursorPosition); + }; + singleClickTimer.Change(DoubleClickWaitTime, Timeout.Infinite); + isLeftClickCommandInvoked = true; + } + else + { + // show popup immediately + ShowTrayPopup(cursorPosition); + } + } + + + // show context menu, if requested + if (me.IsMatch(MenuActivation)) + { + if (me == MouseEvent.IconLeftMouseUp) + { + // show context menu once we are sure it's not a double click + singleClickTimerAction = () => + { + LeftClickCommand.ExecuteIfEnabled(LeftClickCommandParameter, LeftClickCommandTarget ?? this); + ShowContextMenu(cursorPosition); + }; + singleClickTimer.Change(DoubleClickWaitTime, Timeout.Infinite); + isLeftClickCommandInvoked = true; + } + else + { + // show context menu immediately + ShowContextMenu(cursorPosition); + } + } + + // make sure the left click command is invoked on mouse clicks + if (me == MouseEvent.IconLeftMouseUp && !isLeftClickCommandInvoked) + { + // show context menu once we are sure it's not a double click + singleClickTimerAction = + () => + { + LeftClickCommand.ExecuteIfEnabled(LeftClickCommandParameter, LeftClickCommandTarget ?? this); + }; + singleClickTimer.Change(DoubleClickWaitTime, Timeout.Infinite); + } + } + + #endregion + + #region ToolTips + + /// + /// Displays a custom tooltip, if available. This method is only + /// invoked for Windows Vista and above. + /// + /// Whether to show or hide the tooltip. + private void OnToolTipChange(bool visible) + { + // if we don't have a tooltip, there's nothing to do here... + if (TrayToolTipResolved == null) return; + + if (visible) + { + if (IsPopupOpen) + { + // ignore if we are already displaying something down there + return; + } + + var args = RaisePreviewTrayToolTipOpenEvent(); + if (args.Handled) return; + + TrayToolTipResolved.IsOpen = true; + + // raise attached event first + if (TrayToolTip != null) RaiseToolTipOpenedEvent(TrayToolTip); + + // bubble routed event + RaiseTrayToolTipOpenEvent(); + } + else + { + var args = RaisePreviewTrayToolTipCloseEvent(); + if (args.Handled) return; + + // raise attached event first + if (TrayToolTip != null) RaiseToolTipCloseEvent(TrayToolTip); + + TrayToolTipResolved.IsOpen = false; + + // bubble event + RaiseTrayToolTipCloseEvent(); + } + } + + + /// + /// Creates a control that either + /// wraps the currently set + /// control or the string.
+ /// If itself is already + /// a instance, it will be used directly. + ///
+ /// We use a rather than + /// because there was no way to prevent a + /// popup from causing cyclic open/close commands if it was + /// placed under the mouse. ToolTip internally uses a Popup of + /// its own, but takes advance of Popup's internal + /// property which prevents this issue. + private void CreateCustomToolTip() + { + // check if the item itself is a tooltip + ToolTip tt = TrayToolTip as ToolTip; + + if (tt == null && TrayToolTip != null) + { + // create an invisible wrapper tooltip that hosts the UIElement + tt = new ToolTip + { + Placement = PlacementMode.Mouse, + // do *not* set the placement target, as it causes the popup to become hidden if the + // TaskbarIcon's parent is hidden, too. At runtime, the parent can be resolved through + // the ParentTaskbarIcon attached dependency property: + // PlacementTarget = this; + + // make sure the tooltip is invisible + HasDropShadow = false, + BorderThickness = new Thickness(0), + Background = System.Windows.Media.Brushes.Transparent, + // setting the + StaysOpen = true, + Content = TrayToolTip + }; + } + else if (tt == null && !string.IsNullOrEmpty(ToolTipText)) + { + // create a simple tooltip for the ToolTipText string + tt = new ToolTip + { + Content = ToolTipText + }; + } + + // the tooltip explicitly gets the DataContext of this instance. + // If there is no DataContext, the TaskbarIcon assigns itself + if (tt != null) + { + UpdateDataContext(tt, null, DataContext); + } + + // store a reference to the used tooltip + SetTrayToolTipResolved(tt); + } + + + /// + /// Sets tooltip settings for the class depending on defined + /// dependency properties and OS support. + /// + private void WriteToolTipSettings() + { + const IconDataMembers flags = IconDataMembers.Tip; + iconData.ToolTipText = ToolTipText; + + if (messageSink.Version == NotifyIconVersion.Vista) + { + // we need to set a tooltip text to get tooltip events from the + // taskbar icon + if (string.IsNullOrEmpty(iconData.ToolTipText) && TrayToolTipResolved != null) + { + // if we have not tooltip text but a custom tooltip, we + // need to set a dummy value (we're displaying the ToolTip control, not the string) + iconData.ToolTipText = "ToolTip"; + } + } + + // update the tooltip text + Util.WriteIconData(ref iconData, NotifyCommand.Modify, flags); + } + + #endregion + + #region Custom Popup + + /// + /// Creates a control that either + /// wraps the currently set + /// control or the string.
+ /// If itself is already + /// a instance, it will be used directly. + ///
+ /// We use a rather than + /// because there was no way to prevent a + /// popup from causing cyclic open/close commands if it was + /// placed under the mouse. ToolTip internally uses a Popup of + /// its own, but takes advance of Popup's internal + /// property which prevents this issue. + private void CreatePopup() + { + // check if the item itself is a popup + Popup popup = TrayPopup as Popup; + + if (popup == null && TrayPopup != null) + { + // create an invisible popup that hosts the UIElement + popup = new Popup + { + AllowsTransparency = true, + // don't animate by default - developers can use attached events or override + PopupAnimation = PopupAnimation.None, + // the CreateRootPopup method outputs binding errors in the debug window because + // it tries to bind to "Popup-specific" properties in case they are provided by the child. + // We don't need that so just assign the control as the child. + Child = TrayPopup, + // do *not* set the placement target, as it causes the popup to become hidden if the + // TaskbarIcon's parent is hidden, too. At runtime, the parent can be resolved through + // the ParentTaskbarIcon attached dependency property: + // PlacementTarget = this; + + Placement = PlacementMode.AbsolutePoint, + StaysOpen = false + }; + } + + // the popup explicitly gets the DataContext of this instance. + // If there is no DataContext, the TaskbarIcon assigns itself + if (popup != null) + { + UpdateDataContext(popup, null, DataContext); + } + + // store a reference to the used tooltip + SetTrayPopupResolved(popup); + } + + + /// + /// Displays the control if it was set. + /// + private void ShowTrayPopup(Point cursorPosition) + { + if (IsDisposed) return; + + // raise preview event no matter whether popup is currently set + // or not (enables client to set it on demand) + var args = RaisePreviewTrayPopupOpenEvent(); + if (args.Handled) return; + + if (TrayPopup == null) + { + return; + } + + // use absolute position, but place the popup centered above the icon + TrayPopupResolved.Placement = PlacementMode.AbsolutePoint; + TrayPopupResolved.HorizontalOffset = cursorPosition.X; + TrayPopupResolved.VerticalOffset = cursorPosition.Y; + + // open popup + TrayPopupResolved.IsOpen = true; + + IntPtr handle = IntPtr.Zero; + if (TrayPopupResolved.Child != null) + { + // try to get a handle on the popup itself (via its child) + HwndSource source = (HwndSource)PresentationSource.FromVisual(TrayPopupResolved.Child); + if (source != null) handle = source.Handle; + } + + // if we don't have a handle for the popup, fall back to the message sink + if (handle == IntPtr.Zero) handle = messageSink.MessageWindowHandle; + + // activate either popup or message sink to track deactivation. + // otherwise, the popup does not close if the user clicks somewhere else + WinApi.SetForegroundWindow(handle); + + // raise attached event - item should never be null unless developers + // changed the CustomPopup directly... + if (TrayPopup != null) RaisePopupOpenedEvent(TrayPopup); + + // bubble routed event + RaiseTrayPopupOpenEvent(); + } + + #endregion + + #region Context Menu + + /// + /// Displays the if it was set. + /// + private void ShowContextMenu(Point cursorPosition) + { + if (IsDisposed) return; + + // raise preview event no matter whether context menu is currently set + // or not (enables client to set it on demand) + var args = RaisePreviewTrayContextMenuOpenEvent(); + if (args.Handled) return; + + if (ContextMenu == null) + { + return; + } + + // use absolute positioning. We need to set the coordinates, or a delayed opening + // (e.g. when left-clicked) opens the context menu at the wrong place if the mouse + // is moved! + ContextMenu.Placement = PlacementMode.AbsolutePoint; + ContextMenu.HorizontalOffset = cursorPosition.X; + ContextMenu.VerticalOffset = cursorPosition.Y; + ContextMenu.IsOpen = true; + + IntPtr handle = IntPtr.Zero; + + // try to get a handle on the context itself + HwndSource source = (HwndSource)PresentationSource.FromVisual(ContextMenu); + if (source != null) + { + handle = source.Handle; + } + + // if we don't have a handle for the popup, fall back to the message sink + if (handle == IntPtr.Zero) handle = messageSink.MessageWindowHandle; + + // activate the context menu or the message window to track deactivation - otherwise, the context menu + // does not close if the user clicks somewhere else. With the message window + // fallback, the context menu can't receive keyboard events - should not happen though + WinApi.SetForegroundWindow(handle); + + // bubble event + RaiseTrayContextMenuOpenEvent(); + } + + #endregion + + #region Balloon Tips + + /// + /// Bubbles events if a balloon ToolTip was displayed + /// or removed. + /// + /// Whether the ToolTip was just displayed + /// or removed. + private void OnBalloonToolTipChanged(bool visible) + { + if (visible) + { + RaiseTrayBalloonTipShownEvent(); + } + else + { + RaiseTrayBalloonTipClosedEvent(); + } + } + + /// + /// Displays a balloon tip with the specified title, + /// text, and icon in the taskbar for the specified time period. + /// + /// The title to display on the balloon tip. + /// The text to display on the balloon tip. + /// A symbol that indicates the severity. + public void ShowBalloonTip(string title, string message, BalloonIcon symbol) + { + lock (lockObject) + { + ShowBalloonTip(title, message, symbol.GetBalloonFlag(), IntPtr.Zero); + } + } + + /// + /// Displays a balloon tip with the specified title, + /// text, and a custom icon in the taskbar for the specified time period. + /// + /// The title to display on the balloon tip. + /// The text to display on the balloon tip. + /// A custom icon. + /// True to allow large icons (Windows Vista and later). + /// If + /// is a null reference. + public void ShowBalloonTip(string title, string message, Icon customIcon, bool largeIcon = false) + { + if (customIcon == null) throw new ArgumentNullException(nameof(customIcon)); + + lock (lockObject) + { + var flags = BalloonFlags.User; + + if (largeIcon) + { + // ReSharper disable once BitwiseOperatorOnEnumWithoutFlags + flags |= BalloonFlags.LargeIcon; + } + + ShowBalloonTip(title, message, flags, customIcon.Handle); + } + } + + + /// + /// Invokes in order to display + /// a given balloon ToolTip. + /// + /// The title to display on the balloon tip. + /// The text to display on the balloon tip. + /// Indicates what icon to use. + /// A handle to a custom icon, if any, or + /// . + private void ShowBalloonTip(string title, string message, BalloonFlags flags, IntPtr balloonIconHandle) + { + EnsureNotDisposed(); + + iconData.BalloonText = message ?? string.Empty; + iconData.BalloonTitle = title ?? string.Empty; + + iconData.BalloonFlags = flags; + iconData.CustomBalloonIconHandle = balloonIconHandle; + Util.WriteIconData(ref iconData, NotifyCommand.Modify, IconDataMembers.Info | IconDataMembers.Icon); + } + + + /// + /// Hides a balloon ToolTip, if any is displayed. + /// + public void HideBalloonTip() + { + EnsureNotDisposed(); + + // reset balloon by just setting the info to an empty string + iconData.BalloonText = iconData.BalloonTitle = string.Empty; + Util.WriteIconData(ref iconData, NotifyCommand.Modify, IconDataMembers.Info); + } + + #endregion + + #region Single Click Timer event + + /// + /// Performs a delayed action if the user requested an action + /// based on a single click of the left mouse.
+ /// This method is invoked by the . + ///
+ private void DoSingleClickAction(object state) + { + if (IsDisposed) return; + + // run action + Action action = singleClickTimerAction; + if (action != null) + { + // cleanup action + singleClickTimerAction = null; + + // switch to UI thread + this.GetDispatcher().Invoke(action); + } + } + + #endregion + + #region Set Version (API) + + /// + /// Sets the version flag for the . + /// + private void SetVersion() + { + iconData.VersionOrTimeout = (uint)NotifyIconVersion.Vista; + bool status = WinApi.Shell_NotifyIcon(NotifyCommand.SetVersion, ref iconData); + + if (!status) + { + iconData.VersionOrTimeout = (uint)NotifyIconVersion.Win2000; + status = Util.WriteIconData(ref iconData, NotifyCommand.SetVersion); + } + + if (!status) + { + iconData.VersionOrTimeout = (uint)NotifyIconVersion.Win95; + status = Util.WriteIconData(ref iconData, NotifyCommand.SetVersion); + } + + if (!status) + { + Debug.Fail("Could not set version"); + } + } + + #endregion + + #region Create / Remove Taskbar Icon + + /// + /// Recreates the taskbar icon if the whole taskbar was + /// recreated (e.g. because Explorer was shut down). + /// + private void OnTaskbarCreated() + { + RemoveTaskbarIcon(); + CreateTaskbarIcon(); + } + + + /// + /// Creates the taskbar icon. This message is invoked during initialization, + /// if the taskbar is restarted, and whenever the icon is displayed. + /// + private void CreateTaskbarIcon() + { + lock (lockObject) + { + if (IsTaskbarIconCreated) + { + return; + } + + const IconDataMembers members = IconDataMembers.Message + | IconDataMembers.Icon + | IconDataMembers.Tip; + + //write initial configuration + var status = Util.WriteIconData(ref iconData, NotifyCommand.Add, members); + if (!status) + { + // couldn't create the icon - we can assume this is because explorer is not running (yet!) + // -> try a bit later again rather than throwing an exception. Typically, if the windows + // shell is being loaded later, this method is being re-invoked from OnTaskbarCreated + // (we could also retry after a delay, but that's currently YAGNI) + return; + } + + //set to most recent version + SetVersion(); + messageSink.Version = (NotifyIconVersion)iconData.VersionOrTimeout; + + IsTaskbarIconCreated = true; + } + } + + /// + /// Closes the taskbar icon if required. + /// + private void RemoveTaskbarIcon() + { + lock (lockObject) + { + // make sure we didn't schedule a creation + + if (!IsTaskbarIconCreated) + { + return; + } + + Util.WriteIconData(ref iconData, NotifyCommand.Delete, IconDataMembers.Message); + IsTaskbarIconCreated = false; + } + } + + #endregion + + + + #region Dispose / Exit + + /// + /// Set to true as soon as Dispose has been invoked. + /// + public bool IsDisposed { get; private set; } + + + /// + /// Checks if the object has been disposed and + /// raises a in case + /// the flag is true. + /// + private void EnsureNotDisposed() + { + if (IsDisposed) throw new ObjectDisposedException(Name ?? GetType().FullName); + } + + + /// + /// Disposes the class if the application exits. + /// + private void OnExit(object sender, EventArgs e) + { + Dispose(); + } + + + /// + /// This destructor will run only if the + /// method does not get called. This gives this base class the + /// opportunity to finalize. + /// + /// Important: Do not provide destructor in types derived from this class. + /// + /// + ~TaskbarIcon() + { + Dispose(false); + } + + + /// + /// Disposes the object. + /// + /// This method is not virtual by design. Derived classes + /// should override . + /// + public void Dispose() + { + Dispose(true); + + // This object will be cleaned up by the Dispose method. + // Therefore, you should call GC.SuppressFinalize to + // take this object off the finalization queue + // and prevent finalization code for this object + // from executing a second time. + GC.SuppressFinalize(this); + } + + + /// + /// Closes the tray and releases all resources. + /// + /// + /// Dispose(bool disposing) executes in two distinct scenarios. + /// If disposing equals true, the method has been called directly + /// or indirectly by a user's code. Managed and unmanaged resources + /// can be disposed. + /// + /// If disposing equals false, the method + /// has been called by the runtime from inside the finalizer and you + /// should not reference other objects. Only unmanaged resources can + /// be disposed. + /// Check the property to determine whether + /// the method has already been called. + private void Dispose(bool disposing) + { + // don't do anything if the component is already disposed + if (IsDisposed || !disposing) return; + + lock (lockObject) + { + IsDisposed = true; + + // de-register application event listener + if (Application.Current != null) + { + Application.Current.Exit -= OnExit; + } + + // stop timers + singleClickTimer.Dispose(); + balloonCloseTimer.Dispose(); + + // dispose message sink + messageSink.Dispose(); + + // remove icon + RemoveTaskbarIcon(); + } + } + + #endregion + } +} \ No newline at end of file diff --git a/NotifyIconWpf/NotifyIconWpf/Util.cs b/NotifyIconWpf/NotifyIconWpf/Util.cs new file mode 100644 index 0000000..22fa7a3 --- /dev/null +++ b/NotifyIconWpf/NotifyIconWpf/Util.cs @@ -0,0 +1,309 @@ +// hardcodet.net NotifyIcon for WPF +// Copyright (c) 2009 - 2020 Philipp Sumi +// Contact and Information: http://www.hardcodet.net +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the Code Project Open License (CPOL); +// either version 1.0 of the License, or (at your option) any later +// version. +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// THIS COPYRIGHT NOTICE MAY NOT BE REMOVED FROM THIS FILE + + +using System; +using System.ComponentModel; +using System.Drawing; +using System.Windows; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Resources; +using System.Windows.Threading; +using Hardcodet.Wpf.TaskbarNotification.Interop; + +namespace Hardcodet.Wpf.TaskbarNotification +{ + /// + /// Util and extension methods. + /// + internal static class Util + { + public static readonly object SyncRoot = new object(); + + #region IsDesignMode + + private static readonly bool isDesignMode; + + /// + /// Checks whether the application is currently in design mode. + /// + public static bool IsDesignMode + { + get { return isDesignMode; } + } + + #endregion + + #region construction + + static Util() + { + isDesignMode = + (bool) + DependencyPropertyDescriptor.FromProperty(DesignerProperties.IsInDesignModeProperty, + typeof (FrameworkElement)) + .Metadata.DefaultValue; + } + + #endregion + + #region CreateHelperWindow + + /// + /// Creates an transparent window without dimension that + /// can be used to temporarily obtain focus and/or + /// be used as a window message sink. + /// + /// Empty window. + public static Window CreateHelperWindow() + { + return new Window + { + Width = 0, + Height = 0, + ShowInTaskbar = false, + WindowStyle = WindowStyle.None, + AllowsTransparency = true, + Opacity = 0 + }; + } + + #endregion + + #region WriteIconData + + /// + /// Updates the taskbar icons with data provided by a given + /// instance. + /// + /// Configuration settings for the NotifyIcon. + /// Operation on the icon (e.g. delete the icon). + /// True if the data was successfully written. + /// See Shell_NotifyIcon documentation on MSDN for details. + public static bool WriteIconData(ref NotifyIconData data, NotifyCommand command) + { + return WriteIconData(ref data, command, data.ValidMembers); + } + + + /// + /// Updates the taskbar icons with data provided by a given + /// instance. + /// + /// Configuration settings for the NotifyIcon. + /// Operation on the icon (e.g. delete the icon). + /// Defines which members of the + /// structure are set. + /// True if the data was successfully written. + /// See Shell_NotifyIcon documentation on MSDN for details. + public static bool WriteIconData(ref NotifyIconData data, NotifyCommand command, IconDataMembers flags) + { + //do nothing if in design mode + if (IsDesignMode) return true; + + data.ValidMembers = flags; + lock (SyncRoot) + { + return WinApi.Shell_NotifyIcon(command, ref data); + } + } + + #endregion + + #region GetBalloonFlag + + /// + /// Gets a enum value that + /// matches a given . + /// + public static BalloonFlags GetBalloonFlag(this BalloonIcon icon) + { + switch (icon) + { + case BalloonIcon.None: + return BalloonFlags.None; + case BalloonIcon.Info: + return BalloonFlags.Info; + case BalloonIcon.Warning: + return BalloonFlags.Warning; + case BalloonIcon.Error: + return BalloonFlags.Error; + default: + throw new ArgumentOutOfRangeException("icon"); + } + } + + #endregion + + #region ImageSource to Icon + + /// + /// Reads a given image resource into a WinForms icon. + /// + /// Image source pointing to + /// an icon file (*.ico). + /// An icon object that can be used with the + /// taskbar area. + public static Icon ToIcon(this ImageSource imageSource) + { + if (imageSource == null) return null; + + Uri uri = new Uri(imageSource.ToString()); + StreamResourceInfo streamInfo = Application.GetResourceStream(uri); + + if (streamInfo == null) + { + string msg = "The supplied image source '{0}' could not be resolved."; + msg = string.Format(msg, imageSource); + throw new ArgumentException(msg); + } + + return new Icon(streamInfo.Stream); + } + + #endregion + + #region evaluate listings + + /// + /// Checks a list of candidates for equality to a given + /// reference value. + /// + /// + /// The evaluated value. + /// A liste of possible values that are + /// regarded valid. + /// True if one of the submitted + /// matches the evaluated value. If the + /// parameter itself is null, too, the method returns false as well, + /// which allows to check with null values, too. + /// If + /// is a null reference. + public static bool Is(this T value, params T[] candidates) + { + if (candidates == null) return false; + + foreach (var t in candidates) + { + if (value.Equals(t)) return true; + } + + return false; + } + + #endregion + + #region match MouseEvent to PopupActivation + + /// + /// Checks if a given is a match for + /// an effectively pressed mouse button. + /// + public static bool IsMatch(this MouseEvent me, PopupActivationMode activationMode) + { + switch (activationMode) + { + case PopupActivationMode.LeftClick: + return me == MouseEvent.IconLeftMouseUp; + case PopupActivationMode.RightClick: + return me == MouseEvent.IconRightMouseUp; + case PopupActivationMode.LeftOrRightClick: + return me.Is(MouseEvent.IconLeftMouseUp, MouseEvent.IconRightMouseUp); + case PopupActivationMode.LeftOrDoubleClick: + return me.Is(MouseEvent.IconLeftMouseUp, MouseEvent.IconDoubleClick); + case PopupActivationMode.DoubleClick: + return me.Is(MouseEvent.IconDoubleClick); + case PopupActivationMode.MiddleClick: + return me == MouseEvent.IconMiddleMouseUp; + case PopupActivationMode.All: + //return true for everything except mouse movements + return me != MouseEvent.MouseMove; + default: + throw new ArgumentOutOfRangeException("activationMode"); + } + } + + #endregion + + #region execute command + + /// + /// Executes a given command if its method + /// indicates it can run. + /// + /// The command to be executed, or a null reference. + /// An optional parameter that is associated with + /// the command. + /// The target element on which to raise the command. + public static void ExecuteIfEnabled(this ICommand command, object commandParameter, IInputElement target) + { + if (command == null) return; + + RoutedCommand rc = command as RoutedCommand; + if (rc != null) + { + //routed commands work on a target + if (rc.CanExecute(commandParameter, target)) rc.Execute(commandParameter, target); + } + else if (command.CanExecute(commandParameter)) + { + command.Execute(commandParameter); + } + } + + #endregion + + /// + /// Returns a dispatcher for multi-threaded scenarios + /// + /// Dispatcher + internal static Dispatcher GetDispatcher(this DispatcherObject source) + { + //use the application's dispatcher by default + if (Application.Current != null) return Application.Current.Dispatcher; + + //fallback for WinForms environments + if (source.Dispatcher != null) return source.Dispatcher; + + // ultimately use the thread's dispatcher + return Dispatcher.CurrentDispatcher; + } + + + /// + /// Checks whether the + /// is bound or not. + /// + /// The element to be checked. + /// True if the data context property is being managed by a + /// binding expression. + /// If + /// is a null reference. + public static bool IsDataContextDataBound(this FrameworkElement element) + { + if (element == null) throw new ArgumentNullException("element"); + return element.GetBindingExpression(FrameworkElement.DataContextProperty) != null; + } + } +} \ No newline at end of file diff --git a/NotifyIconWpf/README.md b/NotifyIconWpf/README.md new file mode 100644 index 0000000..47c1b3d --- /dev/null +++ b/NotifyIconWpf/README.md @@ -0,0 +1,59 @@ +# Hardcodet NotifyIcon for WPF + +Quelle: https://github.com/hardcodet/wpf-notifyicon + +:rocket: **This is the new official repository of the Hardcodet WPF NotifyIcon** :rocket: + +Current version: [![Nuget](https://img.shields.io/nuget/v/Hardcodet.NotifyIcon.Wpf.svg)](https://www.nuget.org/packages/Hardcodet.NotifyIcon.Wpf/) + +## Description + +This is an implementation of a NotifyIcon (aka system tray icon or taskbar icon) for the WPF platform. It does not just rely on the Windows Forms NotifyIcon component, but is a purely independent control which leverages several features of the WPF framework in order to display rich tooltips, popups, context menus, and balloon messages. It can be used directly in code or embedded in any XAML file. + +### Features at a glance + +- Custom Popups (interactive controls) on mouse clicks. +- Customized ToolTips (Vista and above) with fallback mechanism for xp/2003. +- Rich event model including attached events to trigger animations in Popups, ToolTips, and balloon messages. I just love that. +- Full support for standard Windows balloons, including custom icons. +- Custom balloons that pop up in the tray area. Go wild with styles and animations 🙂 +- Support for WPF context menus. +- You can define whether to show Popups on left-, right-, double-clicks etc. The same goes for context menus. +- Simple data binding for Popups, ToolTips and custom balloons through attached properties and derived data context. +- Command support for single / double clicks on the tray icon. + +### Tutorial and Support + +A comprehensive tutorial that complements the attached sample application can be found on the Code Project: +http://www.codeproject.com/KB/WPF/wpf_notifyicon.aspx + +## XAML Declaration Sample + +The sample below shows some of the properties of the control. For a more comprehensive sample, have a look at the sample application that comes with the download. + +``` XML + + + + + +``` + +## Contributors and Thanks + +Hi, I'm Philipp! This little library was originally written by me, but is currently mostly maintained by [Jan Karger](https://github.com/punker76) and [Robin Krom](https://github.com/Lakritzator). Big, big kudos to the two of you, and everybody else who [contributed](https://github.com/hardcodet/wpf-notifyicon/graphs/contributors) to this library. You rock! + +Make sure to check out Robin's great [Greenshot](https://getgreenshot.org/) tool (that I use on a daily basis), and Jan's [MahApps](https://github.com/MahApps) UI framework. diff --git a/NotifyIconWpf/global.json b/NotifyIconWpf/global.json new file mode 100644 index 0000000..cbde930 --- /dev/null +++ b/NotifyIconWpf/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "5.0.0", + "rollForward": "latestMajor", + "allowPrerelease": true + } +} \ No newline at end of file diff --git a/RadialMenu/.gitignore b/RadialMenu/.gitignore new file mode 100644 index 0000000..8e7a151 --- /dev/null +++ b/RadialMenu/.gitignore @@ -0,0 +1,400 @@ +# ---> VisualStudio +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml + diff --git a/RadialMenu/Controls/PieShape.cs b/RadialMenu/Controls/PieShape.cs new file mode 100644 index 0000000..e91b3d0 --- /dev/null +++ b/RadialMenu/Controls/PieShape.cs @@ -0,0 +1,219 @@ +using System; +using System.Windows; +using System.Windows.Media; +using System.Windows.Shapes; + +namespace RadialMenu.Controls +{ + /// + /// Interaction logic for PieShape.xaml + /// + internal class PieShape : Shape + { + public static readonly DependencyProperty InnerRadiusProperty = + DependencyProperty.Register("InnerRadius", typeof(double), typeof(PieShape), + new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure)); + + /// + /// The inner radius of this pie piece + /// + public double InnerRadius + { + get { return (double)GetValue(InnerRadiusProperty); } + set { SetValue(InnerRadiusProperty, value); } + } + + public static readonly DependencyProperty OuterRadiusProperty = + DependencyProperty.Register("OuterRadius", typeof(double), typeof(PieShape), + new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure)); + + /// + /// The outer radius of this pie piece + /// + public double OuterRadius + { + get { return (double)GetValue(OuterRadiusProperty); } + set { SetValue(OuterRadiusProperty, value); } + } + + public static readonly DependencyProperty PaddingProperty = + DependencyProperty.Register("Padding", typeof(double), typeof(PieShape), + new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure)); + + /// + /// The padding arround this pie piece + /// + public double Padding + { + get { return (double)GetValue(PaddingProperty); } + set { SetValue(PaddingProperty, value); } + } + + public static readonly DependencyProperty PushOutProperty = + DependencyProperty.Register("PushOut", typeof(double), typeof(PieShape), + new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure)); + + /// + /// The distance to 'push' this pie piece out from the center + /// + public double PushOut + { + get { return (double)GetValue(PushOutProperty); } + set { SetValue(PushOutProperty, value); } + } + + public static readonly DependencyProperty AngleDeltaProperty = + DependencyProperty.Register("AngleDelta", typeof(double), typeof(PieShape), + new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure)); + + /// + /// The angle delta of this pie piece in degrees + /// + public double AngleDelta + { + get { return (double)GetValue(AngleDeltaProperty); } + set { SetValue(AngleDeltaProperty, value); } + } + + public static readonly DependencyProperty StartAngleProperty = + DependencyProperty.Register("StartAngle", typeof(double), typeof(PieShape), + new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure)); + + /// + /// The start angle from the Y axis vector of this pie piece in degrees + /// + public double StartAngle + { + get { return (double)GetValue(StartAngleProperty); } + set { SetValue(StartAngleProperty, value); } + } + + public static readonly DependencyProperty CenterXProperty = + DependencyProperty.Register("CenterX", typeof(double), typeof(PieShape), + new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure)); + + /// + /// The X coordinate of center of the circle from which this pie piece is cut + /// + public double CenterX + { + get { return (double)GetValue(CenterXProperty); } + set { SetValue(CenterXProperty, value); } + } + + public static readonly DependencyProperty CenterYProperty = + DependencyProperty.Register("CenterY", typeof(double), typeof(PieShape), + new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure)); + + /// + /// The Y coordinate of center of the circle from which this pie piece is cut + /// + public double CenterY + { + get { return (double)GetValue(CenterYProperty); } + set { SetValue(CenterYProperty, value); } + } + + static PieShape() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(PieShape), new FrameworkPropertyMetadata(typeof(PieShape))); + } + + /// + /// Defines the shape + /// + protected override Geometry DefiningGeometry + { + get + { + // Creates a StreamGeometry for describing the shape + StreamGeometry geometry = new StreamGeometry(); + geometry.FillRule = FillRule.EvenOdd; + + using (StreamGeometryContext context = geometry.Open()) + { + DrawGeometry(context); + } + + // Freezes the geometry for performance benefits + geometry.Freeze(); + + return geometry; + } + } + + /// + /// Draws the pie piece + /// + private void DrawGeometry(StreamGeometryContext context) + { + if (AngleDelta <= 0) + { + return; + } + + double outerStartAngle = StartAngle; + double outerAngleDelta = AngleDelta; + double innerStartAngle = StartAngle; + double innerAngleDelta = AngleDelta; + Point arcCenter = new Point(CenterX, CenterY); + Size outerArcSize = new Size(OuterRadius, OuterRadius); + Size innerArcSize = new Size(InnerRadius, InnerRadius); + + // If have to draw a full-circle, draws two semi-circles, because 'ArcTo()' can not draw a full-circle + if (AngleDelta >= 360 && Padding <= 0) + { + Point outerArcTopPoint = ComputeCartesianCoordinate(arcCenter, outerStartAngle, OuterRadius + PushOut); + Point outerArcBottomPoint = ComputeCartesianCoordinate(arcCenter, outerStartAngle + 180, OuterRadius + PushOut); + Point innerArcTopPoint = ComputeCartesianCoordinate(arcCenter, innerStartAngle, InnerRadius + PushOut); + Point innerArcBottomPoint = ComputeCartesianCoordinate(arcCenter, innerStartAngle + 180, InnerRadius + PushOut); + + context.BeginFigure(innerArcTopPoint, true, true); + context.LineTo(outerArcTopPoint, true, true); + context.ArcTo(outerArcBottomPoint, outerArcSize, 0, false, SweepDirection.Clockwise, true, true); + context.ArcTo(outerArcTopPoint, outerArcSize, 0, false, SweepDirection.Clockwise, true, true); + context.LineTo(innerArcTopPoint, true, true); + context.ArcTo(innerArcBottomPoint, innerArcSize, 0, false, SweepDirection.Counterclockwise, true, true); + context.ArcTo(innerArcTopPoint, innerArcSize, 0, false, SweepDirection.Counterclockwise, true, true); + } + // Else draws as always + else + { + if (Padding > 0) + { + // Offsets the angle by the padding + double outerAngleVariation = (180 * (Padding / OuterRadius)) / Math.PI; + double innerAngleVariation = (180 * (Padding / InnerRadius)) / Math.PI; + + outerStartAngle += outerAngleVariation; + outerAngleDelta -= outerAngleVariation * 2; + innerStartAngle += innerAngleVariation; + innerAngleDelta -= innerAngleVariation * 2; + } + + Point outerArcStartPoint = ComputeCartesianCoordinate(arcCenter, outerStartAngle, OuterRadius + PushOut); + Point outerArcEndPoint = ComputeCartesianCoordinate(arcCenter, outerStartAngle + outerAngleDelta, OuterRadius + PushOut); + Point innerArcStartPoint = ComputeCartesianCoordinate(arcCenter, innerStartAngle, InnerRadius + PushOut); + Point innerArcEndPoint = ComputeCartesianCoordinate(arcCenter, innerStartAngle + innerAngleDelta, InnerRadius + PushOut); + + bool largeArcOuter = outerAngleDelta > 180.0; + bool largeArcInner = innerAngleDelta > 180.0; + + context.BeginFigure(innerArcStartPoint, true, true); + context.LineTo(outerArcStartPoint, true, true); + context.ArcTo(outerArcEndPoint, outerArcSize, 0, largeArcOuter, SweepDirection.Clockwise, true, true); + context.LineTo(innerArcEndPoint, true, true); + context.ArcTo(innerArcStartPoint, innerArcSize, 0, largeArcInner, SweepDirection.Counterclockwise, true, true); + } + } + + private static Point ComputeCartesianCoordinate(Point center, double angle, double radius) + { + // Converts to radians + double radiansAngle = (Math.PI / 180.0) * (angle - 90); + double x = radius * Math.Cos(radiansAngle); + double y = radius * Math.Sin(radiansAngle); + return new Point(x + center.X, y + center.Y); + } + } +} diff --git a/RadialMenu/Controls/RadialMenu.cs b/RadialMenu/Controls/RadialMenu.cs new file mode 100644 index 0000000..a41b89e --- /dev/null +++ b/RadialMenu/Controls/RadialMenu.cs @@ -0,0 +1,80 @@ +using System.Collections.Generic; +using System.Windows; +using System.Windows.Controls; + +namespace RadialMenu.Controls +{ + /// + /// Interaction logic for RadialMenu.xaml + /// + public class RadialMenu : ContentControl + { + public static readonly DependencyProperty IsOpenProperty = + DependencyProperty.Register("IsOpen", typeof(bool), typeof(RadialMenu), + new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure)); + + public bool IsOpen + { + get { return (bool)GetValue(IsOpenProperty); } + set { SetValue(IsOpenProperty, value); } + } + + public static readonly DependencyProperty HalfShiftedItemsProperty = + DependencyProperty.Register("HalfShiftedItems", typeof(bool), typeof(RadialMenu), + new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure)); + + public bool HalfShiftedItems + { + get { return (bool)GetValue(HalfShiftedItemsProperty); } + set { SetValue(HalfShiftedItemsProperty, value); } + } + + public static readonly DependencyProperty CentralItemProperty = + DependencyProperty.Register("CentralItem", typeof(RadialMenuCentralItem), typeof(RadialMenu), + new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure)); + + public RadialMenuCentralItem CentralItem + { + get { return (RadialMenuCentralItem)GetValue(CentralItemProperty); } + set { SetValue(CentralItemProperty, value); } + } + + public new static readonly DependencyProperty ContentProperty = + DependencyProperty.Register("Content", typeof(List), typeof(RadialMenu), + new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure)); + + public new List Content + { + get { return (List)GetValue(ContentProperty); } + set { SetValue(ContentProperty, value); } + } + + public List Items + { + get { return Content; } + set { Content = value; } + } + + static RadialMenu() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(RadialMenu), new FrameworkPropertyMetadata(typeof(RadialMenu))); + } + + public override void BeginInit() + { + Content = new List(); + base.BeginInit(); + } + + protected override Size ArrangeOverride(Size arrangeSize) + { + for (int i = 0, count = Items.Count; i < count; i++) + { + Items[i].Index = i; + Items[i].Count = count; + Items[i].HalfShifted = HalfShiftedItems; + } + return base.ArrangeOverride(arrangeSize); + } + } +} diff --git a/RadialMenu/Controls/RadialMenuCentralItem.cs b/RadialMenu/Controls/RadialMenuCentralItem.cs new file mode 100644 index 0000000..ec19a10 --- /dev/null +++ b/RadialMenu/Controls/RadialMenuCentralItem.cs @@ -0,0 +1,16 @@ +using System.Windows; +using System.Windows.Controls; + +namespace RadialMenu.Controls +{ + /// + /// Interaction logic for RadialMenuCentralItem.xaml + /// + public class RadialMenuCentralItem : Button + { + static RadialMenuCentralItem() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(RadialMenuCentralItem), new FrameworkPropertyMetadata(typeof(RadialMenuCentralItem))); + } + } +} diff --git a/RadialMenu/Controls/RadialMenuItem.cs b/RadialMenu/Controls/RadialMenuItem.cs new file mode 100644 index 0000000..c2c8233 --- /dev/null +++ b/RadialMenu/Controls/RadialMenuItem.cs @@ -0,0 +1,279 @@ +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; + +namespace RadialMenu.Controls +{ + /// + /// Interaction logic for RadialMenuItem.xaml + /// + public class RadialMenuItem : Button + { + public static readonly DependencyProperty IndexProperty = + DependencyProperty.Register("Index", typeof(int), typeof(RadialMenuItem), + new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure, UpdateItemRendering)); + + public int Index + { + get { return (int)GetValue(IndexProperty); } + set { SetValue(IndexProperty, value); } + } + + public static readonly DependencyProperty CountProperty = + DependencyProperty.Register("Count", typeof(int), typeof(RadialMenuItem), + new FrameworkPropertyMetadata(1, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure, UpdateItemRendering)); + + public int Count + { + get { return (int)GetValue(CountProperty); } + set { SetValue(CountProperty, value); } + } + + public static readonly DependencyProperty HalfShiftedProperty = + DependencyProperty.Register("HalfShifted", typeof(bool), typeof(RadialMenuItem), + new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure, UpdateItemRendering)); + + public bool HalfShifted + { + get { return (bool)GetValue(HalfShiftedProperty); } + set { SetValue(HalfShiftedProperty, value); } + } + + public static readonly DependencyProperty CenterXProperty = + DependencyProperty.Register("CenterX", typeof(double), typeof(RadialMenuItem), + new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure)); + + public double CenterX + { + get { return (double)GetValue(CenterXProperty); } + set { SetValue(CenterXProperty, value); } + } + + public static readonly DependencyProperty CenterYProperty = + DependencyProperty.Register("CenterY", typeof(double), typeof(RadialMenuItem), + new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure)); + + public double CenterY + { + get { return (double)GetValue(CenterYProperty); } + set { SetValue(CenterYProperty, value); } + } + + public static readonly DependencyProperty OuterRadiusProperty = + DependencyProperty.Register("OuterRadius", typeof(double), typeof(RadialMenuItem), + new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure)); + + public double OuterRadius + { + get { return (double)GetValue(OuterRadiusProperty); } + set { SetValue(OuterRadiusProperty, value); } + } + + public static readonly DependencyProperty InnerRadiusProperty = + DependencyProperty.Register("InnerRadius", typeof(double), typeof(RadialMenuItem), + new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure)); + + public double InnerRadius + { + get { return (double)GetValue(InnerRadiusProperty); } + set { SetValue(InnerRadiusProperty, value); } + } + + public new static readonly DependencyProperty PaddingProperty = + DependencyProperty.Register("Padding", typeof(double), typeof(RadialMenuItem), + new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure)); + + public new double Padding + { + get { return (double)GetValue(PaddingProperty); } + set { SetValue(PaddingProperty, value); } + } + + public static readonly DependencyProperty ContentRadiusProperty = + DependencyProperty.Register("ContentRadius", typeof(double), typeof(RadialMenuItem), + new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure)); + + public double ContentRadius + { + get { return (double)GetValue(ContentRadiusProperty); } + set { SetValue(ContentRadiusProperty, value); } + } + + public static readonly DependencyProperty EdgeOuterRadiusProperty = + DependencyProperty.Register("EdgeOuterRadius", typeof(double), typeof(RadialMenuItem), + new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure)); + + public double EdgeOuterRadius + { + get { return (double)GetValue(EdgeOuterRadiusProperty); } + set { SetValue(EdgeOuterRadiusProperty, value); } + } + + public static readonly DependencyProperty EdgeInnerRadiusProperty = + DependencyProperty.Register("EdgeInnerRadius", typeof(double), typeof(RadialMenuItem), + new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure)); + + public double EdgeInnerRadius + { + get { return (double)GetValue(EdgeInnerRadiusProperty); } + set { SetValue(EdgeInnerRadiusProperty, value); } + } + + public static readonly DependencyProperty EdgePaddingProperty = + DependencyProperty.Register("EdgePadding", typeof(double), typeof(RadialMenuItem), + new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure)); + + public double EdgePadding + { + get { return (double)GetValue(EdgePaddingProperty); } + set { SetValue(EdgePaddingProperty, value); } + } + + public static readonly DependencyProperty EdgeBackgroundProperty = + DependencyProperty.Register("EdgeBackground", typeof(Brush), typeof(RadialMenuItem), + new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure)); + + public Brush EdgeBackground + { + get { return (Brush)GetValue(EdgeBackgroundProperty); } + set { SetValue(EdgeBackgroundProperty, value); } + } + + public static readonly DependencyProperty EdgeBorderThicknessProperty = + DependencyProperty.Register("EdgeBorderThickness", typeof(double), typeof(RadialMenuItem), + new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure)); + + public double EdgeBorderThickness + { + get { return (double)GetValue(EdgeBorderThicknessProperty); } + set { SetValue(EdgeBorderThicknessProperty, value); } + } + + public static readonly DependencyProperty EdgeBorderBrushProperty = + DependencyProperty.Register("EdgeBorderBrush", typeof(Brush), typeof(RadialMenuItem), + new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure)); + + public Brush EdgeBorderBrush + { + get { return (Brush)GetValue(EdgeBorderBrushProperty); } + set { SetValue(EdgeBorderBrushProperty, value); } + } + + public static readonly DependencyProperty ArrowBackgroundProperty = + DependencyProperty.Register("ArrowBackground", typeof(Brush), typeof(RadialMenuItem), + new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure)); + + public Brush ArrowBackground + { + get { return (Brush)GetValue(ArrowBackgroundProperty); } + set { SetValue(ArrowBackgroundProperty, value); } + } + + public static readonly DependencyProperty ArrowBorderThicknessProperty = + DependencyProperty.Register("ArrowBorderThickness", typeof(double), typeof(RadialMenuItem), + new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure)); + + public double ArrowBorderThickness + { + get { return (double)GetValue(ArrowBorderThicknessProperty); } + set { SetValue(ArrowBorderThicknessProperty, value); } + } + + public static readonly DependencyProperty ArrowBorderBrushProperty = + DependencyProperty.Register("ArrowBorderBrush", typeof(Brush), typeof(RadialMenuItem), + new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure)); + + public Brush ArrowBorderBrush + { + get { return (Brush)GetValue(ArrowBorderBrushProperty); } + set { SetValue(ArrowBorderBrushProperty, value); } + } + + public static readonly DependencyProperty ArrowWidthProperty = + DependencyProperty.Register("ArrowWidth", typeof(double), typeof(RadialMenuItem), + new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure)); + + public double ArrowWidth + { + get { return (double)GetValue(ArrowWidthProperty); } + set { SetValue(ArrowWidthProperty, value); } + } + + public static readonly DependencyProperty ArrowHeightProperty = + DependencyProperty.Register("ArrowHeight", typeof(double), typeof(RadialMenuItem), + new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure)); + + public double ArrowHeight + { + get { return (double)GetValue(ArrowHeightProperty); } + set { SetValue(ArrowHeightProperty, value); } + } + + public static readonly DependencyProperty ArrowRadiusProperty = + DependencyProperty.Register("ArrowRadius", typeof(double), typeof(RadialMenuItem), + new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure)); + + public double ArrowRadius + { + get { return (double)GetValue(ArrowRadiusProperty); } + set { SetValue(ArrowRadiusProperty, value); } + } + + protected static readonly DependencyPropertyKey AngleDeltaPropertyKey = + DependencyProperty.RegisterReadOnly("AngleDelta", typeof(double), typeof(RadialMenuItem), + new FrameworkPropertyMetadata(200.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure)); + + public static readonly DependencyProperty AngleDeltaProperty = AngleDeltaPropertyKey.DependencyProperty; + + public double AngleDelta + { + get { return (double)GetValue(AngleDeltaProperty); } + protected set { SetValue(AngleDeltaPropertyKey, value); } + } + + protected static readonly DependencyPropertyKey StartAnglePropertyKey = + DependencyProperty.RegisterReadOnly("StartAngle", typeof(double), typeof(RadialMenuItem), + new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure)); + + public static readonly DependencyProperty StartAngleProperty = StartAnglePropertyKey.DependencyProperty; + + public double StartAngle + { + get { return (double)GetValue(StartAngleProperty); } + protected set { SetValue(StartAnglePropertyKey, value); } + } + + protected static readonly DependencyPropertyKey RotationPropertyKey = + DependencyProperty.RegisterReadOnly("Rotation", typeof(double), typeof(RadialMenuItem), + new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure)); + + public static readonly DependencyProperty RotationProperty = RotationPropertyKey.DependencyProperty; + + public double Rotation + { + get { return (double)GetValue(RotationProperty); } + protected set { SetValue(RotationPropertyKey, value); } + } + + static RadialMenuItem() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(RadialMenuItem), new FrameworkPropertyMetadata(typeof(RadialMenuItem))); + } + + private static void UpdateItemRendering(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + RadialMenuItem item = d as RadialMenuItem; + if (item != null) + { + var angleDelta = 360.0 / item.Count; + var angleShift = item.HalfShifted ? -angleDelta / 2 : 0; + var startAngle = angleDelta * item.Index + angleShift; + var rotation = startAngle + angleDelta / 2; + + item.AngleDelta = angleDelta; + item.StartAngle = startAngle; + item.Rotation = rotation; + } + } + } +} diff --git a/RadialMenu/Converters/RadialMenuItemToArrowPosition.cs b/RadialMenu/Converters/RadialMenuItemToArrowPosition.cs new file mode 100644 index 0000000..ef0afb2 --- /dev/null +++ b/RadialMenu/Converters/RadialMenuItemToArrowPosition.cs @@ -0,0 +1,56 @@ +using System; +using System.Globalization; +using System.Windows; +using System.Windows.Data; + +namespace RadialMenu.Converters +{ + internal class RadialMenuItemToArrowPosition : IMultiValueConverter + { + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + if (values.Length != 5) + { + throw new ArgumentException("RadialMenuItemToArrowPosition converter needs 7 values (double centerX, double centerY, double arrowWidth, double arrowHeight, double arrowRadius) !", "values"); + } + if (parameter == null) + { + throw new ArgumentNullException("parameter", "RadialMenuItemToArrowPosition converter needs the parameter (string axis) !"); + } + + string axis = (string)parameter; + + if (axis != "X" && axis != "Y") + { + throw new ArgumentException("RadialMenuItemToArrowPosition parameter needs to be 'X' or 'Y' !", "parameter"); + } + + double centerX = (double)values[0]; + double centerY = (double)values[1]; + double arrowWidth = (double)values[2]; + double arrowHeight = (double)values[3]; + double arrowRadius = (double)values[4]; + + if (axis == "X") + { + return centerX - (arrowWidth / 2); + } + + return centerY - arrowRadius - (arrowHeight / 2); + } + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + { + throw new InvalidOperationException("RadialMenuItemToArrowPosition is a One-Way converter only !"); + } + + private static Point ComputeCartesianCoordinate(Point center, double angle, double radius) + { + // Converts to radians + double radiansAngle = (Math.PI / 180.0) * (angle - 90); + double x = radius * Math.Cos(radiansAngle); + double y = radius * Math.Sin(radiansAngle); + return new Point(x + center.X, y + center.Y); + } + } +} diff --git a/RadialMenu/Converters/RadialMenuItemToContentPosition.cs b/RadialMenu/Converters/RadialMenuItemToContentPosition.cs new file mode 100644 index 0000000..6c72663 --- /dev/null +++ b/RadialMenu/Converters/RadialMenuItemToContentPosition.cs @@ -0,0 +1,59 @@ +using System; +using System.Globalization; +using System.Windows; +using System.Windows.Data; + +namespace RadialMenu.Converters +{ + internal class RadialMenuItemToContentPosition : IMultiValueConverter + { + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + if (values.Length != 6) + { + throw new ArgumentException("RadialMenuItemToContentPosition converter needs 6 values (double angle, double centerX, double centerY, double contentWidth, double contentHeight, double contentRadius) !", "values"); + } + if (parameter == null) + { + throw new ArgumentNullException("parameter", "RadialMenuItemToContentPosition converter needs the parameter (string axis) !"); + } + + string axis = (string)parameter; + + if (axis != "X" && axis != "Y") + { + throw new ArgumentException("RadialMenuItemToContentPosition parameter needs to be 'X' or 'Y' !", "parameter"); + } + + double angle = (double)values[0]; + double centerX = (double)values[1]; + double centerY = (double)values[2]; + double contentWidth = (double)values[3]; + double contentHeight = (double)values[4]; + double contentRadius = (double)values[5]; + + Point contentPosition = ComputeCartesianCoordinate(new Point(centerX, centerY), angle, contentRadius); + + if (axis == "X") + { + return contentPosition.X - (contentWidth / 2); + } + + return contentPosition.Y - (contentHeight / 2); + } + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + { + throw new InvalidOperationException("RadialMenuItemToContentPosition is a One-Way converter only !"); + } + + private static Point ComputeCartesianCoordinate(Point center, double angle, double radius) + { + // Converts to radians + double radiansAngle = (Math.PI / 180.0) * (angle - 90); + double x = radius * Math.Cos(radiansAngle); + double y = radius * Math.Sin(radiansAngle); + return new Point(x + center.X, y + center.Y); + } + } +} diff --git a/RadialMenu/Properties/AssemblyInfo.cs b/RadialMenu/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..47264e2 --- /dev/null +++ b/RadialMenu/Properties/AssemblyInfo.cs @@ -0,0 +1,55 @@ +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("RadialMenu")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("RadialMenu")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +//In order to begin building localizable applications, set +//CultureYouAreCodingWith in your .csproj file +//inside a . For example, if you are using US english +//in your source files, set the to en-US. Then uncomment +//the NeutralResourceLanguage attribute below. Update the "en-US" in +//the line below to match the UICulture setting in the project file. + +//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] + + +[assembly:ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] + + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/RadialMenu/README.md b/RadialMenu/README.md new file mode 100644 index 0000000..d0244ac --- /dev/null +++ b/RadialMenu/README.md @@ -0,0 +1,215 @@ +RadialMenu +=========== +Quelle : https://github.com/Julien-Marcou/RadialMenu#readme + +A custom control to create radial-menu into your WPF application. + +![RadialMenu Demo](/Resources/RadialMenu.gif) + +How to use +----------- + +Import the RadialMenu.dll into your project (do not forget to reference it), +then into your Xaml View, import the custom control + + xmlns:Controls="clr-namespace:RadialMenu.Controls;assembly=RadialMenu" + +To create the **main component** of the RadialMenu, simply type + +```xaml + + ... + +``` + +To create the **central menu item** of the RadialMenu, simply type + +```xaml + + ... + +``` + +To create a **menu item** of the RadialMenu, simply type + +```xaml + + ... + +``` + +Full example +----------- + +A tipical example of what can be done + +```xaml + + + + + + + Close + + + + + + + New file + + + + Edit + + + + Save + + + + Delete + + + + Exit + + + + + +``` + +which results in + +![RadialMenu Example](/Resources/RadialMenuExample.png) + +Customization +----------- + +You can even create your own style of RadialMenu (do not forget to add style for `disabled`, `hovered` and `pressed` item states if desired) + +```xaml + + + +``` + +which results in + +![RadialMenu Custom](/Resources/RadialMenuCustom.png) + +Advanced Usage +----------- + +- The radial menu will not scale by default (and transform controls will break it). To modify the size you want to adjust the Radius properties on the style. You will most likely need to adjust OuterRadius, ContentRadius, EdgeInnerRadius, EdgeOuterRadius, and ArrowRadius to resize the control +- When creating RadialMenuItems try to stick the text in a TextBlock for the best formatting results +- You can hide the arrow on a RadialMenuItem by setting its ArrowBackground to Transparent +- To update the control from code behind after creation you cannot simply update the radialMenu.Items list, you must replace it with a new collection. For example: + +```csharp +MyRadialMenu.Items = new List +{ + new RadialMenuItem + { + Content = new TextBlock { Text = "Hello" } + }, + new RadialMenuItem + { + Content = new TextBlock { Text = "World" } + } +}; +``` + +Multi-level menu +----------- + +You can easily create multi-level radial menus by using the arrow as a visual queue and then replacing the items with an updated set. For example: + +```xaml + + ... + +``` + +```csharp +// Main menu +var MainMenuItems = new List +{ + new RadialMenuItem + { + Content = new TextBlock { Text = "Item 1" }, + ArrowBackground = Brushes.Transparent + }, + new RadialMenuItem + { + Content = new TextBlock { Text = "Item 2" }, + ArrowBackground = Brushes.Transparent + }, + new RadialMenuItem + { + Content = new TextBlock { Text = "Sub Menu" } + } +}; + +// Sub menu +var SubMenuItems = new List +{ + new RadialMenuItem + { + Content = new TextBlock { Text = "Sub Item 1" }, + ArrowBackground = Brushes.Transparent + }, + new RadialMenuItem + { + Content = new TextBlock { Text = "Sub Item 2" }, + ArrowBackground = Brushes.Transparent + }, + new RadialMenuItem + { + Content = new TextBlock { Text = "Sub Item 3" }, + ArrowBackground = Brushes.Transparent + } +}; + +// Go to Sub menu when clicking on the third item +MainMenuItems[2].Click += async (sender, args) => +{ + IsOpen = false; + await Task.Delay(400); + MyRadialMenu.Items = SubMenuItems; + IsOpen = true; +}; + +// Set default menu to Main menu +MyRadialMenu.Items = MainMenuItems; +``` + +Which results in + +![RadialMenu Multi-Levels]/Resources/RadialMenuMultiLevels.gif) \ No newline at end of file diff --git a/RadialMenu/RadialMenu.csproj b/RadialMenu/RadialMenu.csproj new file mode 100644 index 0000000..c45c61b --- /dev/null +++ b/RadialMenu/RadialMenu.csproj @@ -0,0 +1,13 @@ + + + net6.0-windows + Library + false + true + true + + + + + + \ No newline at end of file diff --git a/RadialMenu/Resources/RadialMenu.gif b/RadialMenu/Resources/RadialMenu.gif new file mode 100644 index 0000000..b670b19 Binary files /dev/null and b/RadialMenu/Resources/RadialMenu.gif differ diff --git a/RadialMenu/Resources/RadialMenuCustom.png b/RadialMenu/Resources/RadialMenuCustom.png new file mode 100644 index 0000000..a115550 Binary files /dev/null and b/RadialMenu/Resources/RadialMenuCustom.png differ diff --git a/RadialMenu/Resources/RadialMenuDefault.png b/RadialMenu/Resources/RadialMenuDefault.png new file mode 100644 index 0000000..d49663e Binary files /dev/null and b/RadialMenu/Resources/RadialMenuDefault.png differ diff --git a/RadialMenu/Resources/RadialMenuExample.png b/RadialMenu/Resources/RadialMenuExample.png new file mode 100644 index 0000000..1de8493 Binary files /dev/null and b/RadialMenu/Resources/RadialMenuExample.png differ diff --git a/RadialMenu/Resources/RadialMenuHalfShiftedItems.png b/RadialMenu/Resources/RadialMenuHalfShiftedItems.png new file mode 100644 index 0000000..14ee3a5 Binary files /dev/null and b/RadialMenu/Resources/RadialMenuHalfShiftedItems.png differ diff --git a/RadialMenu/Resources/RadialMenuIcon.png b/RadialMenu/Resources/RadialMenuIcon.png new file mode 100644 index 0000000..dd14687 Binary files /dev/null and b/RadialMenu/Resources/RadialMenuIcon.png differ diff --git a/RadialMenu/Resources/RadialMenuMultiLevels.gif b/RadialMenu/Resources/RadialMenuMultiLevels.gif new file mode 100644 index 0000000..9a81273 Binary files /dev/null and b/RadialMenu/Resources/RadialMenuMultiLevels.gif differ diff --git a/RadialMenu/Themes/Generic.xaml b/RadialMenu/Themes/Generic.xaml new file mode 100644 index 0000000..2d84a62 --- /dev/null +++ b/RadialMenu/Themes/Generic.xaml @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/RadialMenu/Themes/PieShape.xaml b/RadialMenu/Themes/PieShape.xaml new file mode 100644 index 0000000..da872c9 --- /dev/null +++ b/RadialMenu/Themes/PieShape.xaml @@ -0,0 +1,10 @@ + + + + + diff --git a/RadialMenu/Themes/RadialMenu.xaml b/RadialMenu/Themes/RadialMenu.xaml new file mode 100644 index 0000000..72325d6 --- /dev/null +++ b/RadialMenu/Themes/RadialMenu.xaml @@ -0,0 +1,110 @@ + + + + + diff --git a/RadialMenu/Themes/RadialMenuCentralItem.xaml b/RadialMenu/Themes/RadialMenuCentralItem.xaml new file mode 100644 index 0000000..e93235a --- /dev/null +++ b/RadialMenu/Themes/RadialMenuCentralItem.xaml @@ -0,0 +1,68 @@ + + + + + diff --git a/RadialMenu/Themes/RadialMenuItem.xaml b/RadialMenu/Themes/RadialMenuItem.xaml new file mode 100644 index 0000000..76e81f0 --- /dev/null +++ b/RadialMenu/Themes/RadialMenuItem.xaml @@ -0,0 +1,182 @@ + + + + + + + + + + diff --git a/git-remote-init b/git-remote-init new file mode 100644 index 0000000..e8da571 --- /dev/null +++ b/git-remote-init @@ -0,0 +1,11 @@ +#!/bin/bash +gitTop=$( git rev-parse --show-toplevel 2> /dev/null ) +[[ "" == "$gitTop" ]] && exit 0 +gitRemotes=$gitTop/.gitremotes +[[ ! -e $gitRemotes ]] && exit 0 +# if exists added remote repos then exit +[[ "$( git remote -v )" == "" ]] && exit 0 +remotes=$(sed '/origin/d' $gitRemotes | awk 'NR%2==0 {print $1";"$2 }') +for r in $remotes ; do + git remote add $(echo $r | sed 's/;/ /') +done diff --git a/git-remote-save b/git-remote-save new file mode 100644 index 0000000..5c0771e --- /dev/null +++ b/git-remote-save @@ -0,0 +1,6 @@ +#!/bin/bash +gitTop=$( git rev-parse --show-toplevel 2> /dev/null ) +[[ "" == "$gitTop" ]] && exit 0 +gitRemotes=$gitTop/.gitremotes +[[ "$( git remote -v )" == "" ]] && exit 0 +git remote -v > $gitRemotes