Click here to Skip to main content
Click here to Skip to main content

Create a system tray icon and a dialog for a Windows Service

, 25 Jan 2008
Rate this:
Please Sign up or sign in to vote.
This article shows how to create a system tray icon and a dialog for a Windows Service.

shot1.JPG

Introduction

Generally speaking, the easiest way to implement a system tray icon with a context menu for a Windows Service is to implement a separate Shell program. But here, I am creating a Windows Service which will have its own system tray icon and dialog.

Background

A Windows Service is started before the Windows logon, so the first challenge is how to detect the Shell is ready in the Windows Service. The second challenge is how to interact with the Windows service. I solve the first issue with the System Event Notification Service (SENS). For the second, I associate the thread to the default desktop by using P/Invoke.

Steps

Step 1: Open VS2005, create a Windows Service project, and add an installer for the Windows Service.

[RunInstaller(true)]
public class SvcInstaller : Installer
{
    private static readonly string SVC_NAME = "SystemTrayIconInSvc";
    private static readonly string SVC_DESC = "This is a test";

    public SvcInstaller()
    {
        Installers.Clear();

        ServiceInstaller serviceInstaller = new ServiceInstaller();
        serviceInstaller.StartType = ServiceStartMode.Automatic;
        serviceInstaller.ServiceName = SVC_NAME;
        serviceInstaller.DisplayName = SVC_NAME;
        serviceInstaller.Description = SVC_DESC;
        serviceInstaller.ServicesDependedOn = new string[] { "SENS", "COMSysApp" };

        Installers.Add(serviceInstaller);

        ServiceProcessInstaller processInstaller = new ServiceProcessInstaller();
        processInstaller.Account = ServiceAccount.LocalSystem;
        processInstaller.Password = null;
        processInstaller.Username = null;

        Installers.Add(processInstaller);
    }


    protected override void OnAfterInstall(IDictionary savedState)
    {
        ServiceController controller = null;
        ServiceController[] controllers = ServiceController.GetServices();
        for (int i = 0; i < controllers.Length; i++)
        {
            if (controllers[i].ServiceName == SVC_NAME)
            {
                controller = controllers[i];
                break;
            }
        }
        if (controller == null)
        {
            return;
        }

        // if the service is not active, start it
        if (controller.Status != ServiceControllerStatus.Running)
        {
            string[] args = { "-install" };
            controller.Start(args);
        }
    }
}

Note: The Windows service should depend on the "SENS" and "COMSysApp" because we need to make sure the Windows service is launched after the dependency is ready.

Step 2: SENS subscription. Add a reference to "COM+ 1.0 Admin Type Library" to the project. Add a reference to "SENS Events Type Library" to the project. Add the SensAdvisor class which is used to do the subscription against the SENS interface. I wrote the SensAdvisor class based on the MSDN article, "Accessing System Power and Network Status Using SENS". Here, we are interested in the ISensLogon2 interface (you can subscribe to the ISensLogon as well).

/// <span class="code-SummaryComment"><summary></span>
/// ISensLogon2 Event Args
/// <span class="code-SummaryComment"></summary></span>
public class SensLogon2EventArgs : EventArgs
{
    public string Username;
    public uint SessionId;
}

/// <span class="code-SummaryComment"><summary></span>
/// subscribe SENS notification
/// Ref MSDN:Accessing System Power and Network Status Using SENS
/// <span class="code-SummaryComment"></summary></span>
public sealed class SensAdvisor : ISensLogon2
{
    public const string ISensLogon2_ID = "{d5978650-5b9f-11d1-8dd2-00aa004abd5e}";

    public SensAdvisor()
    {
        COMAdminCatalogClass comAdmin = new COMAdminCatalogClass();
        ICatalogCollection subCollection = 
          (ICatalogCollection)comAdmin.GetCollection("TransientSubscriptions");

        SubscribeToEvent(subCollection, "PostShell", ISensLogon2_ID);
        SubscribeToEvent(subCollection, "Logon", ISensLogon2_ID);
        SubscribeToEvent(subCollection, "Logoff", ISensLogon2_ID);
        SubscribeToEvent(subCollection, "SessionReconnect", ISensLogon2_ID);
        SubscribeToEvent(subCollection, "SessionDisconnect", ISensLogon2_ID);
    }
        
