├── .gitignore ├── App.xaml ├── App.xaml.cs ├── Converters.cs ├── DTOs ├── Server.cs └── Service.cs ├── IgnoreMouseWheelBehavior.cs ├── MainWindow.xaml ├── MainWindow.xaml.cs ├── Properties ├── AssemblyInfo.cs ├── Resources.Designer.cs ├── Resources.resx ├── Settings.Designer.cs └── Settings.settings ├── README.md ├── Resources ├── application_add_16.png ├── application_remove_16.png ├── cog_16.png ├── cog_22.png ├── cog_24.png ├── cog_32.png ├── go_16.png ├── icon.ico ├── info_16.ico ├── info_24.png ├── server_32.png └── stop_16.png ├── ServiceConfigurator.csproj ├── ServiceConfigurator.sln ├── ServiceConfigurator.suo ├── ServiceUtils ├── IWmiAccess.cs ├── ServiceConstants.cs └── WmiService.cs ├── Styles.xaml ├── TaskbarIcon ├── BalloonFlags.cs ├── BalloonIcon.cs ├── Interop │ ├── IconDataMembers.cs │ ├── IconState.cs │ ├── MouseEvent.cs │ ├── NotifyCommand.cs │ ├── NotifyIconData.cs │ ├── NotifyIconVersion.cs │ ├── Point.cs │ ├── TaskbarIcon.cs │ ├── TrayInfo.cs │ ├── WinApi.cs │ ├── WindowClass.cs │ └── WindowMessageSink.cs ├── PopupActivationMode.cs ├── RoutedEventHelper.cs ├── TaskbarIcon.Declarations.cs └── Util.cs ├── UserControls ├── ImageButton.cs ├── ServerController.xaml └── ServerController.xaml.cs ├── Utils.cs ├── _ReSharper.ServiceConfigurator ├── AspFileDataCache.dat └── RecentItems │ └── RecentFiles.dat ├── app.config ├── config.xml ├── icon.ico └── lib └── WPFToolkit.Extended.dll /.gitignore: -------------------------------------------------------------------------------- 1 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs) 2 | bin 3 | obj 4 | 5 | # mstest test results 6 | TestResults -------------------------------------------------------------------------------- /App.xaml: -------------------------------------------------------------------------------- 1 |  5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Data; 5 | using System.Linq; 6 | using System.Windows; 7 | 8 | namespace ServiceConfigurator 9 | { 10 | /// 11 | /// Interaction logic for App.xaml 12 | /// 13 | public partial class App : Application 14 | { 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Converters.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace ServiceConfigurator 7 | { 8 | public class Converters 9 | { 10 | 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /DTOs/Server.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Xml.Linq; 6 | 7 | namespace ServiceConfigurator 8 | { 9 | public class Server 10 | { 11 | public string MachineName { get; set; } 12 | 13 | /// 14 | /// Gets or sets the friendly name of the server. 15 | /// 16 | /// 17 | /// The friendly name of the server 18 | /// 19 | public string Name { get; set; } 20 | 21 | public string ServiceName { get; set; } 22 | 23 | /// 24 | /// Gets or sets the path to the executeble on this server. 25 | /// 26 | /// 27 | /// The path to the executeble on this server. 28 | /// 29 | public string ExePath { get; set; } 30 | 31 | public Server() { 32 | //System.Diagnostics.Debugger.Break(); 33 | } 34 | 35 | public Server(XElement element) 36 | :this() { 37 | this.MachineName = element.Attribute("machineName").Value; 38 | this.Name = element.Attribute("name").Value; 39 | var eleExePath = element.Attribute("exePath"); 40 | if (eleExePath != null) 41 | ExePath = eleExePath.Value; 42 | } 43 | 44 | public XElement ToXml() { 45 | var root = new XElement("server", 46 | new XAttribute("name", Name), 47 | new XAttribute("machineName", MachineName)); 48 | if (!string.IsNullOrEmpty(ExePath)) 49 | root.Add(new XAttribute("exePath", ExePath)); 50 | return root; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /DTOs/Service.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Xml.Linq; 6 | 7 | namespace ServiceConfigurator 8 | { 9 | public class Service 10 | { 11 | public string ServiceName { get; set; } 12 | public List Servers { get; set; } 13 | 14 | public Service(string serviceName) { 15 | this.ServiceName = serviceName; 16 | this.Servers = new List(); 17 | } 18 | 19 | public Service(string serviceName, IEnumerable servers) 20 | :this(serviceName) { 21 | this.Servers.AddRange(servers); 22 | } 23 | 24 | public Service(XElement element) 25 | : this(element.Attribute("name").Value) { 26 | var eleServers = element.Elements("server"); 27 | foreach (var eleServer in eleServers) { 28 | this.Servers.Add(new Server(eleServer) 29 | { 30 | ServiceName = this.ServiceName 31 | }); 32 | } 33 | } 34 | 35 | public XElement ToXml() { 36 | var root = new XElement("service", 37 | new XAttribute("name", ServiceName)); 38 | foreach(var server in this.Servers) { 39 | root.Add(server.ToXml()); 40 | } 41 | return root; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /IgnoreMouseWheelBehavior.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Windows; 6 | using System.Windows.Input; 7 | using System.Windows.Interactivity; 8 | 9 | namespace ServiceConfigurator 10 | { 11 | /// 12 | /// Captures and eats MouseWheel events so that a nested ListBox does not 13 | /// prevent an outer scrollable control from scrolling. 14 | /// 15 | public sealed class IgnoreMouseWheelBehavior : Behavior 16 | { 17 | 18 | protected override void OnAttached() 19 | { 20 | base.OnAttached(); 21 | AssociatedObject.PreviewMouseWheel += AssociatedObject_PreviewMouseWheel; 22 | } 23 | 24 | protected override void OnDetaching() 25 | { 26 | AssociatedObject.PreviewMouseWheel -= AssociatedObject_PreviewMouseWheel; 27 | base.OnDetaching(); 28 | } 29 | 30 | void AssociatedObject_PreviewMouseWheel(object sender, MouseWheelEventArgs e) 31 | { 32 | 33 | e.Handled = true; 34 | 35 | var e2 = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta); 36 | e2.RoutedEvent = UIElement.MouseWheelEvent; 37 | 38 | AssociatedObject.RaiseEvent(e2); 39 | 40 | } 41 | 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  9 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.ComponentModel; 5 | using System.Configuration; 6 | using System.Diagnostics; 7 | using System.Linq; 8 | using System.ServiceProcess; 9 | using System.Text; 10 | using System.Threading; 11 | using System.Threading.Tasks; 12 | using System.Windows; 13 | using System.Windows.Controls; 14 | using System.Windows.Data; 15 | using System.Windows.Documents; 16 | using System.Windows.Input; 17 | using System.Windows.Media; 18 | using System.Windows.Media.Imaging; 19 | using System.Windows.Navigation; 20 | using System.Windows.Shapes; 21 | using ServiceConfigurator.Properties; 22 | using ServiceConfigurator.ServiceUtils; 23 | using Xceed.Wpf.Toolkit; 24 | using MessageBox = System.Windows.MessageBox; 25 | using ServiceStartMode = System.ServiceProcess.ServiceStartMode; 26 | 27 | namespace ServiceConfigurator 28 | { 29 | /// 30 | /// Interaction logic for MainWindow.xaml 31 | /// 32 | public partial class MainWindow : Window { 33 | public static readonly DependencyProperty ServicesProperty = 34 | DependencyProperty.Register("Services", typeof(ObservableCollection), typeof(MainWindow), new PropertyMetadata(new ObservableCollection())); 35 | 36 | public ObservableCollection Services 37 | { 38 | get { return (ObservableCollection)GetValue(ServicesProperty); } 39 | set { SetValue(ServicesProperty, value); } 40 | } 41 | 42 | public MainWindow() 43 | { 44 | InitializeComponent(); 45 | 46 | var services = Utils.ParseConfig(); 47 | foreach (var s in services) { 48 | this.Services.Add(s); 49 | } 50 | } 51 | 52 | private void Window_Closing(object sender, CancelEventArgs e) { 53 | Utils.SaveConfig(this.Services); 54 | } 55 | 56 | private void CanAlwaysExecute(object sender, CanExecuteRoutedEventArgs e) { 57 | e.CanExecute = true; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Resources; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | using System.Windows; 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [assembly: AssemblyTitle("ServiceConfigurator")] 11 | [assembly: AssemblyDescription("Install, uninstall and control remote and local windows services")] 12 | [assembly: AssemblyConfiguration("")] 13 | [assembly: AssemblyCompany("Lime49 - Harry Jennerway")] 14 | [assembly: AssemblyProduct("ServiceConfiguratorConfigurator")] 15 | [assembly: AssemblyCopyright("Copyright © Harry Jennerway (Lime49) - 2012")] 16 | [assembly: AssemblyTrademark("")] 17 | [assembly: AssemblyCulture("")] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [assembly: ComVisible(false)] 23 | 24 | //In order to begin building localizable applications, set 25 | //CultureYouAreCodingWith in your .csproj file 26 | //inside a . For example, if you are using US english 27 | //in your source files, set the to en-US. Then uncomment 28 | //the NeutralResourceLanguage attribute below. Update the "en-US" in 29 | //the line below to match the UICulture setting in the project file. 30 | 31 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] 32 | 33 | 34 | [assembly: ThemeInfo( 35 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 36 | //(used if a resource is not found in the page, 37 | // or application resource dictionaries) 38 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 39 | //(used if a resource is not found in the page, 40 | // app, or any theme specific resource dictionaries) 41 | )] 42 | 43 | 44 | // Version information for an assembly consists of the following four values: 45 | // 46 | // Major Version 47 | // Minor Version 48 | // Build Number 49 | // Revision 50 | // 51 | // You can specify all the values or you can default the Build and Revision Numbers 52 | // by using the '*' as shown below: 53 | // [assembly: AssemblyVersion("1.0.*")] 54 | [assembly: AssemblyVersion("1.0.0.0")] 55 | [assembly: AssemblyFileVersion("1.0.0.0")] 56 | -------------------------------------------------------------------------------- /Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.225 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace ServiceConfigurator.Properties 12 | { 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources 26 | { 27 | 28 | private static global::System.Resources.ResourceManager resourceMan; 29 | 30 | private static global::System.Globalization.CultureInfo resourceCulture; 31 | 32 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 33 | internal Resources() 34 | { 35 | } 36 | 37 | /// 38 | /// Returns the cached ResourceManager instance used by this class. 39 | /// 40 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 41 | internal static global::System.Resources.ResourceManager ResourceManager 42 | { 43 | get 44 | { 45 | if ((resourceMan == null)) 46 | { 47 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ServiceConfigurator.Properties.Resources", typeof(Resources).Assembly); 48 | resourceMan = temp; 49 | } 50 | return resourceMan; 51 | } 52 | } 53 | 54 | /// 55 | /// Overrides the current thread's CurrentUICulture property for all 56 | /// resource lookups using this strongly typed resource class. 57 | /// 58 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 59 | internal static global::System.Globalization.CultureInfo Culture 60 | { 61 | get 62 | { 63 | return resourceCulture; 64 | } 65 | set 66 | { 67 | resourceCulture = value; 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.225 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace ServiceConfigurator.Properties { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "10.0.0.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 19 | 20 | public static Settings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ServiceConfigurator 2 | =================== 3 | 4 | Install, uninstall and control remote and local windows services. 5 | 6 | Totally configurable via an XML config file. No recompilation necessary, just edit the XML and run the exe. 7 | 8 | # Configuration 9 | The app uses config.xml which is in the application directory. You can monitor as many services as needed. Eg: 10 | For a service which is only deployed on the Alpha server, the config would look like this: 11 | 12 | 13 | 14 | 15 | To monitor 3 servers, the config could look like this: 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | The available options are: 24 | 25 | * *MachineName* - The host name to which to connect (not displayed) 26 | * *ExePath* - The path on the server where the service is located 27 | 28 | # Usage 29 | 1. Copy the service executable to it's destination on the server, Eg: Explorer (the tool doesn't do this). 30 | 2. Enter the path on the server where the service was copied 31 | 3. Click the Install button. 32 | 4. Use the start/stop buttons to control the service 33 | 34 | # Troubleshooting 35 | If the service fails to start, it's likely the exe path is wrong. If you have permission, it should 'just work'. -------------------------------------------------------------------------------- /Resources/application_add_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Echilon/ServiceConfigurator/f2836422d46e5be076ffed1c3d7c43064a28bfe5/Resources/application_add_16.png -------------------------------------------------------------------------------- /Resources/application_remove_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Echilon/ServiceConfigurator/f2836422d46e5be076ffed1c3d7c43064a28bfe5/Resources/application_remove_16.png -------------------------------------------------------------------------------- /Resources/cog_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Echilon/ServiceConfigurator/f2836422d46e5be076ffed1c3d7c43064a28bfe5/Resources/cog_16.png -------------------------------------------------------------------------------- /Resources/cog_22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Echilon/ServiceConfigurator/f2836422d46e5be076ffed1c3d7c43064a28bfe5/Resources/cog_22.png -------------------------------------------------------------------------------- /Resources/cog_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Echilon/ServiceConfigurator/f2836422d46e5be076ffed1c3d7c43064a28bfe5/Resources/cog_24.png -------------------------------------------------------------------------------- /Resources/cog_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Echilon/ServiceConfigurator/f2836422d46e5be076ffed1c3d7c43064a28bfe5/Resources/cog_32.png -------------------------------------------------------------------------------- /Resources/go_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Echilon/ServiceConfigurator/f2836422d46e5be076ffed1c3d7c43064a28bfe5/Resources/go_16.png -------------------------------------------------------------------------------- /Resources/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Echilon/ServiceConfigurator/f2836422d46e5be076ffed1c3d7c43064a28bfe5/Resources/icon.ico -------------------------------------------------------------------------------- /Resources/info_16.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Echilon/ServiceConfigurator/f2836422d46e5be076ffed1c3d7c43064a28bfe5/Resources/info_16.ico -------------------------------------------------------------------------------- /Resources/info_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Echilon/ServiceConfigurator/f2836422d46e5be076ffed1c3d7c43064a28bfe5/Resources/info_24.png -------------------------------------------------------------------------------- /Resources/server_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Echilon/ServiceConfigurator/f2836422d46e5be076ffed1c3d7c43064a28bfe5/Resources/server_32.png -------------------------------------------------------------------------------- /Resources/stop_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Echilon/ServiceConfigurator/f2836422d46e5be076ffed1c3d7c43064a28bfe5/Resources/stop_16.png -------------------------------------------------------------------------------- /ServiceConfigurator.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | x86 6 | 8.0.30703 7 | 2.0 8 | {43AFE579-A8D7-4E31-8C7D-0D915CCD705D} 9 | WinExe 10 | Properties 11 | ServiceConfigurator 12 | ServiceConfigurator 13 | v4.0 14 | Client 15 | 512 16 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 17 | 4 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | x86 29 | true 30 | full 31 | false 32 | bin\Debug\ 33 | DEBUG;TRACE 34 | prompt 35 | 4 36 | 37 | 38 | x86 39 | pdbonly 40 | true 41 | bin\Release\ 42 | TRACE 43 | prompt 44 | 4 45 | 46 | 47 | icon.ico 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | False 58 | ..\..\..\..\..\Program Files\Microsoft SDKs\Expression\Blend\.NETFramework\v4.0\Libraries\System.Windows.Interactivity.dll 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 4.0 67 | 68 | 69 | 70 | 71 | 72 | lib\WPFToolkit.Extended.dll 73 | 74 | 75 | 76 | 77 | MSBuild:Compile 78 | Designer 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | ServerController.xaml 88 | 89 | 90 | 91 | 92 | 93 | MSBuild:Compile 94 | Designer 95 | 96 | 97 | App.xaml 98 | Code 99 | 100 | 101 | MainWindow.xaml 102 | Code 103 | 104 | 105 | Designer 106 | MSBuild:Compile 107 | 108 | 109 | Designer 110 | MSBuild:Compile 111 | 112 | 113 | 114 | 115 | Code 116 | 117 | 118 | True 119 | True 120 | Resources.resx 121 | 122 | 123 | True 124 | Settings.settings 125 | True 126 | 127 | 128 | ResXFileCodeGenerator 129 | Resources.Designer.cs 130 | 131 | 132 | 133 | SettingsSingleFileGenerator 134 | Settings.Designer.cs 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | PreserveNewest 150 | Designer 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 173 | -------------------------------------------------------------------------------- /ServiceConfigurator.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 11.00 3 | # Visual Studio 2010 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceConfigurator", "ServiceConfigurator.csproj", "{43AFE579-A8D7-4E31-8C7D-0D915CCD705D}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|x86 = Debug|x86 9 | Release|x86 = Release|x86 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {43AFE579-A8D7-4E31-8C7D-0D915CCD705D}.Debug|x86.ActiveCfg = Debug|x86 13 | {43AFE579-A8D7-4E31-8C7D-0D915CCD705D}.Debug|x86.Build.0 = Debug|x86 14 | {43AFE579-A8D7-4E31-8C7D-0D915CCD705D}.Release|x86.ActiveCfg = Release|x86 15 | {43AFE579-A8D7-4E31-8C7D-0D915CCD705D}.Release|x86.Build.0 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(SolutionProperties) = preSolution 18 | HideSolutionNode = FALSE 19 | EndGlobalSection 20 | EndGlobal 21 | -------------------------------------------------------------------------------- /ServiceConfigurator.suo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Echilon/ServiceConfigurator/f2836422d46e5be076ffed1c3d7c43064a28bfe5/ServiceConfigurator.suo -------------------------------------------------------------------------------- /ServiceUtils/IWmiAccess.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Management; 5 | using System.Text; 6 | 7 | namespace ServiceConfigurator.ServiceUtils 8 | { 9 | // 10 | // Credit to http://geekswithblogs.net/robz/archive/2008/09/21/how-to-programmatically-install-a-windows-service-.net-on-a.aspx 11 | // 12 | //private readonly string _machineName = Environment.MachineName; 13 | //private const string _serviceName = "_TESTDELETEME"; 14 | //private const string _remoteMachineName = "remoteMachineName"; 15 | //private const string _serviceLocation = @"c:\WINDOWS\system32\taskmgr.exe"; 16 | //private const string _serviceDisplayName = "_TEST DELETE ME"; 17 | //private const string _username = "username"; 18 | //private const string _password = "password"; 19 | //private readonly string[] _dependency = new[] { "MSMQ" }; 20 | //private readonly string[] _multipleDependencies = new[] { "MSMQ", "helpsvc" }; 21 | //private WmiService _wmiService; 22 | //private const string _localSystemAccount = "LocalSystem"; 23 | //private const string _networkAccount = @"NT AUTHORITY\NetworkService"; 24 | 25 | public interface IWmiAccess 26 | { 27 | int InvokeInstanceMethod(string machineName, string className, string name, string methodName); 28 | 29 | /// 30 | /// Calls a named instance of WMI on the remote machine invoking a method on a WMI Class 31 | /// 32 | /// Name of the computer to perform the operation on 33 | /// The WMI Class to invoke 34 | /// The name of the WMI Instance 35 | /// The method to call on the WMI Class 36 | /// Parameters for the method 37 | /// A return code from the invoked method on the WMI Class 38 | int InvokeInstanceMethod(string machineName, string className, string name, string methodName, object[] parameters); 39 | 40 | int InvokeStaticMethod(string machineName, string className, string methodName); 41 | 42 | /// 43 | /// Calls WMI on the remote machine invoking a method on a WMI Class 44 | /// 45 | /// Name of the computer to perform the operation on 46 | /// The WMI Class to invoke 47 | /// The method to call on the WMI Class 48 | /// Parameters for the method 49 | /// A return code from the invoked method on the WMI Class 50 | int InvokeStaticMethod(string machineName, string className, string methodName, object[] parameters); 51 | } 52 | 53 | 54 | public class WmiAccess : IWmiAccess 55 | { 56 | private static ManagementScope Connect(string machineName) 57 | { 58 | ConnectionOptions options = new ConnectionOptions(); 59 | string path = string.Format("\\\\{0}\\root\\cimv2", machineName); 60 | ManagementScope scope = new ManagementScope(path, options); 61 | scope.Connect(); 62 | 63 | return scope; 64 | } 65 | 66 | private static ManagementObject GetInstanceByName(string machineName, string className, string name) 67 | { 68 | ManagementScope scope = Connect(machineName); 69 | ObjectQuery query = new ObjectQuery("SELECT * FROM " + className + " WHERE Name = '" + name + "'"); 70 | ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, query); 71 | ManagementObjectCollection results = searcher.Get(); 72 | foreach (ManagementObject manObject in results) 73 | return manObject; 74 | 75 | return null; 76 | } 77 | 78 | private static ManagementClass GetStaticByName(string machineName, string className) 79 | { 80 | ManagementScope scope = Connect(machineName); 81 | ObjectGetOptions getOptions = new ObjectGetOptions(); 82 | ManagementPath path = new ManagementPath(className); 83 | ManagementClass manClass = new ManagementClass(scope, path, getOptions); 84 | return manClass; 85 | } 86 | 87 | public int InvokeInstanceMethod(string machineName, string className, string name, string methodName) 88 | { 89 | return InvokeInstanceMethod(machineName, className, name, methodName, null); 90 | } 91 | 92 | public int InvokeInstanceMethod(string machineName, string className, string name, string methodName, object[] parameters) 93 | { 94 | try 95 | { 96 | ManagementObject manObject = GetInstanceByName(machineName, className, name); 97 | object result = manObject.InvokeMethod(methodName, parameters); 98 | return Convert.ToInt32(result); 99 | } 100 | catch 101 | { 102 | return -1; 103 | } 104 | } 105 | 106 | public int InvokeStaticMethod(string machineName, string className, string methodName) 107 | { 108 | return InvokeStaticMethod(machineName, className, methodName, null); 109 | } 110 | 111 | public int InvokeStaticMethod(string machineName, string className, string methodName, object[] parameters) 112 | { 113 | try 114 | { 115 | ManagementClass manClass = GetStaticByName(machineName, className); 116 | object result = manClass.InvokeMethod(methodName, parameters); 117 | return Convert.ToInt32(result); 118 | } 119 | catch 120 | { 121 | return -1; 122 | } 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /ServiceUtils/ServiceConstants.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace ServiceConfigurator.ServiceUtils 7 | { 8 | /// 9 | /// How the service starts. Example would be at boot or automatic. 10 | /// 11 | public enum ServiceStartMode 12 | { 13 | Automatic, 14 | Boot, 15 | System, 16 | Manual, 17 | Disabled, 18 | } 19 | 20 | /// 21 | /// The return code from the WMI Class Win32_Service 22 | /// 23 | public enum ServiceReturnCode 24 | { 25 | Success = 0, 26 | NotSupported = 1, 27 | AccessDenied = 2, 28 | DependentServicesRunning = 3, 29 | InvalidServiceControl = 4, 30 | ServiceCannotAcceptControl = 5, 31 | ServiceNotActive = 6, 32 | ServiceRequestTimeout = 7, 33 | UnknownFailure = 8, 34 | PathNotFound = 9, 35 | ServiceAlreadyRunning = 10, 36 | ServiceDatabaseLocked = 11, 37 | ServiceDependencyDeleted = 12, 38 | ServiceDependencyFailure = 13, 39 | ServiceDisabled = 14, 40 | ServiceLogonFailure = 15, 41 | ServiceMarkedForDeletion = 16, 42 | ServiceNoThread = 17, 43 | StatusCircularDependency = 18, 44 | StatusDuplicateName = 19, 45 | StatusInvalidName = 20, 46 | StatusInvalidParameter = 21, 47 | StatusInvalidServiceAccount = 22, 48 | StatusServiceExists = 23, 49 | ServiceAlreadyPaused = 24 50 | } 51 | 52 | /// 53 | /// What type of service is it? Most of the time it will be OwnProcess 54 | /// 55 | public enum ServiceType 56 | { 57 | KernalDriver = 1, 58 | FileSystemDriver = 2, 59 | Adapter = 4, 60 | RecognizerDriver = 8, 61 | OwnProcess = 16, 62 | ShareProcess = 32, 63 | InteractiveProcess = 256, 64 | } 65 | 66 | internal enum ServiceErrorControl 67 | { 68 | UserNotNotified = 0, 69 | UserNotified = 1, 70 | SystemRestartedWithLastKnownGoodConfiguration = 2, 71 | SystemAttemptsToStartWithAGoodConfiguration = 3 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /ServiceUtils/WmiService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace ServiceConfigurator.ServiceUtils 7 | { 8 | public class WmiService 9 | { 10 | static readonly WmiService instance=new WmiService(new WmiAccess()); 11 | static WmiService() { 12 | } 13 | WmiService() { 14 | } 15 | public static WmiService Instance 16 | { 17 | get { return instance; } 18 | } 19 | 20 | private const string CLASS_NAME = "Win32_Service"; 21 | private readonly IWmiAccess _wmi; 22 | 23 | /// 24 | /// Creates a new WmiService for use to access Windows Services 25 | /// 26 | /// The WMI access object - the tool that does the low level work 27 | public WmiService(IWmiAccess wmi) 28 | { 29 | _wmi = wmi; 30 | } 31 | 32 | public ServiceReturnCode Install(string name, string displayName, string physicalLocation, ServiceStartMode startMode, string userName, string password, string[] dependencies) 33 | { 34 | return Install(Environment.MachineName, name, displayName, physicalLocation, startMode, userName, password, dependencies, false); 35 | } 36 | 37 | public ServiceReturnCode Install(string machineName, string name, string displayName, string physicalLocation, ServiceStartMode startMode, string userName, string password, string[] dependencies) 38 | { 39 | return Install(machineName, name, displayName, physicalLocation, startMode, userName, password, dependencies, false); 40 | } 41 | 42 | /// 43 | /// Installs a service on any machine 44 | /// 45 | /// Name of the computer to perform the operation on 46 | /// The name of the service in the registry 47 | /// The display name of the service in the service manager 48 | /// The physical disk location of the executable 49 | /// How the service starts - usually Automatic 50 | /// The user for the service to run under 51 | /// The password fo the user 52 | /// Other dependencies the service may have based on the name of the service in the registry 53 | /// Should the service interact with the desktop? 54 | /// A service return code that defines whether it was successful or not 55 | public ServiceReturnCode Install(string machineName, string name, string displayName, string physicalLocation, ServiceStartMode startMode, string userName, string password, string[] dependencies, bool interactWithDesktop) 56 | { 57 | const string methodName = "Create"; 58 | //string[] serviceDependencies = dependencies != null ? dependencies.Split(',') : null; 59 | if (userName.IndexOf('\\') < 0) 60 | { 61 | //userName = ".\\" + userName; 62 | //UNCOMMENT the line above - it caused issues with color coding in THIS ARTICLE 63 | } 64 | 65 | try 66 | { 67 | object[] parameters = new object[] 68 | { 69 | name, // Name 70 | displayName, // Display Name 71 | physicalLocation, // Path Name | The Location "E:\somewhere\something" 72 | Convert.ToInt32(ServiceType.OwnProcess), // ServiceType 73 | Convert.ToInt32(ServiceErrorControl.UserNotified), // Error Control 74 | startMode.ToString(), // Start Mode 75 | interactWithDesktop, // Desktop Interaction 76 | userName, // StartName | Username 77 | password, // StartPassword |Password 78 | null, // LoadOrderGroup | Service Order Group 79 | null, // LoadOrderGroupDependencies | Load Order Dependencies 80 | dependencies // ServiceDependencies 81 | }; 82 | return (ServiceReturnCode)_wmi.InvokeStaticMethod(machineName, CLASS_NAME, methodName, parameters); 83 | } 84 | catch 85 | { 86 | return ServiceReturnCode.UnknownFailure; 87 | } 88 | } 89 | 90 | public ServiceReturnCode Uninstall(string name) 91 | { 92 | return Uninstall(Environment.MachineName, name); 93 | } 94 | 95 | /// 96 | /// Uninstalls a service on any machine 97 | /// 98 | /// Name of the computer to perform the operation on 99 | /// The name of the service in the registry 100 | /// A service return code that defines whether it was successful or not 101 | public ServiceReturnCode Uninstall(string machineName, string name) 102 | { 103 | try 104 | { 105 | const string methodName = "Delete"; 106 | return (ServiceReturnCode)_wmi.InvokeInstanceMethod(machineName, CLASS_NAME, name, methodName); 107 | } 108 | catch 109 | { 110 | return ServiceReturnCode.UnknownFailure; 111 | } 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /Styles.xaml: -------------------------------------------------------------------------------- 1 |  4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 136 | -------------------------------------------------------------------------------- /TaskbarIcon/BalloonFlags.cs: -------------------------------------------------------------------------------- 1 | namespace Hardcodet.Wpf.TaskbarNotification.Interop 2 | { 3 | /// 4 | /// Flags that define the icon that is shown on a balloon 5 | /// tooltip. 6 | /// 7 | public enum BalloonFlags 8 | { 9 | /// 10 | /// No icon is displayed. 11 | /// 12 | None = 0x00, 13 | /// 14 | /// An information icon is displayed. 15 | /// 16 | Info = 0x01, 17 | /// 18 | /// A warning icon is displayed. 19 | /// 20 | Warning = 0x02, 21 | /// 22 | /// An error icon is displayed. 23 | /// 24 | Error = 0x03, 25 | /// 26 | /// Windows XP Service Pack 2 (SP2) and later. 27 | /// Use a custom icon as the title icon. 28 | /// 29 | User = 0x04, 30 | /// 31 | /// Windows XP (Shell32.dll version 6.0) and later. 32 | /// Do not play the associated sound. Applies only to balloon ToolTips. 33 | /// 34 | NoSound = 0x10, 35 | /// 36 | /// Windows Vista (Shell32.dll version 6.0.6) and later. The large version 37 | /// of the icon should be used as the balloon icon. This corresponds to the 38 | /// icon with dimensions SM_CXICON x SM_CYICON. If this flag is not set, 39 | /// the icon with dimensions XM_CXSMICON x SM_CYSMICON is used.
40 | /// - This flag can be used with all stock icons.
41 | /// - Applications that use older customized icons (NIIF_USER with hIcon) must 42 | /// provide a new SM_CXICON x SM_CYICON version in the tray icon (hIcon). These 43 | /// icons are scaled down when they are displayed in the System Tray or 44 | /// System Control Area (SCA).
45 | /// - New customized icons (NIIF_USER with hBalloonIcon) must supply an 46 | /// SM_CXICON x SM_CYICON version in the supplied icon (hBalloonIcon). 47 | ///
48 | LargeIcon = 0x20, 49 | /// 50 | /// Windows 7 and later. 51 | /// 52 | RespectQuietTime = 0x80 53 | 54 | } 55 | } -------------------------------------------------------------------------------- /TaskbarIcon/BalloonIcon.cs: -------------------------------------------------------------------------------- 1 | // hardcodet.net NotifyIcon for WPF 2 | // Copyright (c) 2009 Philipp Sumi 3 | // Contact and Information: http://www.hardcodet.net 4 | // 5 | // This library is free software; you can redistribute it and/or 6 | // modify it under the terms of the Code Project Open License (CPOL); 7 | // either version 1.0 of the License, or (at your option) any later 8 | // version. 9 | // 10 | // The above copyright notice and this permission notice shall be 11 | // included in all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 14 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 15 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 16 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 17 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 18 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 20 | // OTHER DEALINGS IN THE SOFTWARE. 21 | // 22 | // THIS COPYRIGHT NOTICE MAY NOT BE REMOVED FROM THIS FILE 23 | 24 | 25 | namespace Hardcodet.Wpf.TaskbarNotification 26 | { 27 | /// 28 | /// Supported icons for the tray's balloon messages. 29 | /// 30 | public enum BalloonIcon 31 | { 32 | /// 33 | /// The balloon message is displayed without an icon. 34 | /// 35 | None, 36 | /// 37 | /// An information is displayed. 38 | /// 39 | Info, 40 | /// 41 | /// A warning is displayed. 42 | /// 43 | Warning, 44 | /// 45 | /// An error is displayed. 46 | /// 47 | Error 48 | } 49 | } -------------------------------------------------------------------------------- /TaskbarIcon/Interop/IconDataMembers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Hardcodet.Wpf.TaskbarNotification.Interop 4 | { 5 | /// 6 | /// Indicates which members of a structure 7 | /// were set, and thus contain valid data or provide additional information 8 | /// to the ToolTip as to how it should display. 9 | /// 10 | [Flags] 11 | public enum IconDataMembers 12 | { 13 | /// 14 | /// The message ID is set. 15 | /// 16 | Message = 0x01, 17 | /// 18 | /// The notification icon is set. 19 | /// 20 | Icon = 0x02, 21 | /// 22 | /// The tooltip is set. 23 | /// 24 | Tip = 0x04, 25 | /// 26 | /// State information () is set. This 27 | /// applies to both and 28 | /// . 29 | /// 30 | State = 0x08, 31 | /// 32 | /// The balloon ToolTip is set. Accordingly, the following 33 | /// members are set: , 34 | /// , , 35 | /// and . 36 | /// 37 | Info = 0x10, 38 | 39 | /// 40 | /// Internal identifier is set. Reserved, thus commented out. 41 | /// 42 | //Guid = 0x20, 43 | 44 | /// 45 | /// Windows Vista (Shell32.dll version 6.0.6) and later. If the ToolTip 46 | /// cannot be displayed immediately, discard it.
47 | /// Use this flag for ToolTips that represent real-time information which 48 | /// would be meaningless or misleading if displayed at a later time. 49 | /// For example, a message that states "Your telephone is ringing."
50 | /// This modifies and must be combined with the flag. 51 | ///
52 | Realtime = 0x40, 53 | /// 54 | /// Windows Vista (Shell32.dll version 6.0.6) and later. 55 | /// Use the standard ToolTip. Normally, when uVersion is set 56 | /// to NOTIFYICON_VERSION_4, the standard ToolTip is replaced 57 | /// by the application-drawn pop-up user interface (UI). 58 | /// If the application wants to show the standard tooltip 59 | /// in that case, regardless of whether the on-hover UI is showing, 60 | /// it can specify NIF_SHOWTIP to indicate the standard tooltip 61 | /// should still be shown.
62 | /// Note that the NIF_SHOWTIP flag is effective until the next call 63 | /// to Shell_NotifyIcon. 64 | ///
65 | UseLegacyToolTips = 0x80 66 | } 67 | } -------------------------------------------------------------------------------- /TaskbarIcon/Interop/IconState.cs: -------------------------------------------------------------------------------- 1 | namespace Hardcodet.Wpf.TaskbarNotification.Interop 2 | { 3 | /// 4 | /// The state of the icon - can be set to 5 | /// hide the icon. 6 | /// 7 | public enum IconState 8 | { 9 | /// 10 | /// The icon is visible. 11 | /// 12 | Visible = 0x00, 13 | /// 14 | /// Hide the icon. 15 | /// 16 | Hidden = 0x01, 17 | 18 | /// 19 | /// The icon is shared - currently not supported, thus commented out. 20 | /// 21 | //Shared = 0x02 22 | } 23 | } -------------------------------------------------------------------------------- /TaskbarIcon/Interop/MouseEvent.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | namespace Hardcodet.Wpf.TaskbarNotification.Interop 4 | { 5 | /// 6 | /// Event flags for clicked events. 7 | /// 8 | public enum MouseEvent 9 | { 10 | /// 11 | /// The mouse was moved withing the 12 | /// taskbar icon's area. 13 | /// 14 | MouseMove, 15 | /// 16 | /// The right mouse button was clicked. 17 | /// 18 | IconRightMouseDown, 19 | /// 20 | /// The left mouse button was clicked. 21 | /// 22 | IconLeftMouseDown, 23 | /// 24 | /// The right mouse button was released. 25 | /// 26 | IconRightMouseUp, 27 | /// 28 | /// The left mouse button was released. 29 | /// 30 | IconLeftMouseUp, 31 | /// 32 | /// The middle mouse button was clicked. 33 | /// 34 | IconMiddleMouseDown, 35 | /// 36 | /// The middle mouse button was released. 37 | /// 38 | IconMiddleMouseUp, 39 | /// 40 | /// The taskbar icon was double clicked. 41 | /// 42 | IconDoubleClick, 43 | /// 44 | /// The balloon tip was clicked. 45 | /// 46 | BalloonToolTipClicked 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /TaskbarIcon/Interop/NotifyCommand.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | namespace Hardcodet.Wpf.TaskbarNotification.Interop 4 | { 5 | /// 6 | /// Main operations performed on the 7 | /// function. 8 | /// 9 | public enum NotifyCommand 10 | { 11 | /// 12 | /// The taskbar icon is being created. 13 | /// 14 | Add = 0x00, 15 | /// 16 | /// The settings of the taskbar icon are being updated. 17 | /// 18 | Modify = 0x01, 19 | /// 20 | /// The taskbar icon is deleted. 21 | /// 22 | Delete = 0x02, 23 | /// 24 | /// Focus is returned to the taskbar icon. Currently not in use. 25 | /// 26 | SetFocus = 0x03, 27 | /// 28 | /// Shell32.dll version 5.0 and later only. Instructs the taskbar 29 | /// to behave according to the version number specified in the 30 | /// uVersion member of the structure pointed to by lpdata. 31 | /// This message allows you to specify whether you want the version 32 | /// 5.0 behavior found on Microsoft Windows 2000 systems, or the 33 | /// behavior found on earlier Shell versions. The default value for 34 | /// uVersion is zero, indicating that the original Windows 95 notify 35 | /// icon behavior should be used. 36 | /// 37 | SetVersion = 0x04 38 | } 39 | } -------------------------------------------------------------------------------- /TaskbarIcon/Interop/NotifyIconData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace Hardcodet.Wpf.TaskbarNotification.Interop 6 | { 7 | /// 8 | /// A struct that is submitted in order to configure 9 | /// the taskbar icon. Provides various members that 10 | /// can be configured partially, according to the 11 | /// values of the 12 | /// that were defined. 13 | /// 14 | [StructLayout(LayoutKind.Sequential)] 15 | public struct NotifyIconData 16 | { 17 | /// 18 | /// Size of this structure, in bytes. 19 | /// 20 | public uint cbSize; 21 | 22 | /// 23 | /// Handle to the window that receives notification messages associated with an icon in the 24 | /// taskbar status area. The Shell uses hWnd and uID to identify which icon to operate on 25 | /// when Shell_NotifyIcon is invoked. 26 | /// 27 | public IntPtr WindowHandle; 28 | 29 | /// 30 | /// Application-defined identifier of the taskbar icon. The Shell uses hWnd and uID to identify 31 | /// which icon to operate on when Shell_NotifyIcon is invoked. You can have multiple icons 32 | /// associated with a single hWnd by assigning each a different uID. This feature, however 33 | /// is currently not used. 34 | /// 35 | public uint TaskbarIconId; 36 | 37 | /// 38 | /// Flags that indicate which of the other members contain valid data. This member can be 39 | /// a combination of the NIF_XXX constants. 40 | /// 41 | public IconDataMembers ValidMembers; 42 | 43 | /// 44 | /// Application-defined message identifier. The system uses this identifier to send 45 | /// notifications to the window identified in hWnd. 46 | /// 47 | public uint CallbackMessageId; 48 | 49 | /// 50 | /// A handle to the icon that should be displayed. Just 51 | /// . 52 | /// 53 | public IntPtr IconHandle; 54 | 55 | /// 56 | /// String with the text for a standard ToolTip. It can have a maximum of 64 characters including 57 | /// the terminating NULL. For Version 5.0 and later, szTip can have a maximum of 58 | /// 128 characters, including the terminating NULL. 59 | /// 60 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] public string ToolTipText; 61 | 62 | 63 | /// 64 | /// State of the icon. Remember to also set the . 65 | /// 66 | public IconState IconState; 67 | 68 | /// 69 | /// A value that specifies which bits of the state member are retrieved or modified. 70 | /// For example, setting this member to 71 | /// causes only the item's hidden 72 | /// state to be retrieved. 73 | /// 74 | public IconState StateMask; 75 | 76 | /// 77 | /// String with the text for a balloon ToolTip. It can have a maximum of 255 characters. 78 | /// To remove the ToolTip, set the NIF_INFO flag in uFlags and set szInfo to an empty string. 79 | /// 80 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] public string BalloonText; 81 | 82 | /// 83 | /// Mainly used to set the version when is invoked 84 | /// with . However, for legacy operations, 85 | /// the same member is also used to set timouts for balloon ToolTips. 86 | /// 87 | public uint VersionOrTimeout; 88 | 89 | /// 90 | /// String containing a title for a balloon ToolTip. This title appears in boldface 91 | /// above the text. It can have a maximum of 63 characters. 92 | /// 93 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)] public string BalloonTitle; 94 | 95 | /// 96 | /// Adds an icon to a balloon ToolTip, which is placed to the left of the title. If the 97 | /// member is zero-length, the icon is not shown. 98 | /// 99 | public BalloonFlags BalloonFlags; 100 | 101 | /// 102 | /// Windows XP (Shell32.dll version 6.0) and later.
103 | /// - Windows 7 and later: A registered GUID that identifies the icon. 104 | /// This value overrides uID and is the recommended method of identifying the icon.
105 | /// - Windows XP through Windows Vista: Reserved. 106 | ///
107 | public Guid TaskbarIconGuid; 108 | 109 | /// 110 | /// Windows Vista (Shell32.dll version 6.0.6) and later. The handle of a customized 111 | /// balloon icon provided by the application that should be used independently 112 | /// of the tray icon. If this member is non-NULL and the 113 | /// flag is set, this icon is used as the balloon icon.
114 | /// If this member is NULL, the legacy behavior is carried out. 115 | ///
116 | public IntPtr CustomBalloonIconHandle; 117 | 118 | 119 | /// 120 | /// Creates a default data structure that provides 121 | /// a hidden taskbar icon without the icon being set. 122 | /// 123 | /// 124 | /// 125 | public static NotifyIconData CreateDefault(IntPtr handle) 126 | { 127 | var data = new NotifyIconData(); 128 | 129 | if (Environment.OSVersion.Version.Major >= 6) 130 | { 131 | //use the current size 132 | data.cbSize = (uint)Marshal.SizeOf(data); 133 | } 134 | else 135 | { 136 | //we need to set another size on xp/2003- otherwise certain 137 | //features (e.g. balloon tooltips) don't work. 138 | data.cbSize = 504; 139 | 140 | //set to fixed timeout 141 | data.VersionOrTimeout = 10; 142 | } 143 | 144 | data.WindowHandle = handle; 145 | data.TaskbarIconId = 0x0; 146 | data.CallbackMessageId = WindowMessageSink.CallbackMessageId; 147 | data.VersionOrTimeout = (uint) NotifyIconVersion.Win95; 148 | 149 | data.IconHandle = IntPtr.Zero; 150 | 151 | //hide initially 152 | data.IconState = IconState.Hidden; 153 | data.StateMask = IconState.Hidden; 154 | 155 | //set flags 156 | data.ValidMembers = IconDataMembers.Message 157 | | IconDataMembers.Icon 158 | | IconDataMembers.Tip; 159 | 160 | //reset strings 161 | data.ToolTipText = data.BalloonText = data.BalloonTitle = String.Empty; 162 | 163 | return data; 164 | } 165 | } 166 | } -------------------------------------------------------------------------------- /TaskbarIcon/Interop/NotifyIconVersion.cs: -------------------------------------------------------------------------------- 1 | namespace Hardcodet.Wpf.TaskbarNotification.Interop 2 | { 3 | /// 4 | /// The notify icon version that is used. The higher 5 | /// the version, the more capabilities are available. 6 | /// 7 | public enum NotifyIconVersion 8 | { 9 | /// 10 | /// Default behavior (legacy Win95). Expects 11 | /// a size of 488. 12 | /// 13 | Win95 = 0x0, 14 | /// 15 | /// Behavior representing Win2000 an higher. Expects 16 | /// a size of 504. 17 | /// 18 | Win2000 = 0x3, 19 | /// 20 | /// Extended tooltip support, which is available 21 | /// for Vista and later. 22 | /// 23 | Vista = 0x4 24 | } 25 | } -------------------------------------------------------------------------------- /TaskbarIcon/Interop/Point.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace Hardcodet.Wpf.TaskbarNotification.Interop 4 | { 5 | /// 6 | /// Win API struct providing coordinates for a single point. 7 | /// 8 | [StructLayout(LayoutKind.Sequential)] 9 | public struct Point 10 | { 11 | public int X; 12 | public int Y; 13 | } 14 | } -------------------------------------------------------------------------------- /TaskbarIcon/Interop/TaskbarIcon.cs: -------------------------------------------------------------------------------- 1 | // hardcodet.net NotifyIcon for WPF 2 | // Copyright (c) 2009 Philipp Sumi 3 | // Contact and Information: http://www.hardcodet.net 4 | // 5 | // This library is free software; you can redistribute it and/or 6 | // modify it under the terms of the Code Project Open License (CPOL); 7 | // either version 1.0 of the License, or (at your option) any later 8 | // version. 9 | // 10 | // The above copyright notice and this permission notice shall be 11 | // included in all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 14 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 15 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 16 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 17 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 18 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 20 | // OTHER DEALINGS IN THE SOFTWARE. 21 | // 22 | // THIS COPYRIGHT NOTICE MAY NOT BE REMOVED FROM THIS FILE 23 | 24 | 25 | using System; 26 | using System.ComponentModel; 27 | using System.Diagnostics; 28 | using System.Drawing; 29 | using System.Threading; 30 | using System.Windows; 31 | using System.Windows.Controls; 32 | using System.Windows.Controls.Primitives; 33 | using System.Windows.Interop; 34 | using System.Windows.Threading; 35 | using Hardcodet.Wpf.TaskbarNotification.Interop; 36 | using Point=Hardcodet.Wpf.TaskbarNotification.Interop.Point; 37 | 38 | 39 | 40 | namespace Hardcodet.Wpf.TaskbarNotification 41 | { 42 | /// 43 | /// A WPF proxy to for a taskbar icon (NotifyIcon) that sits in the system's 44 | /// taskbar notification area ("system tray"). 45 | /// 46 | public partial class TaskbarIcon : FrameworkElement, IDisposable 47 | { 48 | #region Members 49 | 50 | /// 51 | /// Represents the current icon data. 52 | /// 53 | private NotifyIconData iconData; 54 | 55 | /// 56 | /// Receives messages from the taskbar icon. 57 | /// 58 | private readonly WindowMessageSink messageSink; 59 | 60 | /// 61 | /// An action that is being invoked if the 62 | /// fires. 63 | /// 64 | private Action delayedTimerAction; 65 | 66 | /// 67 | /// A timer that is used to differentiate between single 68 | /// and double clicks. 69 | /// 70 | private readonly Timer singleClickTimer; 71 | 72 | /// 73 | /// A timer that is used to close open balloon tooltips. 74 | /// 75 | private readonly Timer balloonCloseTimer; 76 | 77 | /// 78 | /// Indicates whether the taskbar icon has been created or not. 79 | /// 80 | public bool IsTaskbarIconCreated { get; private set; } 81 | 82 | /// 83 | /// Indicates whether custom tooltips are supported, which depends 84 | /// on the OS. Windows Vista or higher is required in order to 85 | /// support this feature. 86 | /// 87 | public bool SupportsCustomToolTips 88 | { 89 | get { return messageSink.Version == NotifyIconVersion.Vista; } 90 | } 91 | 92 | 93 | 94 | /// 95 | /// Checks whether a non-tooltip popup is currently opened. 96 | /// 97 | private bool IsPopupOpen 98 | { 99 | get 100 | { 101 | var popup = TrayPopupResolved; 102 | var menu = ContextMenu; 103 | var balloon = CustomBalloon; 104 | 105 | return popup != null && popup.IsOpen || 106 | menu != null && menu.IsOpen || 107 | balloon != null && balloon.IsOpen; 108 | 109 | } 110 | } 111 | 112 | #endregion 113 | 114 | 115 | #region Construction 116 | 117 | /// 118 | /// Inits the taskbar icon and registers a message listener 119 | /// in order to receive events from the taskbar area. 120 | /// 121 | public TaskbarIcon() 122 | { 123 | //using dummy sink in design mode 124 | messageSink = Util.IsDesignMode 125 | ? WindowMessageSink.CreateEmpty() 126 | : new WindowMessageSink(NotifyIconVersion.Win95); 127 | 128 | //init icon data structure 129 | iconData = NotifyIconData.CreateDefault(messageSink.MessageWindowHandle); 130 | 131 | //create the taskbar icon 132 | CreateTaskbarIcon(); 133 | 134 | //register event listeners 135 | messageSink.MouseEventReceived += OnMouseEvent; 136 | messageSink.TaskbarCreated += OnTaskbarCreated; 137 | messageSink.ChangeToolTipStateRequest += OnToolTipChange; 138 | messageSink.BalloonToolTipChanged += OnBalloonToolTipChanged; 139 | 140 | //init single click / balloon timers 141 | singleClickTimer = new Timer(DoSingleClickAction); 142 | balloonCloseTimer = new Timer(CloseBalloonCallback); 143 | 144 | //register listener in order to get notified when the application closes 145 | if (Application.Current != null) Application.Current.Exit += OnExit; 146 | } 147 | 148 | #endregion 149 | 150 | 151 | #region Custom Balloons 152 | 153 | /// 154 | /// Shows a custom control as a tooltip in the tray location. 155 | /// 156 | /// 157 | /// An optional animation for the popup. 158 | /// The time after which the popup is being closed. 159 | /// Submit null in order to keep the balloon open inde 160 | /// 161 | /// If 162 | /// is a null reference. 163 | public void ShowCustomBalloon(UIElement balloon, PopupAnimation animation, int? timeout) 164 | { 165 | Dispatcher dispatcher = this.GetDispatcher(); 166 | if (!dispatcher.CheckAccess()) 167 | { 168 | var action = new Action(() => ShowCustomBalloon(balloon, animation, timeout)); 169 | dispatcher.Invoke(DispatcherPriority.Normal, action); 170 | return; 171 | } 172 | 173 | if (balloon == null) throw new ArgumentNullException("balloon"); 174 | if (timeout.HasValue && timeout < 500) 175 | { 176 | string msg = "Invalid timeout of {0} milliseconds. Timeout must be at least 500 ms"; 177 | msg = String.Format(msg, timeout); 178 | throw new ArgumentOutOfRangeException("timeout", msg); 179 | } 180 | 181 | EnsureNotDisposed(); 182 | 183 | //make sure we don't have an open balloon 184 | lock (this) 185 | { 186 | CloseBalloon(); 187 | } 188 | 189 | //create an invisible popup that hosts the UIElement 190 | Popup popup = new Popup(); 191 | popup.AllowsTransparency = true; 192 | 193 | //provide the popup with the taskbar icon's data context 194 | UpdateDataContext(popup, null, DataContext); 195 | 196 | //don't animate by default - devs can use attached 197 | //events or override 198 | popup.PopupAnimation = animation; 199 | 200 | popup.Child = balloon; 201 | 202 | //don't set the PlacementTarget as it causes the popup to become hidden if the 203 | //TaskbarIcon's parent is hidden, too... 204 | //popup.PlacementTarget = this; 205 | 206 | popup.Placement = PlacementMode.AbsolutePoint; 207 | popup.StaysOpen = true; 208 | 209 | Point position = TrayInfo.GetTrayLocation(); 210 | popup.HorizontalOffset = position.X -1; 211 | popup.VerticalOffset = position.Y -1; 212 | 213 | //store reference 214 | lock (this) 215 | { 216 | SetCustomBalloon(popup); 217 | } 218 | 219 | //assign this instance as an attached property 220 | SetParentTaskbarIcon(balloon, this); 221 | 222 | //fire attached event 223 | RaiseBalloonShowingEvent(balloon, this); 224 | 225 | //display item 226 | popup.IsOpen = true; 227 | 228 | if (timeout.HasValue) 229 | { 230 | //register timer to close the popup 231 | balloonCloseTimer.Change(timeout.Value, Timeout.Infinite); 232 | } 233 | } 234 | 235 | 236 | /// 237 | /// Resets the closing timeout, which effectively 238 | /// keeps a displayed balloon message open until 239 | /// it is either closed programmatically through 240 | /// or due to a new 241 | /// message being displayed. 242 | /// 243 | public void ResetBalloonCloseTimer() 244 | { 245 | if (IsDisposed) return; 246 | 247 | lock (this) 248 | { 249 | //reset timer in any case 250 | balloonCloseTimer.Change(Timeout.Infinite, Timeout.Infinite); 251 | } 252 | } 253 | 254 | 255 | /// 256 | /// Closes the current , if the 257 | /// property is set. 258 | /// 259 | public void CloseBalloon() 260 | { 261 | if (IsDisposed) return; 262 | 263 | Dispatcher dispatcher = this.GetDispatcher(); 264 | if (!dispatcher.CheckAccess()) 265 | { 266 | Action action = CloseBalloon; 267 | dispatcher.Invoke(DispatcherPriority.Normal, action); 268 | return; 269 | } 270 | 271 | lock (this) 272 | { 273 | //reset timer in any case 274 | balloonCloseTimer.Change(Timeout.Infinite, Timeout.Infinite); 275 | 276 | //reset old popup, if we still have one 277 | Popup popup = CustomBalloon; 278 | if (popup != null) 279 | { 280 | UIElement element = popup.Child; 281 | 282 | //announce closing 283 | RoutedEventArgs eventArgs = RaiseBalloonClosingEvent(element, this); 284 | if (!eventArgs.Handled) 285 | { 286 | //if the event was handled, clear the reference to the popup, 287 | //but don't close it - the handling code has to manage this stuff now 288 | 289 | //close the popup 290 | popup.IsOpen = false; 291 | 292 | //reset attached property 293 | if (element != null) SetParentTaskbarIcon(element, null); 294 | } 295 | 296 | //remove custom balloon anyway 297 | SetCustomBalloon(null); 298 | } 299 | } 300 | } 301 | 302 | 303 | /// 304 | /// Timer-invoke event which closes the currently open balloon and 305 | /// resets the dependency property. 306 | /// 307 | private void CloseBalloonCallback(object state) 308 | { 309 | if (IsDisposed) return; 310 | 311 | //switch to UI thread 312 | Action action = CloseBalloon; 313 | this.GetDispatcher().Invoke(action); 314 | } 315 | 316 | #endregion 317 | 318 | #region Process Incoming Mouse Events 319 | 320 | /// 321 | /// Processes mouse events, which are bubbled 322 | /// through the class' routed events, trigger 323 | /// certain actions (e.g. show a popup), or 324 | /// both. 325 | /// 326 | /// Event flag. 327 | private void OnMouseEvent(MouseEvent me) 328 | { 329 | if (IsDisposed) return; 330 | 331 | switch (me) 332 | { 333 | case MouseEvent.MouseMove: 334 | RaiseTrayMouseMoveEvent(); 335 | //immediately return - there's nothing left to evaluate 336 | return; 337 | case MouseEvent.IconRightMouseDown: 338 | RaiseTrayRightMouseDownEvent(); 339 | break; 340 | case MouseEvent.IconLeftMouseDown: 341 | RaiseTrayLeftMouseDownEvent(); 342 | break; 343 | case MouseEvent.IconRightMouseUp: 344 | RaiseTrayRightMouseUpEvent(); 345 | break; 346 | case MouseEvent.IconLeftMouseUp: 347 | RaiseTrayLeftMouseUpEvent(); 348 | break; 349 | case MouseEvent.IconMiddleMouseDown: 350 | RaiseTrayMiddleMouseDownEvent(); 351 | break; 352 | case MouseEvent.IconMiddleMouseUp: 353 | RaiseTrayMiddleMouseUpEvent(); 354 | break; 355 | case MouseEvent.IconDoubleClick: 356 | //cancel single click timer 357 | singleClickTimer.Change(Timeout.Infinite, Timeout.Infinite); 358 | //bubble event 359 | RaiseTrayMouseDoubleClickEvent(); 360 | break; 361 | case MouseEvent.BalloonToolTipClicked: 362 | RaiseTrayBalloonTipClickedEvent(); 363 | break; 364 | default: 365 | throw new ArgumentOutOfRangeException("me", "Missing handler for mouse event flag: " + me); 366 | } 367 | 368 | 369 | //get mouse coordinates 370 | Point cursorPosition = new Point(); 371 | WinApi.GetCursorPos(ref cursorPosition); 372 | 373 | bool isLeftClickCommandInvoked = false; 374 | 375 | //show popup, if requested 376 | if (me.IsMatch(PopupActivation)) 377 | { 378 | if (me == MouseEvent.IconLeftMouseUp) 379 | { 380 | //show popup once we are sure it's not a double click 381 | delayedTimerAction = () => 382 | { 383 | LeftClickCommand.ExecuteIfEnabled(LeftClickCommandParameter, LeftClickCommandTarget ?? this); 384 | ShowTrayPopup(cursorPosition); 385 | }; 386 | singleClickTimer.Change(WinApi.GetDoubleClickTime(), Timeout.Infinite); 387 | isLeftClickCommandInvoked = true; 388 | } 389 | else 390 | { 391 | //show popup immediately 392 | ShowTrayPopup(cursorPosition); 393 | } 394 | } 395 | 396 | 397 | //show context menu, if requested 398 | if (me.IsMatch(MenuActivation)) 399 | { 400 | if (me == MouseEvent.IconLeftMouseUp) 401 | { 402 | //show context menu once we are sure it's not a double click 403 | delayedTimerAction = () => 404 | { 405 | LeftClickCommand.ExecuteIfEnabled(LeftClickCommandParameter, LeftClickCommandTarget ?? this); 406 | ShowContextMenu(cursorPosition); 407 | }; 408 | singleClickTimer.Change(WinApi.GetDoubleClickTime(), Timeout.Infinite); 409 | isLeftClickCommandInvoked = true; 410 | } 411 | else 412 | { 413 | //show context menu immediately 414 | ShowContextMenu(cursorPosition); 415 | } 416 | } 417 | 418 | //make sure the left click command is invoked on mouse clicks 419 | if (me == MouseEvent.IconLeftMouseUp && !isLeftClickCommandInvoked) 420 | { 421 | //show context menu once we are sure it's not a double click 422 | delayedTimerAction = () => LeftClickCommand.ExecuteIfEnabled(LeftClickCommandParameter, LeftClickCommandTarget ?? this); 423 | singleClickTimer.Change(WinApi.GetDoubleClickTime(), Timeout.Infinite); 424 | } 425 | 426 | } 427 | 428 | #endregion 429 | 430 | #region ToolTips 431 | 432 | /// 433 | /// Displays a custom tooltip, if available. This method is only 434 | /// invoked for Windows Vista and above. 435 | /// 436 | /// Whether to show or hide the tooltip. 437 | private void OnToolTipChange(bool visible) 438 | { 439 | //if we don't have a tooltip, there's nothing to do here... 440 | if (TrayToolTipResolved == null) return; 441 | 442 | if (visible) 443 | { 444 | if (IsPopupOpen) 445 | { 446 | //ignore if we are already displaying something down there 447 | return; 448 | } 449 | 450 | var args = RaisePreviewTrayToolTipOpenEvent(); 451 | if (args.Handled) return; 452 | 453 | TrayToolTipResolved.IsOpen = true; 454 | 455 | //raise attached event first 456 | if (TrayToolTip != null) RaiseToolTipOpenedEvent(TrayToolTip); 457 | 458 | //bubble routed event 459 | RaiseTrayToolTipOpenEvent(); 460 | } 461 | else 462 | { 463 | var args = RaisePreviewTrayToolTipCloseEvent(); 464 | if (args.Handled) return; 465 | 466 | //raise attached event first 467 | if (TrayToolTip != null) RaiseToolTipCloseEvent(TrayToolTip); 468 | 469 | TrayToolTipResolved.IsOpen = false; 470 | 471 | //bubble event 472 | RaiseTrayToolTipCloseEvent(); 473 | } 474 | } 475 | 476 | 477 | /// 478 | /// Creates a control that either 479 | /// wraps the currently set 480 | /// control or the string.
481 | /// If itself is already 482 | /// a instance, it will be used directly. 483 | ///
484 | /// We use a rather than 485 | /// because there was no way to prevent a 486 | /// popup from causing cyclic open/close commands if it was 487 | /// placed under the mouse. ToolTip internally uses a Popup of 488 | /// its own, but takes advance of Popup's internal 489 | /// property which prevents this issue. 490 | private void CreateCustomToolTip() 491 | { 492 | //check if the item itself is a tooltip 493 | ToolTip tt = TrayToolTip as ToolTip; 494 | 495 | if (tt == null && TrayToolTip != null) 496 | { 497 | //create an invisible tooltip that hosts the UIElement 498 | tt = new ToolTip(); 499 | tt.Placement = PlacementMode.Mouse; 500 | 501 | //do *not* set the placement target, as it causes the popup to become hidden if the 502 | //TaskbarIcon's parent is hidden, too. At runtime, the parent can be resolved through 503 | //the ParentTaskbarIcon attached dependency property: 504 | //tt.PlacementTarget = this; 505 | 506 | //make sure the tooltip is invisible 507 | tt.HasDropShadow = false; 508 | tt.BorderThickness = new Thickness(0); 509 | tt.Background = System.Windows.Media.Brushes.Transparent; 510 | 511 | //setting the 512 | tt.StaysOpen = true; 513 | tt.Content = TrayToolTip; 514 | } 515 | else if (tt == null && !String.IsNullOrEmpty(ToolTipText)) 516 | { 517 | //create a simple tooltip for the string 518 | tt = new ToolTip(); 519 | tt.Content = ToolTipText; 520 | } 521 | 522 | //the tooltip explicitly gets the DataContext of this instance. 523 | //If there is no DataContext, the TaskbarIcon assigns itself 524 | if (tt != null) 525 | { 526 | UpdateDataContext(tt, null, DataContext); 527 | } 528 | 529 | //store a reference to the used tooltip 530 | SetTrayToolTipResolved(tt); 531 | } 532 | 533 | 534 | /// 535 | /// Sets tooltip settings for the class depending on defined 536 | /// dependency properties and OS support. 537 | /// 538 | private void WriteToolTipSettings() 539 | { 540 | const IconDataMembers flags = IconDataMembers.Tip; 541 | iconData.ToolTipText = ToolTipText; 542 | 543 | if (messageSink.Version == NotifyIconVersion.Vista) 544 | { 545 | //we need to set a tooltip text to get tooltip events from the 546 | //taskbar icon 547 | if (String.IsNullOrEmpty(iconData.ToolTipText) && TrayToolTipResolved != null) 548 | { 549 | //if we have not tooltip text but a custom tooltip, we 550 | //need to set a dummy value (we're displaying the ToolTip control, not the string) 551 | iconData.ToolTipText = "ToolTip"; 552 | } 553 | } 554 | 555 | //update the tooltip text 556 | Util.WriteIconData(ref iconData, NotifyCommand.Modify, flags); 557 | } 558 | 559 | #endregion 560 | 561 | #region Custom Popup 562 | 563 | /// 564 | /// Creates a control that either 565 | /// wraps the currently set 566 | /// control or the string.
567 | /// If itself is already 568 | /// a instance, it will be used directly. 569 | ///
570 | /// We use a rather than 571 | /// because there was no way to prevent a 572 | /// popup from causing cyclic open/close commands if it was 573 | /// placed under the mouse. ToolTip internally uses a Popup of 574 | /// its own, but takes advance of Popup's internal 575 | /// property which prevents this issue. 576 | private void CreatePopup() 577 | { 578 | //check if the item itself is a popup 579 | Popup popup = TrayPopup as Popup; 580 | 581 | if (popup == null && TrayPopup != null) 582 | { 583 | //create an invisible popup that hosts the UIElement 584 | popup = new Popup(); 585 | popup.AllowsTransparency = true; 586 | 587 | //don't animate by default - devs can use attached 588 | //events or override 589 | popup.PopupAnimation = PopupAnimation.None; 590 | 591 | //the CreateRootPopup method outputs binding errors in the debug window because 592 | //it tries to bind to "Popup-specific" properties in case they are provided by the child. 593 | //We don't need that so just assign the control as the child. 594 | popup.Child = TrayPopup; 595 | 596 | //do *not* set the placement target, as it causes the popup to become hidden if the 597 | //TaskbarIcon's parent is hidden, too. At runtime, the parent can be resolved through 598 | //the ParentTaskbarIcon attached dependency property: 599 | //popup.PlacementTarget = this; 600 | 601 | popup.Placement = PlacementMode.AbsolutePoint; 602 | popup.StaysOpen = false; 603 | } 604 | 605 | //the popup explicitly gets the DataContext of this instance. 606 | //If there is no DataContext, the TaskbarIcon assigns itself 607 | if (popup != null) 608 | { 609 | UpdateDataContext(popup, null, DataContext); 610 | } 611 | 612 | //store a reference to the used tooltip 613 | SetTrayPopupResolved(popup); 614 | } 615 | 616 | 617 | /// 618 | /// Displays the control if 619 | /// it was set. 620 | /// 621 | private void ShowTrayPopup(Point cursorPosition) 622 | { 623 | if (IsDisposed) return; 624 | 625 | //raise preview event no matter whether popup is currently set 626 | //or not (enables client to set it on demand) 627 | var args = RaisePreviewTrayPopupOpenEvent(); 628 | if (args.Handled) return; 629 | 630 | if (TrayPopup != null) 631 | { 632 | //use absolute position, but place the popup centered above the icon 633 | TrayPopupResolved.Placement = PlacementMode.AbsolutePoint; 634 | TrayPopupResolved.HorizontalOffset = cursorPosition.X; 635 | TrayPopupResolved.VerticalOffset = cursorPosition.Y; 636 | 637 | //open popup 638 | TrayPopupResolved.IsOpen = true; 639 | 640 | 641 | IntPtr handle = IntPtr.Zero; 642 | if (TrayPopupResolved.Child != null) 643 | { 644 | //try to get a handle on the popup itself (via its child) 645 | HwndSource source = (HwndSource)PresentationSource.FromVisual(TrayPopupResolved.Child); 646 | if (source != null) handle = source.Handle; 647 | } 648 | 649 | //if we don't have a handle for the popup, fall back to the message sink 650 | if (handle == IntPtr.Zero) handle = messageSink.MessageWindowHandle; 651 | 652 | //activate either popup or message sink to track deactivation. 653 | //otherwise, the popup does not close if the user clicks somewhere else 654 | WinApi.SetForegroundWindow(handle); 655 | 656 | //raise attached event - item should never be null unless developers 657 | //changed the CustomPopup directly... 658 | if (TrayPopup != null) RaisePopupOpenedEvent(TrayPopup); 659 | 660 | //bubble routed event 661 | RaiseTrayPopupOpenEvent(); 662 | } 663 | } 664 | 665 | #endregion 666 | 667 | #region Context Menu 668 | 669 | /// 670 | /// Displays the if 671 | /// it was set. 672 | /// 673 | private void ShowContextMenu(Point cursorPosition) 674 | { 675 | if (IsDisposed) return; 676 | 677 | //raise preview event no matter whether context menu is currently set 678 | //or not (enables client to set it on demand) 679 | var args = RaisePreviewTrayContextMenuOpenEvent(); 680 | if (args.Handled) return; 681 | 682 | if (ContextMenu != null) 683 | { 684 | //use absolute position 685 | ContextMenu.Placement = PlacementMode.AbsolutePoint; 686 | ContextMenu.HorizontalOffset = cursorPosition.X; 687 | ContextMenu.VerticalOffset = cursorPosition.Y; 688 | ContextMenu.IsOpen = true; 689 | 690 | //activate the message window to track deactivation - otherwise, the context menu 691 | //does not close if the user clicks somewhere else 692 | WinApi.SetForegroundWindow(messageSink.MessageWindowHandle); 693 | 694 | //bubble event 695 | RaiseTrayContextMenuOpenEvent(); 696 | } 697 | } 698 | 699 | #endregion 700 | 701 | #region Balloon Tips 702 | 703 | /// 704 | /// Bubbles events if a balloon ToolTip was displayed 705 | /// or removed. 706 | /// 707 | /// Whether the ToolTip was just displayed 708 | /// or removed. 709 | private void OnBalloonToolTipChanged(bool visible) 710 | { 711 | if (visible) 712 | { 713 | RaiseTrayBalloonTipShownEvent(); 714 | } 715 | else 716 | { 717 | RaiseTrayBalloonTipClosedEvent(); 718 | } 719 | } 720 | 721 | /// 722 | /// Displays a balloon tip with the specified title, 723 | /// text, and icon in the taskbar for the specified time period. 724 | /// 725 | /// The title to display on the balloon tip. 726 | /// The text to display on the balloon tip. 727 | /// A symbol that indicates the severity. 728 | public void ShowBalloonTip(string title, string message, BalloonIcon symbol) 729 | { 730 | lock (this) 731 | { 732 | ShowBalloonTip(title, message, symbol.GetBalloonFlag(), IntPtr.Zero); 733 | } 734 | } 735 | 736 | 737 | /// 738 | /// Displays a balloon tip with the specified title, 739 | /// text, and a custom icon in the taskbar for the specified time period. 740 | /// 741 | /// The title to display on the balloon tip. 742 | /// The text to display on the balloon tip. 743 | /// A custom icon. 744 | /// If 745 | /// is a null reference. 746 | public void ShowBalloonTip(string title, string message, Icon customIcon) 747 | { 748 | if (customIcon == null) throw new ArgumentNullException("customIcon"); 749 | 750 | lock (this) 751 | { 752 | ShowBalloonTip(title, message, BalloonFlags.User, customIcon.Handle); 753 | } 754 | } 755 | 756 | 757 | /// 758 | /// Invokes in order to display 759 | /// a given balloon ToolTip. 760 | /// 761 | /// The title to display on the balloon tip. 762 | /// The text to display on the balloon tip. 763 | /// Indicates what icon to use. 764 | /// A handle to a custom icon, if any, or 765 | /// . 766 | private void ShowBalloonTip(string title, string message, BalloonFlags flags, IntPtr balloonIconHandle) 767 | { 768 | EnsureNotDisposed(); 769 | 770 | iconData.BalloonText = message ?? String.Empty; 771 | iconData.BalloonTitle = title ?? String.Empty; 772 | 773 | iconData.BalloonFlags = flags; 774 | iconData.CustomBalloonIconHandle = balloonIconHandle; 775 | Util.WriteIconData(ref iconData, NotifyCommand.Modify, IconDataMembers.Info | IconDataMembers.Icon); 776 | } 777 | 778 | 779 | /// 780 | /// Hides a balloon ToolTip, if any is displayed. 781 | /// 782 | public void HideBalloonTip() 783 | { 784 | EnsureNotDisposed(); 785 | 786 | //reset balloon by just setting the info to an empty string 787 | iconData.BalloonText = iconData.BalloonTitle = String.Empty; 788 | Util.WriteIconData(ref iconData, NotifyCommand.Modify, IconDataMembers.Info); 789 | } 790 | 791 | #endregion 792 | 793 | #region Single Click Timer event 794 | 795 | /// 796 | /// Performs a delayed action if the user requested an action 797 | /// based on a single click of the left mouse.
798 | /// This method is invoked by the . 799 | ///
800 | private void DoSingleClickAction(object state) 801 | { 802 | if (IsDisposed) return; 803 | 804 | //run action 805 | Action action = delayedTimerAction; 806 | if (action != null) 807 | { 808 | //cleanup action 809 | delayedTimerAction = null; 810 | 811 | //switch to UI thread 812 | this.GetDispatcher().Invoke(action); 813 | } 814 | } 815 | 816 | #endregion 817 | 818 | #region Set Version (API) 819 | 820 | /// 821 | /// Sets the version flag for the . 822 | /// 823 | private void SetVersion() 824 | { 825 | iconData.VersionOrTimeout = (uint) NotifyIconVersion.Vista; 826 | bool status = WinApi.Shell_NotifyIcon(NotifyCommand.SetVersion, ref iconData); 827 | 828 | if (!status) 829 | { 830 | iconData.VersionOrTimeout = (uint) NotifyIconVersion.Win2000; 831 | status = Util.WriteIconData(ref iconData, NotifyCommand.SetVersion); 832 | } 833 | 834 | if (!status) 835 | { 836 | iconData.VersionOrTimeout = (uint) NotifyIconVersion.Win95; 837 | status = Util.WriteIconData(ref iconData, NotifyCommand.SetVersion); 838 | } 839 | 840 | if (!status) 841 | { 842 | Debug.Fail("Could not set version"); 843 | } 844 | } 845 | 846 | #endregion 847 | 848 | #region Create / Remove Taskbar Icon 849 | 850 | /// 851 | /// Recreates the taskbar icon if the whole taskbar was 852 | /// recreated (e.g. because Explorer was shut down). 853 | /// 854 | private void OnTaskbarCreated() 855 | { 856 | IsTaskbarIconCreated = false; 857 | CreateTaskbarIcon(); 858 | } 859 | 860 | 861 | /// 862 | /// Creates the taskbar icon. This message is invoked during initialization, 863 | /// if the taskbar is restarted, and whenever the icon is displayed. 864 | /// 865 | private void CreateTaskbarIcon() 866 | { 867 | lock (this) 868 | { 869 | if (!IsTaskbarIconCreated) 870 | { 871 | const IconDataMembers members = IconDataMembers.Message 872 | | IconDataMembers.Icon 873 | | IconDataMembers.Tip; 874 | 875 | //write initial configuration 876 | var status = Util.WriteIconData(ref iconData, NotifyCommand.Add, members); 877 | if (!status) 878 | { 879 | throw new Win32Exception("Could not create icon data"); 880 | } 881 | 882 | //set to most recent version 883 | SetVersion(); 884 | messageSink.Version = (NotifyIconVersion) iconData.VersionOrTimeout; 885 | 886 | IsTaskbarIconCreated = true; 887 | } 888 | } 889 | } 890 | 891 | 892 | /// 893 | /// Closes the taskbar icon if required. 894 | /// 895 | private void RemoveTaskbarIcon() 896 | { 897 | lock (this) 898 | { 899 | if (IsTaskbarIconCreated) 900 | { 901 | Util.WriteIconData(ref iconData, NotifyCommand.Delete, IconDataMembers.Message); 902 | IsTaskbarIconCreated = false; 903 | } 904 | } 905 | } 906 | 907 | #endregion 908 | 909 | #region Dispose / Exit 910 | 911 | /// 912 | /// Set to true as soon as 913 | /// has been invoked. 914 | /// 915 | public bool IsDisposed { get; private set; } 916 | 917 | 918 | /// 919 | /// Checks if the object has been disposed and 920 | /// raises a in case 921 | /// the flag is true. 922 | /// 923 | private void EnsureNotDisposed() 924 | { 925 | if (IsDisposed) throw new ObjectDisposedException(Name ?? GetType().FullName); 926 | } 927 | 928 | 929 | /// 930 | /// Disposes the class if the application exits. 931 | /// 932 | private void OnExit(object sender, EventArgs e) 933 | { 934 | Dispose(); 935 | } 936 | 937 | 938 | /// 939 | /// This destructor will run only if the 940 | /// method does not get called. This gives this base class the 941 | /// opportunity to finalize. 942 | /// 943 | /// Important: Do not provide destructors in types derived from 944 | /// this class. 945 | /// 946 | /// 947 | ~TaskbarIcon() 948 | { 949 | Dispose(false); 950 | } 951 | 952 | 953 | /// 954 | /// Disposes the object. 955 | /// 956 | /// This method is not virtual by design. Derived classes 957 | /// should override . 958 | /// 959 | public void Dispose() 960 | { 961 | Dispose(true); 962 | 963 | // This object will be cleaned up by the Dispose method. 964 | // Therefore, you should call GC.SupressFinalize to 965 | // take this object off the finalization queue 966 | // and prevent finalization code for this object 967 | // from executing a second time. 968 | GC.SuppressFinalize(this); 969 | } 970 | 971 | 972 | /// 973 | /// Closes the tray and releases all resources. 974 | /// 975 | /// 976 | /// Dispose(bool disposing) executes in two distinct scenarios. 977 | /// If disposing equals true, the method has been called directly 978 | /// or indirectly by a user's code. Managed and unmanaged resources 979 | /// can be disposed. 980 | /// 981 | /// If disposing equals false, the method 982 | /// has been called by the runtime from inside the finalizer and you 983 | /// should not reference other objects. Only unmanaged resources can 984 | /// be disposed. 985 | /// Check the property to determine whether 986 | /// the method has already been called. 987 | private void Dispose(bool disposing) 988 | { 989 | //don't do anything if the component is already disposed 990 | if (IsDisposed || !disposing) return; 991 | 992 | lock (this) 993 | { 994 | IsDisposed = true; 995 | 996 | //deregister application event listener 997 | if (Application.Current != null) 998 | { 999 | Application.Current.Exit -= OnExit; 1000 | } 1001 | 1002 | //stop timers 1003 | singleClickTimer.Dispose(); 1004 | balloonCloseTimer.Dispose(); 1005 | 1006 | //dispose message sink 1007 | messageSink.Dispose(); 1008 | 1009 | //remove icon 1010 | RemoveTaskbarIcon(); 1011 | } 1012 | } 1013 | 1014 | #endregion 1015 | } 1016 | } -------------------------------------------------------------------------------- /TaskbarIcon/Interop/TrayInfo.cs: -------------------------------------------------------------------------------- 1 | // Some interop code taken from Mike Marshall's AnyForm 2 | 3 | using System; 4 | using System.Drawing; 5 | using System.Runtime.InteropServices; 6 | 7 | 8 | namespace Hardcodet.Wpf.TaskbarNotification.Interop 9 | { 10 | /// 11 | /// Resolves the current tray position. 12 | /// 13 | public static class TrayInfo 14 | { 15 | /// 16 | /// Gets the position of the system tray. 17 | /// 18 | /// Tray coordinates. 19 | public static Point GetTrayLocation() 20 | { 21 | var info = new AppBarInfo(); 22 | info.GetSystemTaskBarPosition(); 23 | 24 | Rectangle rcWorkArea = info.WorkArea; 25 | 26 | int x = 0, y = 0; 27 | if (info.Edge == AppBarInfo.ScreenEdge.Left) 28 | { 29 | x = rcWorkArea.Left + 2; 30 | y = rcWorkArea.Bottom; 31 | } 32 | else if (info.Edge == AppBarInfo.ScreenEdge.Bottom) 33 | { 34 | x = rcWorkArea.Right; 35 | y = rcWorkArea.Bottom; 36 | } 37 | else if (info.Edge == AppBarInfo.ScreenEdge.Top) 38 | { 39 | x = rcWorkArea.Right; 40 | y = rcWorkArea.Top; 41 | } 42 | else if (info.Edge == AppBarInfo.ScreenEdge.Right) 43 | { 44 | x = rcWorkArea.Right; 45 | y = rcWorkArea.Bottom; 46 | } 47 | 48 | return new Point { X = x, Y = y}; 49 | } 50 | } 51 | 52 | 53 | 54 | 55 | internal class AppBarInfo 56 | { 57 | 58 | [DllImport("user32.dll")] 59 | private static extern IntPtr FindWindow(String lpClassName, String lpWindowName); 60 | 61 | [DllImport("shell32.dll")] 62 | private static extern UInt32 SHAppBarMessage(UInt32 dwMessage, ref APPBARDATA data); 63 | 64 | [DllImport("user32.dll")] 65 | private static extern Int32 SystemParametersInfo(UInt32 uiAction, UInt32 uiParam, 66 | IntPtr pvParam, UInt32 fWinIni); 67 | 68 | 69 | private const int ABE_BOTTOM = 3; 70 | private const int ABE_LEFT = 0; 71 | private const int ABE_RIGHT = 2; 72 | private const int ABE_TOP = 1; 73 | 74 | private const int ABM_GETTASKBARPOS = 0x00000005; 75 | 76 | // SystemParametersInfo constants 77 | private const UInt32 SPI_GETWORKAREA = 0x0030; 78 | 79 | private APPBARDATA m_data; 80 | 81 | public ScreenEdge Edge 82 | { 83 | get { return (ScreenEdge) m_data.uEdge; } 84 | } 85 | 86 | 87 | public Rectangle WorkArea 88 | { 89 | get 90 | { 91 | Int32 bResult = 0; 92 | var rc = new RECT(); 93 | IntPtr rawRect = Marshal.AllocHGlobal(Marshal.SizeOf(rc)); 94 | bResult = SystemParametersInfo(SPI_GETWORKAREA, 0, rawRect, 0); 95 | rc = (RECT) Marshal.PtrToStructure(rawRect, rc.GetType()); 96 | 97 | if (bResult == 1) 98 | { 99 | Marshal.FreeHGlobal(rawRect); 100 | return new Rectangle(rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top); 101 | } 102 | 103 | return new Rectangle(0, 0, 0, 0); 104 | } 105 | } 106 | 107 | 108 | 109 | public void GetPosition(string strClassName, string strWindowName) 110 | { 111 | m_data = new APPBARDATA(); 112 | m_data.cbSize = (UInt32) Marshal.SizeOf(m_data.GetType()); 113 | 114 | IntPtr hWnd = FindWindow(strClassName, strWindowName); 115 | 116 | if (hWnd != IntPtr.Zero) 117 | { 118 | UInt32 uResult = SHAppBarMessage(ABM_GETTASKBARPOS, ref m_data); 119 | 120 | if (uResult != 1) 121 | { 122 | throw new Exception("Failed to communicate with the given AppBar"); 123 | } 124 | } 125 | else 126 | { 127 | throw new Exception("Failed to find an AppBar that matched the given criteria"); 128 | } 129 | } 130 | 131 | 132 | public void GetSystemTaskBarPosition() 133 | { 134 | GetPosition("Shell_TrayWnd", null); 135 | } 136 | 137 | 138 | 139 | 140 | public enum ScreenEdge 141 | { 142 | Undefined = -1, 143 | Left = ABE_LEFT, 144 | Top = ABE_TOP, 145 | Right = ABE_RIGHT, 146 | Bottom = ABE_BOTTOM 147 | } 148 | 149 | 150 | [StructLayout(LayoutKind.Sequential)] 151 | private struct APPBARDATA 152 | { 153 | public UInt32 cbSize; 154 | public IntPtr hWnd; 155 | public UInt32 uCallbackMessage; 156 | public UInt32 uEdge; 157 | public RECT rc; 158 | public Int32 lParam; 159 | } 160 | 161 | [StructLayout(LayoutKind.Sequential)] 162 | private struct RECT 163 | { 164 | public Int32 left; 165 | public Int32 top; 166 | public Int32 right; 167 | public Int32 bottom; 168 | } 169 | 170 | } 171 | } -------------------------------------------------------------------------------- /TaskbarIcon/Interop/WinApi.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace Hardcodet.Wpf.TaskbarNotification.Interop 5 | { 6 | /// 7 | /// Win32 API imports. 8 | /// 9 | internal static class WinApi 10 | { 11 | /// 12 | /// Creates, updates or deletes the taskbar icon. 13 | /// 14 | [DllImport("shell32.Dll")] 15 | public static extern bool Shell_NotifyIcon(NotifyCommand cmd, [In]ref NotifyIconData data); 16 | 17 | 18 | /// 19 | /// Creates the helper window that receives messages from the taskar icon. 20 | /// 21 | [DllImport("USER32.DLL", EntryPoint = "CreateWindowExW", SetLastError = true)] 22 | public static extern IntPtr CreateWindowEx(int dwExStyle, [MarshalAs(UnmanagedType.LPWStr)] string lpClassName, 23 | [MarshalAs(UnmanagedType.LPWStr)] string lpWindowName, int dwStyle, int x, int y, 24 | int nWidth, int nHeight, uint hWndParent, int hMenu, int hInstance, 25 | int lpParam); 26 | 27 | 28 | /// 29 | /// Processes a default windows procedure. 30 | /// 31 | [DllImport("USER32.DLL")] 32 | public static extern long DefWindowProc(IntPtr hWnd, uint msg, uint wparam, uint lparam); 33 | 34 | /// 35 | /// Registers the helper window class. 36 | /// 37 | [DllImport("USER32.DLL", EntryPoint = "RegisterClassW", SetLastError = true)] 38 | public static extern short RegisterClass(ref WindowClass lpWndClass); 39 | 40 | /// 41 | /// Registers a listener for a window message. 42 | /// 43 | /// 44 | /// 45 | [DllImport("User32.Dll", EntryPoint = "RegisterWindowMessageW")] 46 | public static extern uint RegisterWindowMessage([MarshalAs(UnmanagedType.LPWStr)] string lpString); 47 | 48 | /// 49 | /// Used to destroy the hidden helper window that receives messages from the 50 | /// taskbar icon. 51 | /// 52 | /// 53 | /// 54 | [DllImport("USER32.DLL", SetLastError = true)] 55 | public static extern bool DestroyWindow(IntPtr hWnd); 56 | 57 | 58 | /// 59 | /// Gives focus to a given window. 60 | /// 61 | /// 62 | /// 63 | [DllImport("USER32.DLL")] 64 | public static extern bool SetForegroundWindow(IntPtr hWnd); 65 | 66 | 67 | /// 68 | /// Gets the maximum number of milliseconds that can elapse between a 69 | /// first click and a second click for the OS to consider the 70 | /// mouse action a double-click. 71 | /// 72 | /// The maximum amount of time, in milliseconds, that can 73 | /// elapse between a first click and a second click for the OS to 74 | /// consider the mouse action a double-click. 75 | [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] 76 | public static extern int GetDoubleClickTime(); 77 | 78 | 79 | /// 80 | /// Gets the screen coordinates of the current mouse position. 81 | /// 82 | /// 83 | /// 84 | [DllImport("USER32.DLL", SetLastError = true)] 85 | public static extern bool GetCursorPos(ref Point lpPoint); 86 | } 87 | } -------------------------------------------------------------------------------- /TaskbarIcon/Interop/WindowClass.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace Hardcodet.Wpf.TaskbarNotification.Interop 5 | { 6 | /// 7 | /// Callback delegate which is used by the Windows API to 8 | /// submit window messages. 9 | /// 10 | public delegate long WindowProcedureHandler(IntPtr hwnd, uint uMsg, uint wparam, uint lparam); 11 | 12 | 13 | /// 14 | /// Win API WNDCLASS struct - represents a single window. 15 | /// Used to receive window messages. 16 | /// 17 | [StructLayout(LayoutKind.Sequential)] 18 | public struct WindowClass 19 | { 20 | public uint style; 21 | public WindowProcedureHandler lpfnWndProc; 22 | public int cbClsExtra; 23 | public int cbWndExtra; 24 | public IntPtr hInstance; 25 | public IntPtr hIcon; 26 | public IntPtr hCursor; 27 | public IntPtr hbrBackground; 28 | [MarshalAs(UnmanagedType.LPWStr)] public string lpszMenuName; 29 | [MarshalAs(UnmanagedType.LPWStr)] public string lpszClassName; 30 | } 31 | } -------------------------------------------------------------------------------- /TaskbarIcon/Interop/WindowMessageSink.cs: -------------------------------------------------------------------------------- 1 | // hardcodet.net NotifyIcon for WPF 2 | // Copyright (c) 2009 Philipp Sumi 3 | // Contact and Information: http://www.hardcodet.net 4 | // 5 | // This library is free software; you can redistribute it and/or 6 | // modify it under the terms of the Code Project Open License (CPOL); 7 | // either version 1.0 of the License, or (at your option) any later 8 | // version. 9 | // 10 | // The above copyright notice and this permission notice shall be 11 | // included in all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 14 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 15 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 16 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 17 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 18 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 20 | // OTHER DEALINGS IN THE SOFTWARE. 21 | // 22 | // THIS COPYRIGHT NOTICE MAY NOT BE REMOVED FROM THIS FILE 23 | 24 | 25 | 26 | using System; 27 | using System.ComponentModel; 28 | using System.Diagnostics; 29 | 30 | namespace Hardcodet.Wpf.TaskbarNotification.Interop 31 | { 32 | /// 33 | /// Receives messages from the taskbar icon through 34 | /// window messages of an underlying helper window. 35 | /// 36 | public class WindowMessageSink : IDisposable 37 | { 38 | 39 | #region members 40 | 41 | /// 42 | /// The ID of messages that are received from the the 43 | /// taskbar icon. 44 | /// 45 | public const int CallbackMessageId = 0x400; 46 | 47 | /// 48 | /// The ID of the message that is being received if the 49 | /// taskbar is (re)started. 50 | /// 51 | private uint taskbarRestartMessageId; 52 | 53 | /// 54 | /// Used to track whether a mouse-up event is just 55 | /// the aftermath of a double-click and therefore needs 56 | /// to be suppressed. 57 | /// 58 | private bool isDoubleClick; 59 | 60 | /// 61 | /// A delegate that processes messages of the hidden 62 | /// native window that receives window messages. Storing 63 | /// this reference makes sure we don't loose our reference 64 | /// to the message window. 65 | /// 66 | private WindowProcedureHandler messageHandler; 67 | 68 | /// 69 | /// Window class ID. 70 | /// 71 | internal string WindowId { get; private set; } 72 | 73 | /// 74 | /// Handle for the message window. 75 | /// 79 | /// The version of the underlying icon. Defines how 80 | /// incoming messages are interpreted. 81 | /// 82 | public NotifyIconVersion Version { get; set; } 83 | 84 | #endregion 85 | 86 | 87 | #region events 88 | 89 | /// 90 | /// The custom tooltip should be closed or hidden. 91 | /// 92 | public event Action ChangeToolTipStateRequest; 93 | 94 | /// 95 | /// Fired in case the user clicked or moved within 96 | /// the taskbar icon area. 97 | /// 98 | public event Action MouseEventReceived; 99 | 100 | /// 101 | /// Fired if a balloon ToolTip was either displayed 102 | /// or closed (indicated by the boolean flag). 103 | /// 104 | public event Action BalloonToolTipChanged; 105 | 106 | /// 107 | /// Fired if the taskbar was created or restarted. Requires the taskbar 108 | /// icon to be reset. 109 | /// 110 | public event Action TaskbarCreated; 111 | 112 | #endregion 113 | 114 | 115 | #region construction 116 | 117 | /// 118 | /// Creates a new message sink that receives message from 119 | /// a given taskbar icon. 120 | /// 121 | /// 122 | public WindowMessageSink(NotifyIconVersion version) 123 | { 124 | Version = version; 125 | CreateMessageWindow(); 126 | } 127 | 128 | 129 | private WindowMessageSink() 130 | { 131 | } 132 | 133 | 134 | /// 135 | /// Creates a dummy instance that provides an empty 136 | /// pointer rather than a real window handler.
137 | /// Used at design time. 138 | ///
139 | /// 140 | internal static WindowMessageSink CreateEmpty() 141 | { 142 | return new WindowMessageSink 143 | { 144 | MessageWindowHandle = IntPtr.Zero, 145 | Version = NotifyIconVersion.Vista 146 | }; 147 | } 148 | 149 | #endregion 150 | 151 | 152 | #region CreateMessageWindow 153 | 154 | /// 155 | /// Creates the helper message window that is used 156 | /// to receive messages from the taskbar icon. 157 | /// 158 | private void CreateMessageWindow() 159 | { 160 | //generate a unique ID for the window 161 | WindowId = "WPFTaskbarIcon_" + DateTime.Now.Ticks; 162 | 163 | //register window message handler 164 | messageHandler = OnWindowMessageReceived; 165 | 166 | // Create a simple window class which is reference through 167 | //the messageHandler delegate 168 | WindowClass wc; 169 | 170 | wc.style = 0; 171 | wc.lpfnWndProc = messageHandler; 172 | wc.cbClsExtra = 0; 173 | wc.cbWndExtra = 0; 174 | wc.hInstance = IntPtr.Zero; 175 | wc.hIcon = IntPtr.Zero; 176 | wc.hCursor = IntPtr.Zero; 177 | wc.hbrBackground = IntPtr.Zero; 178 | wc.lpszMenuName = ""; 179 | wc.lpszClassName = WindowId; 180 | 181 | // Register the window class 182 | WinApi.RegisterClass(ref wc); 183 | 184 | // Get the message used to indicate the taskbar has been restarted 185 | // This is used to re-add icons when the taskbar restarts 186 | taskbarRestartMessageId = WinApi.RegisterWindowMessage("TaskbarCreated"); 187 | 188 | // Create the message window 189 | MessageWindowHandle = WinApi.CreateWindowEx(0, WindowId, "", 0, 0, 0, 1, 1, 0, 0, 0, 0); 190 | 191 | if (MessageWindowHandle == IntPtr.Zero) 192 | { 193 | throw new Win32Exception(); 194 | } 195 | } 196 | 197 | #endregion 198 | 199 | 200 | #region Handle Window Messages 201 | 202 | /// 203 | /// Callback method that receives messages from the taskbar area. 204 | /// 205 | private long OnWindowMessageReceived(IntPtr hwnd, uint messageId, uint wparam, uint lparam) 206 | { 207 | if (messageId == taskbarRestartMessageId) 208 | { 209 | //recreate the icon if the taskbar was restarted (e.g. due to Win Explorer shutdown) 210 | TaskbarCreated(); 211 | } 212 | 213 | //forward message 214 | ProcessWindowMessage(messageId, wparam, lparam); 215 | 216 | // Pass the message to the default window procedure 217 | return WinApi.DefWindowProc(hwnd, messageId, wparam, lparam); 218 | } 219 | 220 | 221 | /// 222 | /// Processes incoming system messages. 223 | /// 224 | /// Callback ID. 225 | /// If the version is 226 | /// or higher, this parameter can be used to resolve mouse coordinates. 227 | /// Currently not in use. 228 | /// Provides information about the event. 229 | private void ProcessWindowMessage(uint msg, uint wParam, uint lParam) 230 | { 231 | if (msg != CallbackMessageId) return; 232 | 233 | switch (lParam) 234 | { 235 | case 0x200: 236 | MouseEventReceived(MouseEvent.MouseMove); 237 | break; 238 | 239 | case 0x201: 240 | MouseEventReceived(MouseEvent.IconLeftMouseDown); 241 | break; 242 | 243 | case 0x202: 244 | if (!isDoubleClick) 245 | { 246 | MouseEventReceived(MouseEvent.IconLeftMouseUp); 247 | } 248 | isDoubleClick = false; 249 | break; 250 | 251 | case 0x203: 252 | isDoubleClick = true; 253 | MouseEventReceived(MouseEvent.IconDoubleClick); 254 | break; 255 | 256 | case 0x204: 257 | MouseEventReceived(MouseEvent.IconRightMouseDown); 258 | break; 259 | 260 | case 0x205: 261 | MouseEventReceived(MouseEvent.IconRightMouseUp); 262 | break; 263 | 264 | case 0x206: 265 | //double click with right mouse button - do not trigger event 266 | break; 267 | 268 | case 0x207: 269 | MouseEventReceived(MouseEvent.IconMiddleMouseDown); 270 | break; 271 | 272 | case 520: 273 | MouseEventReceived(MouseEvent.IconMiddleMouseUp); 274 | break; 275 | 276 | case 0x209: 277 | //double click with middle mouse button - do not trigger event 278 | break; 279 | 280 | case 0x402: 281 | BalloonToolTipChanged(true); 282 | break; 283 | 284 | case 0x403: 285 | case 0x404: 286 | BalloonToolTipChanged(false); 287 | break; 288 | 289 | case 0x405: 290 | MouseEventReceived(MouseEvent.BalloonToolTipClicked); 291 | break; 292 | 293 | case 0x406: 294 | ChangeToolTipStateRequest(true); 295 | break; 296 | 297 | case 0x407: 298 | ChangeToolTipStateRequest(false); 299 | break; 300 | 301 | default: 302 | Debug.WriteLine("Unhandled NotifyIcon message ID: " + lParam); 303 | break; 304 | } 305 | 306 | } 307 | 308 | #endregion 309 | 310 | 311 | #region Dispose 312 | 313 | /// 314 | /// Set to true as soon as 315 | /// has been invoked. 316 | /// 317 | public bool IsDisposed { get; private set; } 318 | 319 | 320 | /// 321 | /// Disposes the object. 322 | /// 323 | /// This method is not virtual by design. Derived classes 324 | /// should override . 325 | /// 326 | public void Dispose() 327 | { 328 | Dispose(true); 329 | 330 | // This object will be cleaned up by the Dispose method. 331 | // Therefore, you should call GC.SupressFinalize to 332 | // take this object off the finalization queue 333 | // and prevent finalization code for this object 334 | // from executing a second time. 335 | GC.SuppressFinalize(this); 336 | } 337 | 338 | /// 339 | /// This destructor will run only if the 340 | /// method does not get called. This gives this base class the 341 | /// opportunity to finalize. 342 | /// 343 | /// Important: Do not provide destructors in types derived from 344 | /// this class. 345 | /// 346 | /// 347 | ~WindowMessageSink() 348 | { 349 | Dispose(false); 350 | } 351 | 352 | 353 | /// 354 | /// Removes the windows hook that receives window 355 | /// messages and closes the underlying helper window. 356 | /// 357 | private void Dispose(bool disposing) 358 | { 359 | //don't do anything if the component is already disposed 360 | if (IsDisposed || !disposing) return; 361 | IsDisposed = true; 362 | 363 | WinApi.DestroyWindow(MessageWindowHandle); 364 | messageHandler = null; 365 | } 366 | 367 | #endregion 368 | } 369 | } -------------------------------------------------------------------------------- /TaskbarIcon/PopupActivationMode.cs: -------------------------------------------------------------------------------- 1 | // hardcodet.net NotifyIcon for WPF 2 | // Copyright (c) 2009 Philipp Sumi 3 | // Contact and Information: http://www.hardcodet.net 4 | // 5 | // This library is free software; you can redistribute it and/or 6 | // modify it under the terms of the Code Project Open License (CPOL); 7 | // either version 1.0 of the License, or (at your option) any later 8 | // version. 9 | // 10 | // The above copyright notice and this permission notice shall be 11 | // included in all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 14 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 15 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 16 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 17 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 18 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 20 | // OTHER DEALINGS IN THE SOFTWARE. 21 | // 22 | // THIS COPYRIGHT NOTICE MAY NOT BE REMOVED FROM THIS FILE 23 | 24 | 25 | namespace Hardcodet.Wpf.TaskbarNotification 26 | { 27 | /// 28 | /// Defines flags that define when a popup 29 | /// is being displyed. 30 | /// 31 | public enum PopupActivationMode 32 | { 33 | /// 34 | /// The item is displayed if the user clicks the 35 | /// tray icon with the left mouse button. 36 | /// 37 | LeftClick, 38 | /// 39 | /// The item is displayed if the user clicks the 40 | /// tray icon with the right mouse button. 41 | /// 42 | RightClick, 43 | /// 44 | /// The item is displayed if the user double-clicks the 45 | /// tray icon. 46 | /// 47 | DoubleClick, 48 | /// 49 | /// The item is displayed if the user clicks the 50 | /// tray icon with the left or the right mouse button. 51 | /// 52 | LeftOrRightClick, 53 | /// 54 | /// The item is displayed if the user clicks the 55 | /// tray icon with the left mouse button or if a 56 | /// double-click is being performed. 57 | /// 58 | LeftOrDoubleClick, 59 | /// 60 | /// The item is displayed if the user clicks the 61 | /// tray icon with the middle mouse button. 62 | /// 63 | MiddleClick, 64 | /// 65 | /// The item is displayed whenever a click occurs. 66 | /// 67 | All 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /TaskbarIcon/RoutedEventHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows; 3 | 4 | namespace Hardcodet.Wpf.TaskbarNotification 5 | { 6 | /// 7 | /// Helper class used by routed events of the 8 | /// class. 9 | /// 10 | internal static class RoutedEventHelper 11 | { 12 | #region RoutedEvent Helper Methods 13 | 14 | /// 15 | /// A static helper method to raise a routed event on a target UIElement or ContentElement. 16 | /// 17 | /// UIElement or ContentElement on which to raise the event 18 | /// RoutedEventArgs to use when raising the event 19 | internal static void RaiseEvent(DependencyObject target, RoutedEventArgs args) 20 | { 21 | if (target is UIElement) 22 | { 23 | (target as UIElement).RaiseEvent(args); 24 | } 25 | else if (target is ContentElement) 26 | { 27 | (target as ContentElement).RaiseEvent(args); 28 | } 29 | } 30 | 31 | /// 32 | /// A static helper method that adds a handler for a routed event 33 | /// to a target UIElement or ContentElement. 34 | /// 35 | /// UIElement or ContentElement that listens to the event 36 | /// Event that will be handled 37 | /// Event handler to be added 38 | internal static void AddHandler(DependencyObject element, RoutedEvent routedEvent, Delegate handler) 39 | { 40 | UIElement uie = element as UIElement; 41 | if (uie != null) 42 | { 43 | uie.AddHandler(routedEvent, handler); 44 | } 45 | else 46 | { 47 | ContentElement ce = element as ContentElement; 48 | if (ce != null) 49 | { 50 | ce.AddHandler(routedEvent, handler); 51 | } 52 | } 53 | } 54 | 55 | /// 56 | /// A static helper method that removes a handler for a routed event 57 | /// from a target UIElement or ContentElement. 58 | /// 59 | /// UIElement or ContentElement that listens to the event 60 | /// Event that will no longer be handled 61 | /// Event handler to be removed 62 | internal static void RemoveHandler(DependencyObject element, RoutedEvent routedEvent, Delegate handler) 63 | { 64 | UIElement uie = element as UIElement; 65 | if (uie != null) 66 | { 67 | uie.RemoveHandler(routedEvent, handler); 68 | } 69 | else 70 | { 71 | ContentElement ce = element as ContentElement; 72 | if (ce != null) 73 | { 74 | ce.RemoveHandler(routedEvent, handler); 75 | } 76 | } 77 | } 78 | 79 | #endregion 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /TaskbarIcon/Util.cs: -------------------------------------------------------------------------------- 1 | // hardcodet.net NotifyIcon for WPF 2 | // Copyright (c) 2009 Philipp Sumi 3 | // Contact and Information: http://www.hardcodet.net 4 | // 5 | // This library is free software; you can redistribute it and/or 6 | // modify it under the terms of the Code Project Open License (CPOL); 7 | // either version 1.0 of the License, or (at your option) any later 8 | // version. 9 | // 10 | // The above copyright notice and this permission notice shall be 11 | // included in all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 14 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 15 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 16 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 17 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 18 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 20 | // OTHER DEALINGS IN THE SOFTWARE. 21 | // 22 | // THIS COPYRIGHT NOTICE MAY NOT BE REMOVED FROM THIS FILE 23 | 24 | 25 | using System; 26 | using System.ComponentModel; 27 | using System.Drawing; 28 | using System.Windows; 29 | using System.Windows.Input; 30 | using System.Windows.Media; 31 | using System.Windows.Resources; 32 | using System.Windows.Threading; 33 | using Hardcodet.Wpf.TaskbarNotification.Interop; 34 | 35 | namespace Hardcodet.Wpf.TaskbarNotification 36 | { 37 | /// 38 | /// Util and extension methods. 39 | /// 40 | internal static class Util 41 | { 42 | public static readonly object SyncRoot = new object(); 43 | 44 | #region IsDesignMode 45 | 46 | private static readonly bool isDesignMode; 47 | 48 | /// 49 | /// Checks whether the application is currently in design mode. 50 | /// 51 | public static bool IsDesignMode 52 | { 53 | get { return isDesignMode; } 54 | } 55 | 56 | #endregion 57 | 58 | #region construction 59 | 60 | static Util() 61 | { 62 | isDesignMode = 63 | (bool) 64 | DependencyPropertyDescriptor.FromProperty(DesignerProperties.IsInDesignModeProperty, typeof (FrameworkElement)) 65 | .Metadata.DefaultValue; 66 | } 67 | 68 | #endregion 69 | 70 | #region CreateHelperWindow 71 | 72 | /// 73 | /// Creates an transparent window without dimension that 74 | /// can be used to temporarily obtain focus and/or 75 | /// be used as a window message sink. 76 | /// 77 | /// Empty window. 78 | public static Window CreateHelperWindow() 79 | { 80 | return new Window 81 | { 82 | Width = 0, 83 | Height = 0, 84 | ShowInTaskbar = false, 85 | WindowStyle = WindowStyle.None, 86 | AllowsTransparency = true, 87 | Opacity = 0 88 | }; 89 | } 90 | 91 | #endregion 92 | 93 | #region WriteIconData 94 | 95 | /// 96 | /// Updates the taskbar icons with data provided by a given 97 | /// instance. 98 | /// 99 | /// Configuration settings for the NotifyIcon. 100 | /// Operation on the icon (e.g. delete the icon). 101 | /// True if the data was successfully written. 102 | /// See Shell_NotifyIcon documentation on MSDN for details. 103 | public static bool WriteIconData(ref NotifyIconData data, NotifyCommand command) 104 | { 105 | return WriteIconData(ref data, command, data.ValidMembers); 106 | } 107 | 108 | 109 | /// 110 | /// Updates the taskbar icons with data provided by a given 111 | /// instance. 112 | /// 113 | /// Configuration settings for the NotifyIcon. 114 | /// Operation on the icon (e.g. delete the icon). 115 | /// Defines which members of the 116 | /// structure are set. 117 | /// True if the data was successfully written. 118 | /// See Shell_NotifyIcon documentation on MSDN for details. 119 | public static bool WriteIconData(ref NotifyIconData data, NotifyCommand command, IconDataMembers flags) 120 | { 121 | //do nothing if in design mode 122 | if (IsDesignMode) return true; 123 | 124 | data.ValidMembers = flags; 125 | lock (SyncRoot) 126 | { 127 | return WinApi.Shell_NotifyIcon(command, ref data); 128 | } 129 | } 130 | 131 | #endregion 132 | 133 | #region GetBalloonFlag 134 | 135 | /// 136 | /// Gets a enum value that 137 | /// matches a given . 138 | /// 139 | public static BalloonFlags GetBalloonFlag(this BalloonIcon icon) 140 | { 141 | switch (icon) 142 | { 143 | case BalloonIcon.None: 144 | return BalloonFlags.None; 145 | case BalloonIcon.Info: 146 | return BalloonFlags.Info; 147 | case BalloonIcon.Warning: 148 | return BalloonFlags.Warning; 149 | case BalloonIcon.Error: 150 | return BalloonFlags.Error; 151 | default: 152 | throw new ArgumentOutOfRangeException("icon"); 153 | } 154 | } 155 | 156 | #endregion 157 | 158 | #region ImageSource to Icon 159 | 160 | /// 161 | /// Reads a given image resource into a WinForms icon. 162 | /// 163 | /// Image source pointing to 164 | /// an icon file (*.ico). 165 | /// An icon object that can be used with the 166 | /// taskbar area. 167 | public static Icon ToIcon(this ImageSource imageSource) 168 | { 169 | if (imageSource == null) return null; 170 | 171 | Uri uri = new Uri(imageSource.ToString()); 172 | StreamResourceInfo streamInfo = Application.GetResourceStream(uri); 173 | 174 | if (streamInfo == null) 175 | { 176 | string msg = "The supplied image source '{0}' could not be resolved."; 177 | msg = String.Format(msg, imageSource); 178 | throw new ArgumentException(msg); 179 | } 180 | 181 | return new Icon(streamInfo.Stream); 182 | } 183 | 184 | #endregion 185 | 186 | #region evaluate listings 187 | 188 | /// 189 | /// Checks a list of candidates for equality to a given 190 | /// reference value. 191 | /// 192 | /// 193 | /// The evaluated value. 194 | /// A liste of possible values that are 195 | /// regarded valid. 196 | /// True if one of the submitted 197 | /// matches the evaluated value. If the 198 | /// parameter itself is null, too, the method returns false as well, 199 | /// which allows to check with null values, too. 200 | /// If 201 | /// is a null reference. 202 | public static bool Is(this T value, params T[] candidates) 203 | { 204 | if (candidates == null) return false; 205 | 206 | foreach (var t in candidates) 207 | { 208 | if (value.Equals(t)) return true; 209 | } 210 | 211 | return false; 212 | } 213 | 214 | #endregion 215 | 216 | #region match MouseEvent to PopupActivation 217 | 218 | /// 219 | /// Checks if a given is a match for 220 | /// an effectively pressed mouse button. 221 | /// 222 | public static bool IsMatch(this MouseEvent me, PopupActivationMode activationMode) 223 | { 224 | switch (activationMode) 225 | { 226 | case PopupActivationMode.LeftClick: 227 | return me == MouseEvent.IconLeftMouseUp; 228 | case PopupActivationMode.RightClick: 229 | return me == MouseEvent.IconRightMouseUp; 230 | case PopupActivationMode.LeftOrRightClick: 231 | return me.Is(MouseEvent.IconLeftMouseUp, MouseEvent.IconRightMouseUp); 232 | case PopupActivationMode.LeftOrDoubleClick: 233 | return me.Is(MouseEvent.IconLeftMouseUp, MouseEvent.IconDoubleClick); 234 | case PopupActivationMode.DoubleClick: 235 | return me.Is(MouseEvent.IconDoubleClick); 236 | case PopupActivationMode.MiddleClick: 237 | return me == MouseEvent.IconMiddleMouseUp; 238 | case PopupActivationMode.All: 239 | //return true for everything except mouse movements 240 | return me != MouseEvent.MouseMove; 241 | default: 242 | throw new ArgumentOutOfRangeException("activationMode"); 243 | } 244 | } 245 | 246 | #endregion 247 | 248 | #region execute command 249 | 250 | /// 251 | /// Executes a given command if its method 252 | /// indicates it can run. 253 | /// 254 | /// The command to be executed, or a null reference. 255 | /// An optional parameter that is associated with 256 | /// the command. 257 | /// The target element on which to raise the command. 258 | public static void ExecuteIfEnabled(this ICommand command, object commandParameter, IInputElement target) 259 | { 260 | if (command == null) return; 261 | 262 | RoutedCommand rc = command as RoutedCommand; 263 | if (rc != null) 264 | { 265 | //routed commands work on a target 266 | if (rc.CanExecute(commandParameter, target)) rc.Execute(commandParameter, target); 267 | } 268 | else if (command.CanExecute(commandParameter)) 269 | { 270 | command.Execute(commandParameter); 271 | } 272 | } 273 | 274 | #endregion 275 | 276 | /// 277 | /// Returns a dispatcher for multi-threaded scenarios 278 | /// 279 | /// 280 | internal static Dispatcher GetDispatcher(this DispatcherObject source) 281 | { 282 | //use the application's dispatcher by default 283 | if (Application.Current != null) return Application.Current.Dispatcher; 284 | 285 | //fallback for WinForms environments 286 | if (source.Dispatcher != null) return source.Dispatcher; 287 | 288 | //ultimatively use the thread's dispatcher 289 | return Dispatcher.CurrentDispatcher; 290 | } 291 | 292 | 293 | /// 294 | /// Checks whether the 295 | /// is bound or not. 296 | /// 297 | /// The element to be checked. 298 | /// True if the data context property is being managed by a 299 | /// binding expression. 300 | /// If 301 | /// is a null reference. 302 | public static bool IsDataContextDataBound(this FrameworkElement element) 303 | { 304 | if (element == null) throw new ArgumentNullException("element"); 305 | return element.GetBindingExpression(FrameworkElement.DataContextProperty) != null; 306 | } 307 | } 308 | } -------------------------------------------------------------------------------- /UserControls/ImageButton.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Windows; 6 | using System.Windows.Controls; 7 | using System.Windows.Controls.Primitives; 8 | using System.Windows.Media; 9 | 10 | namespace ServiceConfigurator 11 | { 12 | /// 13 | /// A button which can be assigned an image 14 | /// 15 | public class ImageButton : Button 16 | { 17 | public static readonly DependencyProperty ImageLocationProperty = DependencyProperty.Register("ImageLocation", typeof(PlacementMode), typeof(ImageButton), new UIPropertyMetadata(PlacementMode.Left)); 18 | /// 19 | /// Gets or sets the position of the image in relative to the text. 20 | /// 21 | /// The image. 22 | public PlacementMode ImageLocation 23 | { 24 | get { return (PlacementMode)base.GetValue(ImageLocationProperty); } 25 | set { base.SetValue(ImageLocationProperty, value); } 26 | } 27 | 28 | public static readonly DependencyProperty ImageProperty = DependencyProperty.Register("Image", typeof(ImageSource), typeof(ImageButton)); 29 | /// 30 | /// Gets or sets the image to display. 31 | /// 32 | /// The image. 33 | public ImageSource Image 34 | { 35 | get { return base.GetValue(ImageProperty) as ImageSource; } 36 | set { base.SetValue(ImageProperty, value); } 37 | } 38 | 39 | public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(ImageButton)); 40 | /// 41 | /// Gets or sets the text. 42 | /// 43 | /// The text. 44 | public string Text 45 | { 46 | get { return base.GetValue(TextProperty) as string; } 47 | set { base.SetValue(TextProperty, value); } 48 | } 49 | 50 | public static readonly DependencyProperty ShowTextProperty = DependencyProperty.Register("ShowText", typeof(bool), typeof(ImageButton), new UIPropertyMetadata(true)); 51 | /// 52 | /// Gets or sets whether to show the text. 53 | /// 54 | /// true if the text should be shown, otherwise false. 55 | public bool ShowText 56 | { 57 | get { return (bool)base.GetValue(ShowTextProperty); } 58 | set { base.SetValue(ShowTextProperty, value); } 59 | } 60 | 61 | public static readonly DependencyProperty AlwaysShowBorderProperty = DependencyProperty.Register("AlwaysShowBorder", typeof(bool), typeof(ImageButton)); 62 | /// 63 | /// Gets or sets whether to always show the border. 64 | /// 65 | /// true if the border should be shown, otherwise false. 66 | public bool AlwaysShowBorder 67 | { 68 | get { return (bool)base.GetValue(AlwaysShowBorderProperty); } 69 | set { base.SetValue(AlwaysShowBorderProperty, value); } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /UserControls/ServerController.xaml: -------------------------------------------------------------------------------- 1 |  9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 32 | 33 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /UserControls/ServerController.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | using System.ServiceProcess; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Windows; 9 | using System.Windows.Controls; 10 | using System.Windows.Data; 11 | using System.Windows.Documents; 12 | using System.Windows.Input; 13 | using System.Windows.Media; 14 | using System.Windows.Media.Imaging; 15 | using System.Windows.Navigation; 16 | using System.Windows.Shapes; 17 | using ServiceConfigurator.Properties; 18 | using ServiceConfigurator.ServiceUtils; 19 | using Xceed.Wpf.Toolkit; 20 | using MessageBox = System.Windows.MessageBox; 21 | using ServiceStartMode = System.ServiceProcess.ServiceStartMode; 22 | 23 | namespace ServiceConfigurator 24 | { 25 | /// 26 | /// Shows the start/stop/install/uninstall buttons for a single server 27 | /// 28 | public partial class ServerController : UserControl 29 | { 30 | public static readonly DependencyProperty RefreshCommandProperty = 31 | DependencyProperty.Register("RefreshCommand", typeof (ICommand), typeof (ServerController), new PropertyMetadata(default(ICommand))); 32 | 33 | public ICommand RefreshCommand { 34 | get { return (ICommand) GetValue(RefreshCommandProperty); } 35 | set { SetValue(RefreshCommandProperty, value); } 36 | } 37 | 38 | public static readonly DependencyProperty ServerProperty = 39 | DependencyProperty.Register("Server", typeof(Server), typeof(ServerController), 40 | new PropertyMetadata(new Server(), 41 | new PropertyChangedCallback(ServerChanged))); 42 | 43 | private static void ServerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { 44 | var controller = d as ServerController; 45 | var server = e.NewValue as Server; 46 | if(e.NewValue!=e.OldValue&& server!=null) { 47 | controller._serviceName = server.ServiceName; 48 | controller.Controller = new ServiceController(server.ServiceName, server.MachineName); 49 | controller.RefreshStatus(); 50 | } 51 | } 52 | 53 | public Server Server 54 | { 55 | get { return (Server)GetValue(ServerProperty); } 56 | set { SetValue(ServerProperty, value); } 57 | } 58 | private ServiceController Controller { get; set; } 59 | private string _serviceName { get; set; } 60 | 61 | public ServerController() 62 | { 63 | InitializeComponent(); 64 | } 65 | 66 | public ServerController(string serviceName, Server server) 67 | :this() { 68 | this._serviceName = serviceName; 69 | this.Server = server; 70 | this.Controller = new ServiceController(serviceName, server.MachineName); 71 | } 72 | 73 | #region Starting/Stopping 74 | private void btnStartStop_Click(object sender, RoutedEventArgs e) { 75 | ChangeStatus(sender); 76 | } 77 | 78 | /// 79 | /// Changes the status of a controller based on the CommandParameter of the sender (A button), then updates the form controls 80 | /// 81 | /// 82 | private void ChangeStatus(object sender) 83 | { 84 | var btn = sender as Button; 85 | bool shouldStartService = string.Equals(Convert.ToString(btn.CommandParameter), "1", StringComparison.InvariantCultureIgnoreCase); 86 | BackgroundWorker worker = new BackgroundWorker(); 87 | worker.DoWork += (s, dwe) => 88 | { 89 | if (shouldStartService) { 90 | Controller.Start(); 91 | } else { 92 | Controller.Stop(); 93 | } 94 | Thread.Sleep(5000); 95 | }; 96 | worker.RunWorkerCompleted += (s, rwe) => 97 | { 98 | busyIndicator.IsBusy = false; 99 | if (rwe.Error != null) 100 | { 101 | MessageBox.Show(rwe.Error.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error, MessageBoxResult.OK); 102 | } 103 | RefreshStatus(); 104 | }; 105 | busyIndicator.IsBusy = true; 106 | worker.RunWorkerAsync(); 107 | } 108 | #endregion 109 | 110 | private void btnInstall_Click(object sender, RoutedEventArgs e) 111 | { 112 | //Settings.Default.ExePathProduction = txtPath.Text; 113 | Settings.Default.Save(); 114 | var btn = sender as Button; 115 | bool shouldInstall = string.Equals(Convert.ToString(btn.CommandParameter), "1", StringComparison.InvariantCultureIgnoreCase); 116 | InstallService(shouldInstall); 117 | } 118 | 119 | private void InstallService(bool shouldInstall) { 120 | string exePath = txtPath.Text; 121 | if (string.IsNullOrEmpty(exePath)) { 122 | MessageBox.Show("Path to executable is required", "Error", MessageBoxButton.OK, MessageBoxImage.Error, MessageBoxResult.OK); 123 | return; 124 | } 125 | ServiceReturnCode installStatus = ServiceReturnCode.NotSupported; 126 | BackgroundWorker worker = new BackgroundWorker(); 127 | worker.DoWork += (s, dwe) => 128 | { 129 | if (shouldInstall) { 130 | installStatus = WmiService.Instance.Install(Controller.MachineName, _serviceName, _serviceName, exePath, ServiceUtils.ServiceStartMode.Automatic, "LocalSystem", null, null); 131 | } else { 132 | installStatus = WmiService.Instance.Uninstall(Controller.MachineName, _serviceName); 133 | } 134 | Thread.Sleep(5000); 135 | }; 136 | worker.RunWorkerCompleted += (s, rwe) => 137 | { 138 | busyIndicator.IsBusy = false; 139 | if (rwe.Error != null) { 140 | MessageBox.Show(rwe.Error.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error, MessageBoxResult.OK); 141 | } else if (installStatus != ServiceReturnCode.Success) { 142 | MessageBox.Show(installStatus.ToString(), "Error", MessageBoxButton.OK, MessageBoxImage.Error, MessageBoxResult.OK); 143 | } 144 | RefreshStatus(); 145 | }; 146 | busyIndicator.IsBusy = true; 147 | worker.RunWorkerAsync(); 148 | } 149 | 150 | /// 151 | /// Updates the status of a single service 152 | /// 153 | private void RefreshStatus() 154 | { 155 | bool isInstalled = false; 156 | ServiceControllerStatus status = ServiceControllerStatus.Stopped; 157 | BackgroundWorker worker = new BackgroundWorker(); 158 | worker.DoWork += (s, dwe) => 159 | { 160 | isInstalled = ServiceController.GetServices(Controller.MachineName).Any(svc => svc.ServiceName == _serviceName); 161 | if (isInstalled) 162 | { 163 | Controller.Refresh(); 164 | status = Controller.Status; 165 | } 166 | }; 167 | worker.RunWorkerCompleted += (s, rwe) => 168 | { 169 | if (isInstalled) 170 | { 171 | switch (status) 172 | { 173 | case ServiceControllerStatus.Running: 174 | lblStatus.Foreground = Brushes.LimeGreen; 175 | lblStatus.Content = "Running"; 176 | btnStop.IsEnabled = true; 177 | btnStart.IsEnabled = false; 178 | break; 179 | default: 180 | lblStatus.Foreground = Brushes.Red; 181 | lblStatus.Content = "Stopped"; 182 | btnStop.IsEnabled = false; 183 | btnStart.IsEnabled = true; 184 | break; 185 | } 186 | btnInstall.IsEnabled = false; 187 | btnUninstall.IsEnabled = true; 188 | } 189 | else 190 | { 191 | lblStatus.Foreground = Brushes.Red; 192 | lblStatus.Content = "Not installed"; 193 | btnStart.IsEnabled = false; 194 | btnStop.IsEnabled = false; 195 | btnInstall.IsEnabled = true; 196 | btnUninstall.IsEnabled = false; 197 | } 198 | }; 199 | worker.RunWorkerAsync(); 200 | } 201 | 202 | private void UpdateStatus(object sender, ExecutedRoutedEventArgs e) { 203 | RefreshStatus(); 204 | } 205 | 206 | private void CanAlwaysExecute(object sender, CanExecuteRoutedEventArgs e) { 207 | e.CanExecute = true; 208 | } 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /Utils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Xml.Linq; 8 | 9 | namespace ServiceConfigurator 10 | { 11 | public class Utils 12 | { 13 | /// 14 | /// Parses the configuration file from the application directory. 15 | /// 16 | /// 17 | public static IEnumerable ParseConfig() 18 | { 19 | List services = new List(); 20 | var configPath = Path.Combine(Utils.GetApplicationPath(), "config.xml"); 21 | var configFile = XDocument.Load(configPath, LoadOptions.None); 22 | var config = configFile.Root; 23 | var eleServices = config.Element("services").Elements("service"); 24 | foreach (var eleService in eleServices) { 25 | var service = new Service(eleService); 26 | services.Add(service); 27 | } 28 | return services; 29 | } 30 | 31 | /// 32 | /// Saves a configuration to the app directory. 33 | /// 34 | /// The services. 35 | public static void SaveConfig(IEnumerable services) 36 | { 37 | var configPath = Path.Combine(Utils.GetApplicationPath(), "config.xml"); 38 | var doc = new XDocument(new XDeclaration("1.0", "utf-8", "no"), 39 | new XElement("config", 40 | new XElement("services", services.Select(s => s.ToXml())))); 41 | doc.Save(configPath, SaveOptions.None); 42 | } 43 | 44 | /// 45 | /// Gets the path of the directory in which the application is located. 46 | /// 47 | /// The path of the directory in which the application is located. 48 | public static string GetApplicationPath() 49 | { 50 | string fqPath = System.Reflection.Assembly.GetExecutingAssembly().GetModules()[0].FullyQualifiedName; 51 | return Path.GetDirectoryName(fqPath); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /_ReSharper.ServiceConfigurator/AspFileDataCache.dat: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /_ReSharper.ServiceConfigurator/RecentItems/RecentFiles.dat: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /config.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Echilon/ServiceConfigurator/f2836422d46e5be076ffed1c3d7c43064a28bfe5/icon.ico -------------------------------------------------------------------------------- /lib/WPFToolkit.Extended.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Echilon/ServiceConfigurator/f2836422d46e5be076ffed1c3d7c43064a28bfe5/lib/WPFToolkit.Extended.dll --------------------------------------------------------------------------------