While I haven’t been working on C#/WPF in awhile, I figured I’d go ahead and complete a draft that’s been sitting in my queue for awhile.
Fast user switching is a concept Microsoft first included with Windows XP that allows the OS to quickly switch to another user and back by keeping the first user’s applications running while the second uses the computer.
This sounds like a great idea, and for the most part, it is; however, it places a new responsibility on application developers, because the context in which an application is running could change at any time.
That is, user1 could be using app.exe, switch to user2, and user2 could use app.exe. Then you have two users using the same executable at the same time, and user1 expects that app.exe will retain his place in the app for when he returns.
Now, before you start shouting, “Anarchy!”, note that there are a variety of things you can do to address this responsibility.
You can just tell user2 that he can’t use the application until user1 closes it. This is a poor approach, but you can feel free to annoy your users with how lazy you are if you like.
Alternatively, you can set your application to begin listening for window events that signal a change in the computer’s session.
Just as we have before, we’ll be using our friend, WndProc, to capture messages that WPF doesn’t fire for us. The one we’re looking for this time is WM_WTSSESSION_CHANGE, which will notify the application that the Windows session has changed. In order to receive notifications for this event, we’ll have to register using the function WTSRegisterSessionNotification.
Let’s kick things off with some imports for the Win32 APIs we’ll be using and the constants associated with them (see the MSDN links in the previous paragraph).
[DllImport("WtsApi32.dll")] private static extern bool WTSRegisterSessionNotification(IntPtr hWnd, [MarshalAs(UnmanagedType.U4)]int dwFlags); [DllImport("WtsApi32.dll")] private static extern bool WTSUnRegisterSessionNotification(IntPtr hWnd); [DllImport("kernel32.dll")] public static extern int WTSGetActiveConsoleSessionId(); // dwFlags options for WTSRegisterSessionNotification const int NOTIFY_FOR_THIS_SESSION = 0; // Only session notifications involving the session attached to by the window identified by the hWnd parameter value are to be received. const int NOTIFY_FOR_ALL_SESSIONS = 1; // All session notifications are to be received. // session change message ID const int WM_WTSSESSION_CHANGE = 0x2b1; public enum WTSMessage { // WParam values that can be received: WTS_CONSOLE_CONNECT = 0x1, // A session was connected to the console terminal. WTS_CONSOLE_DISCONNECT = 0x2, // A session was disconnected from the console terminal. WTS_REMOTE_CONNECT = 0x3, // A session was connected to the remote terminal. WTS_REMOTE_DISCONNECT = 0x4, // A session was disconnected from the remote terminal. WTS_SESSION_LOGON = 0x5, // A user has logged on to the session. WTS_SESSION_LOGOFF = 0x6, // A user has logged off the session. WTS_SESSION_LOCK = 0x7, // A session has been locked. WTS_SESSION_UNLOCK = 0x8, // A session has been unlocked. WTS_SESSION_REMOTE_CONTROL = 0x9 // A session has changed its remote controlled status. } |
The first thing we’ll need to do is register for notifications. You’ll probably want this near the start of your application, in case your user is fast and/or part of your QA. After registration is successful, we want to capture the initial session ID to use for comparisons later on.
private int initialSessionId; if (!WTSRegisterSessionNotification((new WindowInteropHelper(this)).Handle, NOTIFY_FOR_THIS_SESSION)) { // throw exception - registration has failed! } initialSessionId = Process.GetCurrentProcess().SessionId; |
When our WndProc is executed, we can check the message to see if it corresponds to the WM_WTSSESSION_CHANGE we’ve defined (confused? should’ve clicked that link earlier…).
We once again get the active session ID for the event to allow us to compare against our initial value. After all, we may not want to do anything if the session ID hasn’t changed.
It’s also useful to check the wParamValue in order to understand the type of session change that has occurred. If a user logs out, we can perform auto-save functions or some clean up to get rid of unneeded resources as a preparation for the new user to log in. Alternatively, we can expand the use of this feature and show low-res images to make repaints faster if it’s a remote connection.
protected override void WndProc(ref Message m) { int evtSessionID = WTSGetActiveConsoleSessionId(); switch (m.Msg) { case WM_WTSSESSION_CHANGE: { WTSMessage wParamValue = (WTSMessage)m.WParam.ToInt32(); Console.WriteLine("Session message " + wParamValue + " Active Session ID:" + evtSessionID + " Current Process Session ID:" + initialSessionId); // do something useful } break; } base.WndProc(ref m); } |
Lastly, let’s be a good neighbor and unregister our subscription for notifications using WTSUnRegisterSessionNotification. Be sure to do this before your window handle is destroyed, such as in your Window_Closing event.
WTSUnRegisterSessionNotification((new WindowInteropHelper(this)).Handle); |
All done! Now, let me know how you use it.