    private void SubscribeToEvent(ICatalogCollection subCollection, 
                                  string methodName, string guidString)
    {
       ICatalogObject catalogObject = (ICatalogObject)subCollection.Add();

       // Specify the parameters of the desired subscription.
       catalogObject.set_Value("EventCLSID", guidString);
       catalogObject.set_Value("Name", "Subscription to " + 
                               methodName + " event");
       catalogObject.set_Value("MethodName", methodName);
       catalogObject.set_Value("SubscriberInterface", this);
       catalogObject.set_Value("Enabled", true);
       // This setting allows subscriptions to work for non-Administrator users.
       catalogObject.set_Value("PerUser", true);  

       // Save the changes made to the transient subscription collection.
       subCollection.SaveChanges();
    }


    public delegate void PostShellEventHandler(object sender, SensLogon2EventArgs e);
    public delegate void SessionReconnectEventHandler(object sender, SensLogon2EventArgs e);
    public delegate void SessionDisconnectEventHandler(object sender, SensLogon2EventArgs e);
    public delegate void LogonEventHandler(object sender, SensLogon2EventArgs e);
    public delegate void LogoffEventHandler(object sender, SensLogon2EventArgs e);

    public event PostShellEventHandler OnShellStarted;
    public event SessionReconnectEventHandler OnSessionReconnected;
    public event SessionDisconnectEventHandler OnSessionDisconnected;
    public event LogonEventHandler OnLogon;
    public event LogoffEventHandler OnLogoff;


    public void PostShell(string bstrUserName, uint dwSessionId)
    {
        if (OnShellStarted != null)
        {
            SensLogon2EventArgs args = new SensLogon2EventArgs();
            args.Username = bstrUserName;
            args.SessionId = dwSessionId;
            OnShellStarted(this, args);
        }
    }

    public void SessionReconnect(string bstrUserName, uint dwSessionId)
    {
        if (OnSessionReconnected != null)
        {
            SensLogon2EventArgs args = new SensLogon2EventArgs();
            args.Username = bstrUserName;
            args.SessionId = dwSessionId;
            OnSessionReconnected(this, args);
        }
    }

    public void SessionDisconnect(string bstrUserName, uint dwSessionId)
    {
        if (OnSessionDisconnected != null)
        {
            SensLogon2EventArgs args = new SensLogon2EventArgs();
            args.Username = bstrUserName;
            args.SessionId = dwSessionId;
            OnSessionDisconnected(this, args);
        }
    }

    public void Logoff(string bstrUserName, uint dwSessionId)
    {
        if (OnLogoff != null)
        {
            SensLogon2EventArgs args = new SensLogon2EventArgs();
            args.Username = bstrUserName;
            args.SessionId = dwSessionId;
            OnLogoff(this, args);
        }
    }

    public void Logon(string bstrUserName, uint dwSessionId)
    {
        if (OnLogon != null)
        {
            SensLogon2EventArgs args = new SensLogon2EventArgs();
            args.Username = bstrUserName;
            args.SessionId = dwSessionId;
            OnLogon(this, args);
        }
    }  
}

Step 3: Add a wrapper class for the required APIs in the User32.dll.

