├── .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 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
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
--------------------------------------------------------------------------------