/// <span class="code-SummaryComment"><summary></span>
/// The wrapper class for User32.dll
/// <span class="code-SummaryComment"></summary></span>
public static class User32DLL
{
    /// <span class="code-SummaryComment"><summary></span>
    /// The GetDesktopWindow function returns a handle to the desktop window. 
    /// The desktop window covers the entire screen. 
    /// The desktop window is the area on top of which other windows are painted. 
    /// <span class="code-SummaryComment"></summary></span>
    /// <span class="code-SummaryComment"><returns>The return value is a handle to the desktop window. </returns></span>
    [DllImport("User32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern IntPtr GetDesktopWindow();


    /// <span class="code-SummaryComment"><summary></span>
    /// Retrieves a handle to the current window station for the calling process.
    /// <span class="code-SummaryComment"></summary></span>
    /// <span class="code-SummaryComment"><returns>If the function succeeds,</span>
    /// the return value is a handle to the window station.
    /// If the function fails, the return value is NULL.<span class="code-SummaryComment"></returns></span>
    [DllImport("User32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern IntPtr GetProcessWindowStation();


    /// <span class="code-SummaryComment"><summary></span>
    /// Retrieves a handle to the desktop assigned to the specified thread.
    /// <span class="code-SummaryComment"></summary></span>
    /// <span class="code-SummaryComment"><param name="dwThread">[in] Handle to the thread</span>
    ///     for which to return the desktop handle.<span class="code-SummaryComment"></param></span>
    /// <span class="code-SummaryComment"><returns>If the function succeeds, the return value is a handle to the </span>
    /// desktop associated with the specified thread. 
    /// If the function fails, the return value is NULL.<span class="code-SummaryComment"></returns></span>
    [DllImport("User32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern IntPtr GetThreadDesktop(uint dwThread);


    /// <span class="code-SummaryComment"><summary></span>
    /// Opens the specified window station.
    /// <span class="code-SummaryComment"></summary></span>
    /// <span class="code-SummaryComment"><param name="lpszWinSta">Pointer to a null-terminated</span>
    ///           string specifying the name of the window station 
    /// to be opened. Window station names are case-insensitive.
    /// This window station must belong to the current session.
    /// <span class="code-SummaryComment"></param></span>
    /// <span class="code-SummaryComment"><param name="fInherit">[in] If this value</span>
    ///            is TRUE, processes created by this process 
    /// will inherit the handle. Otherwise, 
    /// the processes do not inherit this handle. 
    /// <span class="code-SummaryComment"></param></span>
    /// <span class="code-SummaryComment"><param name="dwDesiredAccess">[in] Access to the window station</param></span>
    /// <span class="code-SummaryComment"><returns>If the function succeeds, the return value</span>
    ///          is the handle to the specified window station.
    /// If the function fails, the return value is NULL.<span class="code-SummaryComment"></returns></span>
    [DllImport("User32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern IntPtr OpenWindowStation(string lpszWinSta
        , bool fInherit
        , WindowStationAccessRight dwDesiredAccess
        );


    /// <span class="code-SummaryComment"><summary></span>
    /// Assigns the specified window station to the calling process. 
    /// This enables the process to access objects in the window
    /// station such as desktops, the clipboard, and global atoms. 
    /// All subsequent operations on the window station
    /// use the access rights granted to hWinSta.
    /// <span class="code-SummaryComment"></summary></span>
    /// <span class="code-SummaryComment"><param name="hWinSta">[in] Handle to the window</span>
    ///         station to be assigned to the calling process<span class="code-SummaryComment"></param></span>
    /// <span class="code-SummaryComment"><returns>If the function succeeds, the return value is nonzero.</span>
    /// If the function fails, the return value is zero. <span class="code-SummaryComment"></returns></span>
    [DllImport("User32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern IntPtr SetProcessWindowStation(IntPtr hWinSta);


    /// <span class="code-SummaryComment"><summary></span>
    /// Closes an open window station handle.
    /// <span class="code-SummaryComment"></summary></span>
    /// <span class="code-SummaryComment"><param name="hWinSta">[in] Handle</span>
    ///         to the window station to be closed.<span class="code-SummaryComment"></param></span>
    /// <span class="code-SummaryComment"><returns>If the function succeeds, the return value is nonzero.</span>
    /// If the function fails, the return value is zero. <span class="code-SummaryComment"></returns></span>
    [DllImport("User32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern IntPtr CloseWindowStation(IntPtr hWinSta);


    /// <span class="code-SummaryComment"><summary></span>
    /// Opens the specified desktop object.
    /// <span class="code-SummaryComment"></summary></span>
    /// <span class="code-SummaryComment"><param name="lpszDesktop">[in] Pointer to null-terminated string </span>
    /// specifying the name of the desktop to be opened. 
    /// Desktop names are case-insensitive.
    /// This desktop must belong to the current window station.<span class="code-SummaryComment"></param></span>
    /// <span class="code-SummaryComment"><param name="dwFlags">[in] This parameter can</span>
    ///          be zero or DF_ALLOWOTHERACCOUNTHOOK=0x0001<span class="code-SummaryComment"></param></span>
    /// <span class="code-SummaryComment"><param name="fInherit">[in] If this value is TRUE, processes created by </span>
    /// this process will inherit the handle. 
    /// Otherwise, the processes do not inherit this handle. <span class="code-SummaryComment"></param></span>
    /// <span class="code-SummaryComment"><param name="dwDesiredAccess">[in] Access</span>
    ///         to the desktop. For a list of access rights<span class="code-SummaryComment"></param></span>
    /// <span class="code-SummaryComment"><returns>If the function succeeds, the return value is a handle to the opened desktop. </span>
    /// When you are finished using the handle, call the CloseDesktop function to close it.
    /// If the function fails, the return value is NULL.
    /// <span class="code-SummaryComment"></returns></span>
    [DllImport("User32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern IntPtr OpenDesktop(string lpszDesktop
        , OpenDesktopFlag dwFlags
        , bool fInherit
        , DesktopAccessRight dwDesiredAccess
        );


    /// <span class="code-SummaryComment"><summary></span>
    /// Closes an open handle to a desktop object.
    /// <span class="code-SummaryComment"></summary></span>
    /// <span class="code-SummaryComment"><param name="hDesktop">[in] Handle to the desktop to be closed.</param></span>
    /// <span class="code-SummaryComment"><returns>If the function succeeds, the return value is nonzero.</span>
    /// If the function fails, the return value is zero. <span class="code-SummaryComment"></returns></span>
    [DllImport("User32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern IntPtr CloseDesktop(IntPtr hDesktop);

    /// <span class="code-SummaryComment"><summary></span>
    /// Assigns the specified desktop to the calling thread. 
    /// All subsequent operations on the desktop use the access rights granted to the desktop.
    /// <span class="code-SummaryComment"></summary></span>
    /// <span class="code-SummaryComment"><param name="hDesktop">[in] Handle to the desktop</span>
    ///           to be assigned to the calling thread.<span class="code-SummaryComment"></param></span>
    /// <span class="code-SummaryComment"><returns>If the function succeeds, the return value is nonzero.</span>
    /// If the function fails, the return value is zero. <span class="code-SummaryComment"></returns></span>
    [DllImport("User32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern bool SetThreadDesktop(IntPtr hDesktop);
}

/// <span class="code-SummaryComment"><summary></span>
/// REF MSDN:Window Station Security and Access Rights
/// ms-help://MS.MSDN.vAug06.en/dllproc/base/window_station_security_and_access_rights.htm
/// <span class="code-SummaryComment"></summary></span>
[FlagsAttribute]
public enum WindowStationAccessRight : uint
{
    /// <span class="code-SummaryComment"><summary>All possible access rights for the window station.</summary></span>
    WINSTA_ALL_ACCESS = 0x37F,

    /// <span class="code-SummaryComment"><summary>Required to use the clipboard.</summary></span>
    WINSTA_ACCESSCLIPBOARD = 0x0004,

    /// <span class="code-SummaryComment"><summary>Required to manipulate global atoms.</summary></span>
    WINSTA_ACCESSGLOBALATOMS = 0x0020,

    /// <span class="code-SummaryComment"><summary>Required to create new desktop</span>
    /// objects on the window station.<span class="code-SummaryComment"></summary></span>
    WINSTA_CREATEDESKTOP = 0x0008,

    /// <span class="code-SummaryComment"><summary>Required to enumerate existing desktop objects.</summary></span>
    WINSTA_ENUMDESKTOPS = 0x0001,

    /// <span class="code-SummaryComment"><summary>Required for the window station to be enumerated.</summary></span>
    WINSTA_ENUMERATE = 0x0100,

    /// <span class="code-SummaryComment"><summary>Required to successfully call the ExitWindows or ExitWindowsEx function. </span>
    /// Window stations can be shared by users and this access type can prevent other users 
    /// of a window station from logging off the window station owner.<span class="code-SummaryComment"></summary></span>
    WINSTA_EXITWINDOWS = 0x0040,

    /// <span class="code-SummaryComment"><summary>Required to read the attributes of a window station object. </span>
    /// This attribute includes color settings 
    /// and other global window station properties.<span class="code-SummaryComment"></summary></span>
    WINSTA_READATTRIBUTES = 0x0002,

    /// <span class="code-SummaryComment"><summary>Required to access screen contents.</summary></span>
    WINSTA_READSCREEN = 0x0200,

    /// <span class="code-SummaryComment"><summary>Required to modify the attributes of </span>
    /// a window station object. 
    /// The attributes include color settings 
    /// and other global window station properties.<span class="code-SummaryComment"></summary></span>
    WINSTA_WRITEATTRIBUTES = 0x0010,
}

/// <span class="code-SummaryComment"><summary></span>
/// OpenDesktop 2nd param
/// <span class="code-SummaryComment"></summary></span>
public enum OpenDesktopFlag : uint
{
    /// <span class="code-SummaryComment"><summary></span>
    /// Default value
    /// <span class="code-SummaryComment"></summary></span>
    DF_NONE = 0x0000,

    /// <span class="code-SummaryComment"><summary></span>
    /// Allows processes running in other accounts on the desktop
    /// to set hooks in this process.
    /// <span class="code-SummaryComment"></summary></span>
    DF_ALLOWOTHERACCOUNTHOOK = 0x0001,
}

/// <span class="code-SummaryComment"><summary></span>
/// REF MSDN:Desktop Security and Access Rights
/// ms-help://MS.MSDN.vAug06.en/dllproc/base/desktop_security_and_access_rights.htm
/// <span class="code-SummaryComment"></summary></span>
[FlagsAttribute]
public enum DesktopAccessRight : uint
{
    /// <span class="code-SummaryComment"><summary>Required to create a menu on the desktop. </summary></span>
    DESKTOP_CREATEMENU = 0x0004,

    /// <span class="code-SummaryComment"><summary>Required to create a window on the desktop. </summary></span>
    DESKTOP_CREATEWINDOW = 0x0002,

    /// <span class="code-SummaryComment"><summary>Required for the desktop to be enumerated. </summary></span>
    DESKTOP_ENUMERATE = 0x0040,

    /// <span class="code-SummaryComment"><summary>Required to establish any of the window hooks. </summary></span>
    DESKTOP_HOOKCONTROL = 0x0008,

    /// <span class="code-SummaryComment"><summary>Required to perform journal playback on a desktop. </summary></span>
    DESKTOP_JOURNALPLAYBACK = 0x0020,

    /// <span class="code-SummaryComment"><summary>Required to perform journal recording on a desktop. </summary></span>
    DESKTOP_JOURNALRECORD = 0x0010,

    /// <span class="code-SummaryComment"><summary>Required to read objects on the desktop. </summary></span>
    DESKTOP_READOBJECTS = 0x0001,

    /// <span class="code-SummaryComment"><summary>Required to activate the desktop</span>
    ///          using the SwitchDesktop function. <span class="code-SummaryComment"></summary></span>
    DESKTOP_SWITCHDESKTOP = 0x0100,

    /// <span class="code-SummaryComment"><summary>Required to write objects on the desktop. </summary></span>
    DESKTOP_WRITEOBJECTS = 0x0080,
}

Step 4: Add the Desktop class, which will help us to set the thread station.

internal class Desktop
{
    private IntPtr m_hCurWinsta = IntPtr.Zero;
    private IntPtr m_hCurDesktop = IntPtr.Zero;
    private IntPtr m_hWinsta = IntPtr.Zero;
    private IntPtr m_hDesk = IntPtr.Zero;

    /// <span class="code-SummaryComment"><summary></span>
    /// associate the current thread to the default desktop
    /// <span class="code-SummaryComment"></summary></span>
    /// <span class="code-SummaryComment"><returns></returns></span>
    internal bool BeginInteraction()
    {
        EndInteraction();
        m_hCurWinsta = User32DLL.GetProcessWindowStation();
        if (m_hCurWinsta == IntPtr.Zero)
            return false;

        m_hCurDesktop = User32DLL.GetDesktopWindow();
        if (m_hCurDesktop == IntPtr.Zero)
            return false;

        m_hWinsta = User32DLL.OpenWindowStation("winsta0", false,
            WindowStationAccessRight.WINSTA_ACCESSCLIPBOARD |
            WindowStationAccessRight.WINSTA_ACCESSGLOBALATOMS |
            WindowStationAccessRight.WINSTA_CREATEDESKTOP |
            WindowStationAccessRight.WINSTA_ENUMDESKTOPS |
            WindowStationAccessRight.WINSTA_ENUMERATE |
            WindowStationAccessRight.WINSTA_EXITWINDOWS |
            WindowStationAccessRight.WINSTA_READATTRIBUTES |
            WindowStationAccessRight.WINSTA_READSCREEN |
            WindowStationAccessRight.WINSTA_WRITEATTRIBUTES
            );
        if (m_hWinsta == IntPtr.Zero)
            return false;

        User32DLL.SetProcessWindowStation(m_hWinsta);

        m_hDesk = User32DLL.OpenDesktop("default", OpenDesktopFlag.DF_NONE, false,
                DesktopAccessRight.DESKTOP_CREATEMENU |
                DesktopAccessRight.DESKTOP_CREATEWINDOW |
                DesktopAccessRight.DESKTOP_ENUMERATE |
                DesktopAccessRight.DESKTOP_HOOKCONTROL |
                DesktopAccessRight.DESKTOP_JOURNALPLAYBACK |
                DesktopAccessRight.DESKTOP_JOURNALRECORD |
                DesktopAccessRight.DESKTOP_READOBJECTS |
                DesktopAccessRight.DESKTOP_SWITCHDESKTOP |
                DesktopAccessRight.DESKTOP_WRITEOBJECTS
                );
        if (m_hDesk == IntPtr.Zero)
            return false;

        User32DLL.SetThreadDesktop(m_hDesk);

        return true;
    }

    /// <span class="code-SummaryComment"><summary></span>
    /// restore
    /// <span class="code-SummaryComment"></summary></span>
    internal void EndInteraction()
    {
        if (m_hCurWinsta != IntPtr.Zero)
            User32DLL.SetProcessWindowStation(m_hCurWinsta);

        if (m_hCurDesktop != IntPtr.Zero)
            User32DLL.SetThreadDesktop(m_hCurDesktop);

        if (m_hWinsta != IntPtr.Zero)
            User32DLL.CloseWindowStation(m_hWinsta);

        if (m_hDesk != IntPtr.Zero)
            User32DLL.CloseDesktop(m_hDesk);
    }
}

Step 5: Add the Form and the system tray icon class. Note that the UI is running in another thread.

public partial class SettingDlg : Form
{
    private Desktop m_Desktop = new Desktop();
    private IContainer m_Container = null;
    private NotifyIcon m_NotifyIcon = null;
    private Button btnHide;
    private ContextMenu m_ContextMenu = null;

    /// <span class="code-SummaryComment"><summary></span>
    /// Start the UI thread
    /// <span class="code-SummaryComment"></summary></span>
    public static SettingDlg StartUIThread()
    {
        SettingDlg dlg = new SettingDlg();

        Thread thread = new Thread(new ThreadStart(dlg.UIThread));
        thread.Start();

        return dlg;
    }

    /// <span class="code-SummaryComment"><summary></span>
    /// UI thread
    /// <span class="code-SummaryComment"></summary></span>
    public void UIThread()
    {
        if( !m_Desktop.BeginInteraction() )
                return;
        Application.Run(this);
    }

    protected SettingDlg()
    {
        InitializeComponent();
    }

    /// <span class="code-SummaryComment"><summary></span>
    /// Move the window to the right-bottom corner
    /// <span class="code-SummaryComment"></summary></span>
    /// <span class="code-SummaryComment"><param name="e"></param></span>
    protected override void OnShown(EventArgs e)
    {
        this.Left = Screen.PrimaryScreen.WorkingArea.Left
            + Screen.PrimaryScreen.WorkingArea.Width
            - this.Width
            ;
        this.Top = Screen.PrimaryScreen.WorkingArea.Top
            + Screen.PrimaryScreen.WorkingArea.Height
            - this.Height
            ;
    }


    private void SettingDlg_Load(object sender, EventArgs e)
    {
        m_ContextMenu = new ContextMenu();
        m_ContextMenu.MenuItems.Add(new MenuItem("Open Dialog", this.OpenDialog));
        
        Icon icon = new Icon(SystemIcons.Application, 16, 16);
        m_Container = new Container();
        m_NotifyIcon = new NotifyIcon(m_Container);
        m_NotifyIcon.ContextMenu = m_ContextMenu;
        m_NotifyIcon.Icon = icon;
        m_NotifyIcon.Visible = true;

        m_NotifyIcon.ShowBalloonTip( 200
            , "SystemTrayIconInSvc"
            , "The system tray icon is implemented in the windows service itself."
            , ToolTipIcon.Info
            );
    }



    public void OpenDialog(Object sender, EventArgs e)
    {
        this.Visible = true;
        BringToFront();
    }

    protected override void OnClosed(EventArgs e)
    {
        m_NotifyIcon.Dispose();
        m_ContextMenu.Dispose();
        m_Container.Dispose();
    }

    private void btnHide_Click(object sender, EventArgs e)
    {
        this.Visible = false;
    }
}

Step 6: Integrate all of the above together.

public partial class Svc : ServiceBase
{
    private SensAdvisor m_Advisor = new SensAdvisor();
    private List<SettingDlg> m_Dlgs = new List<SettingDlg>();

    public Svc()
    {
        InitializeComponent();

        m_Advisor.OnShellStarted += this.PostShell;
    }

    internal void DebugStart()
    {
        OnStart(null);
    }

    protected override void OnStart(string[] args)
    {
        // if the service started after the windows logon
        m_Dlgs.Add(SettingDlg.StartUIThread());
    }

    protected override void OnStop()
    {
        foreach (SettingDlg dlg in m_Dlgs)
        {
            try
            {
                dlg.Close();
                dlg.Dispose();
            }
            catch { }
        }

        m_Dlgs.Clear();
    }

    /// <span class="code-SummaryComment"><summary></span>
    /// Called when the shell is ready
    /// <span class="code-SummaryComment"></summary></span>
    /// <span class="code-SummaryComment"><param name="sender"></param></span>
    /// <span class="code-SummaryComment"><param name="e"></param></span>
    public void PostShell(object sender, SensLogon2EventArgs e)
    {
        m_Dlgs.Add(SettingDlg.StartUIThread());
    }
}

Points

I haven't tested this from a remote desktop client because the sample just associates the UI to the default desktop, so it may not work from a remote desktop. If so, you can add a "/console" parameter when you connect to the remote desktop, and that will make the remote client use the default desktop. Note, the "/console" parameter is only supported under Windows 5.2 and above.

rr.JPG

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Jerry.Wang
Team Leader
China China
Jerry is from China. He was captivated by computer programming since 13 years old when first time played with Q-Basic.
 

  • Windows / Linux & C++
  • iOS & Obj-C
  • .Net & C#
  • Flex/Flash & ActionScript
  • HTML / CSS / Javascript
  • Gaming Server programming / video, audio processing / image & graphics
 
Contact: vcer(at)qq.com
Chinese Blog: http://blog.csdn.net/wangjia184

Comments and Discussions

 
GeneralThis may not work on Vista PinmemberLeo Davidson25-Jan-08 3:26 
GeneralRe: This may not work on Vista Pinmembersanjibm_0228-Mar-11 21:59 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140827.1 | Last Updated 25 Jan 2008
Article Copyright 2008 by Jerry.Wang
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid