Optimizing Visual Studio 2010 and WPF applications for Remote Desktop

It is increasingly common for users to run their client application remotely, either connected to another Windows Client machine (Remote Desktop) or to a Windows Server (Terminal Service). In both of these scenarios the sources and the target machines are communicating over a protocol called Remote Desktop Protocol (or RDP).

In this blog, I wanted to share some of our findings while testing Visual Studio 2010 (VS 2010) over RDP as well as provide best practices to improve VS 2010 and your WPF-base app performance over RDP.

You may have seen the announcements (Soma , Jason Zander, Scott Guthris's) for the public release of Visual Studio 2010 RC . Some of the principles mentioned in this blog are also implemented by VS 2010.

While this blog is focusing on Microsoft remoting technologies some of the ideas discussed here should be applicable to other non-Microsoft technologies.

This blog is few pages long, the first part is a summary for folks who just want the short version. More details follow below in the document.

Summary: Best Practices for Improving VS 2010 and WPF Performance with Remote Desktop

A) Tune your Remote Desktop Connection (RDC) settings.

In slow bandwidth connections (such as slow DSL) or in high latency scenarios (such as connecting from US coast-to-coast) below tips can provide huge perf improvement. We for example measured ~4x gain in some scenarios.

  1. On Windows7, change settings to use 16-bit color in the RDC Options Display tab and check your performance.
    This could significantly reduce number of bytes sent across the RDP wire.
    (Windows XP/Vista already use 16-bit color by default)
  2. On Windows7, change settings to use "WAN" or "Satellite" for hi-latency connections in the RDC Options Experience tab. For example, consider doing so when connecting from coast-to-coast scenarios (e.g. 150+ms round-trip delay), regardless of the bandwidth. (You can determine your latency by doing "ping <your_server>" from a command line). This helps RDC decide how to do its optimization and improve perf.
    (Windows XP/Vista do not have these settings by default)
  3. On Windows XP/Vista install "Remote Desktop Connection 7.0 client update " and repeat steps from above.
  4. Disable all check boxes (e.g. Font Smoothing, Visual Styles, etc) other than "Persistence bitmap caching" in the RDC Experience tab.
    This also helps reduce number of bytes sent across the RDP wire.
  5. Select lower window size for your Remote Desktop window in the RDC Options Display tab. This also results in reduced number of bytes sent across the RDP wire.

Note that by doing above you are improving responsiveness but sacrificing some visual "prettiness" . Here is a summary of what we discussed above:

Connecting From:  

Win7/R2 

Vista SP+  

XP SP3 

XP SP2 

Default RDC version installed:  

RDC 7.0

RDC 6.1

RDC 6.1

RDC 5.2

For VS10 & WPF in slow network consider: ( e.g. slow DSL, or coast-to-coast connection)

1.Use 16-bit color. 2. Use "WAN"/"Satellite" in high-latency cases. 3. User lower window size

Install RDC 7.0. Repeat the Win7 suggestions  

Install RDC 7.0. Repeat the Win7 suggestions. 

Upgrade to XP3. Repeat XP SP3 suggestions.

B) Optimize your WPF app to be Remote Desktop aware.  
     Detect when you are running in Remote Desktop session and make sure to:
     1. Reduce amount of your app UI updates while running over RDP, by:

    • Turn off all animations during Remote Desktop session
    • Verify you do not have any hidden animations running
    • For animations that you must keep, use the DesiredFrameRate property to decrease frame-rate.
    • Modify control templates to remove animations that are built-in the default style.
    • Reduce redrawing in operations that are too fast for user to notice anyway. (e.g. during fast file scroll, do not update "line #' every line change , instead consider update every 200 msec)

     2. Simplify content that is redrawn frequently so that the RDC compressor can be more efficient.
(E.g. use solid colors over gradients or images.)
3. Avoid operations that are slow to render in software. (e.g. BitmapEffects & 3D)
4. Use the new VisualScrollableAreaClip NET4 API if your scenarios include line-scrolling.

C) During development, test your app to verify it is optimized for RDP

    1. Use WpfPerf Perforator tool to detect regions of your WPF app that are un-intentionally being invalidated.
    2. Use Network Emulation tool to emulate slow connection and verify your apps still perform.
    3. Use network monitoring tools to track regression by watching the amount of data you app sends over the wire as new features are added to your app. Consider adding test automation for RDP scenarios.

History and background

WPF Remoting architectures changes 

Until .NET Framework 3.5 SP1 (NET 3.5 SP1) and earlier, remoting between Vista to Vista with DWM on, leveraged a custom WPF primitive remoting protocol. In all other scenarios content was remoted as bitmaps.
Starting with the release of NET 3.5 SP1 (including NET 4), WPF renders the application content using a software rasterizer on the server and then remotes the content as bitmaps in all cases.
Note:

Bitmaps are highly compressed by the underlying RDC stack and only regions that changed are being updated. Also note that WPF does not currently have efficient occlusion support, so for instance animations that are completely hidden behind other opaque WPF elements will still force invalidation and RDP update.

When apps use GDI (such as many Win32 and Winforms apps do), only the GDI primitives are remoted. In many cases this can be more efficient than remoting WPF apps since WPF apps remotes bitmaps which typically result in more content being sent over the wire than a similar GDI-based app. The additional data may result in slower performance depending on network bandwidth and the size and frequency of updates.
However, in most scenarios, on reasonably fast connections, this is not an issue and in some scenarios (e.g. complex 3D scenes) remoting bitmaps can even present an advantage.
Noticeable performance issues can appear in low bandwidth situations and when there is significant amount of data that must be remoted. For example, fast scrolling text file, playing video, or lots of animations.

Window 7 & Windows Server 2008 R2 changes

Windows Server 2008 R2 and Windows 7 operating systems includes a much improved Remote Desktop, called RDP 7.0. Read more in the RDP white-paper.

In addition to these improvements, the Windows 7 "Remote Desktop Connection" (RDC) client has few other changes. The interesting ones, which can have big impact on WPF performance and that we will discuss in more details later are:

  • 32-bit Color Depth is the new default. (Vista/XP for example, has 16-bit color as the default)
  • User can select "High Latency" as one of the options in the Experience Tab
image image

It turns out that using 32-bit Color Depth, can improve many scenarios, however not all scenarios will improve.

The charts (taken from the RDP white-paper) show that PowerPoint scenarios would produce better RDP performance using RDP 7.0 with 32bpp Color Depth over RDP 7.0/6.1 16bpp. However, you are better off using 16bpp if you care more about better scrolling in Word/IE8.

Visual Studio 2010 over RDP is one of the scenarios that will benefit from switching to 16-bit color on slow connections.

image 

It is important to note that in Remote Desktop scenario, even though the Client can specify that it wants to use 32-bit color, the Server can override this and set a maximum color depth.

By default, Windows 2008 Server/Windows 2008 R2 set the limit to 16-bpp color (On W2k8R2 see: "Start->Administrative Tools->Remote Desktop Services"->Remote Desktop Session Host COnfiguration". Right Click on the connection name listed).  
image

Tuning RDP for optimal Visual Studio 2010 usage

As you may already know, many (but not all) UI components of VS 2010 are built using WPF 4.0 for the very first time. This implies that over RDP, VS 2010 remotes bitmaps for the portions of the UI that are built using WPF and remote GDI primitives for the non-WPF UI. The Editor is one of the VS 2010 UI features which are WPF-based.

During VS 2010 development, we worked really hard to optimize the Editor performance especially during continuous Scroll. By optimizing the Editor for the local scenario, we also improved the RDP scenario. Some of these principles we used are listed below.

We used a network emulator tool to simulate a slow network and measured amount of data transmitted on the wire during Editor scrolling. We also solicited feedback from users on the other side of the world who remoted to VS 2010 on machines in Redmond and compared the experience to Visual Studio 2008 side-by-side.

Some of our key findings were:
Over fast network with little latency:

  • VS 2010 RDP performance approximately matches the performance of VS 2010 on a local machine with average GPU performance.

  • VS 2010 RDP performance almost matches VS 2008 RDP performance

Over slow network with high latency (we simulated 512 kbps, 75 msec delay each way to imitate a typical coast-to-coast connection):

  • VS 2010 RDP performance significantly slower than VS08 if the default 32-bit color and the "LAN (10Mbps or higher)" are kept on Windows7-to-Windows7 connection.

  • VS 2010 RDP performance is only slightly slower than VS08 when we changed to 16-bit color and selected "WAN (10Mbps or higher with high latency)" or "Satellite (2Mbps or higher with high latency)".

The chart and table below show we can improve VS 2010 scrolling perf by ~74% (from 82 sec to 22 sec) on a slow network just by picking the right RDC settings!

image 

Page Scroll Scenario

RDP6/Vista 16bpp LAN Settings

Win7 16bpp - LAN settings

Win7 32bpp - LAN settings

Win7 16bpp - WAN settings

Win7 32bpp - WAN settings

Win7 16bpp - SAT settings

Win7 32bpp - SAT settings

VS 2010

28sec

28

82

22

75

22

65

We saw similar results in other VS 2010 scenarios, such as using menus, Intelisense, etc

In summary, to improve your VS 2010 perf over RDP consider:

  1. In Window 7:

    • If running over slow connection (e.g. slow DSL connection, for example 768kbs or lower), change RDC setting to use 16-bit color. This could significantly reduce number of bytes sent across the RDP wire. (Windows XP/vista already uses 16-bit color by default)

    • If running over high latency connection, for example when connecting from coast–to-coast (e.g. 150+ms round-trip latency), set "WAN (10Mbps or higher with high latency)" or "Satellite (2Mbps or higher with high latency) ". Consider doing so as long as you have latency, even if the bandwidth is fast!
      This helps the underlying Remote Desktop client decide how to do its optimizations and improve perf.
      You can determine your latency by doing "ping <your_server>" from a command line. (Windows XP/Vista do not have these settings by default)

  2. In Windows XP/Vista:
    Install, "Remote Desktop Connection 7.0 client update" and repeat steps from above. In addition to providing improve performance, installing RDP 7.0 it also allows you to select "high latency" in the "Remote Desktop Connection" Experience Tab UI which is not available in RDC 6.1/5.2.

  3. Disable all check boxes (e.g. Font Smoothing, Visual Styles, etc) other than "Persistence bitmap caching" in the Experience tab. This also helps reduce number of bytes sent across the RDP wire.

  4. Consider selecting lower size for your Remote Desktop window in the RDC settings. This will send smaller size bitmaps across the wire.

The above steps should help especially in low-bandwidth scenarios. As always check if performance actually improves after you made these changes. You may need to revert these changes if you use other apps or connect to another target machine with different network characteristics.

Optimizing your WPF app for RDP

In addition to selecting the correct RDC settings mentioned earlier, reducing the amount of content that needs to be sent across the wire is the key for better performance.

Your app for example, can detect that it is running over RDP and reduce the amount of content that needs to be updated and sent as bitmaps over the wire. It can also use graphic elements that can be compressed more efficiently by RDC bitmap compressor (e.g. use solid colors vs. gradients).
In addition, since WPF renders the content on the server in software, the app should avoid using elements that are slow to render in software.
Here are some more specific guidelines of what you can do:

  1. Detect if your app is running in Remote Desktop session.
    Listen to WM_WTSSESSION_CHANGE/WTS_REMOTE_CONNECT Windows messages. (See attached sample)

  2. Reduce the amount of UI updates while running over RDP.
    You can even consider doing so only when connection is slow (techniques on how to detect connection bandwidth is not provided here).
    Some of the common concepts are:

    • If possible consider turning off all animations during Remote Desktop session.
      For example: Creating a window background to an animating gradient or a video will cause the entire window to redraw every frame and be transmitted over the wire.

    • WPF does not currently ignore occluded parts of your app, so turn off all animations that are hidden behind some other opaque element.

    • For animations that you must keep, use the DesiredFrameRate property to decrease animation frame-rate and reduce traffic.

  3. Simplify control templates to remove all animations.
    Many of the default WPF control templates include animation. Consider modifying those. For example:

    • ProgressBar control has ambient animation.
    • Button, Radio, Checkbox, etc animate when you hover over them
    • ComboBox animated when it drops down , you can re-style it and say 'PopupAnimation="None"'
  4. Help the RDP compressor to compress more efficiently by simplifying other dirty regions of your app that are redrawn frequently. For example:
    Use solid colors over gradients or other fills such as images.

  5. Since WPF renders in software on the target machine, avoid operations that are especially slow to render in software. For example:

    • 3D (3D is significantly slower to render in Software)
    • BitmapEffects such as blur and drop shadow are significantly slow to render in software
  6. Be smart and reduce redrawing in operations that are happing too fast for user to notice anyway.For example if user is fast scrolling thru a file, you can update the "line number" and other outlining every 200ms but not on every line change. Or display thumbnail tooltip while fast scrolling in large data-grid instead of scrolling the entire grid.

  7. If your application scrolls text, take advantage of the new VisualScrollableAreaClip NET4 apis which was specifically designed to improve line-scrolling over RDP.

  8. Use WpfPerf Perforator tool to detect what areas of your app are being invalidated. You may find regions that are not intended to be invalidated.
    Perforator will scroll three colors (gray, yellow, purple) for every region of you app that is changing and requires invalidations. More info here: https://windowsclient.net/wpf/perf/wpf-perf-tool.aspx

    As an example, in the image below I have two ProgressBars one of which is hidden (the one on the left).
    By default ProgressBar has ambient animation and therefore causes additional network traffic on each frame.
    What you may have not expected is for the hidden ProgressBar to animate and causing unnecessary invalidations over RDP (lack of occlusion support mentioned earlier).

    You may have also expected only the two ProgerssBar animating rectangles to get updated. However, under certain conditions WPF will coalesce number of dirty regions into one larger one and this is why you see the larger yellow rectangle in the image below. This effect depends on how far the dirty rectangles are. In some cases this may provide an improvement as instead of multiple bitmaps only one is sent over RDP. Apps have no control over this but should be aware of this characteristic.
    image   

  9. Use Network Emulation tools (e.g. https://static.shunra.com/free-trials.php ) to emulate different network conditions and verify your apps still perform well under low bandwidth/ hi-latency conditions.

  10. Use network tools to track regression in amount of data that is sent over the wire. Consider using wireshark (https://www.wireshark.org/) Network Monitor (https://www.microsoft.com/downloads/details.aspx?FamilyID=983b941d-06cb-4658-b7f6-3088333d062f&displaylang=en ) or other network tools to measure the amount of data your application sends over RDP and make sure it is not 'excessive'.

  11. Consider adding test automation to make sure new features are not causing excessive invalidations and data transfer which can cause RDP performance regression.
    ETW based tools are a good approach for this automation this and I hope to post an example in future blogs.

How we optimized Visual Studio 2010 for Remote Desktop

Visual Studio 2010 uses many of the approaches mentioned above.
VS 2010 has a new "Enable rich client visual experience" checkbox in the Tools/Option menu.
image

By default, the "Automatically adjust visual experience based on client performance" option is checked and Visual Studio 2010 will try to choose settings that are optimized for best performance. For example, if Visual Studio 2010 detects that it is running in a Remote Desktop session (as well as for other scenarios such as Virtual Machine environment or on machine w/ low-end graphics where WPF rendering tier return 0 or 1) then some visual effects are turned off.
Here is a non-exhaustive list of things that happen in that situation:

  • Animations and gradients (such as on the Start page) are turned off.
  • Icons in the status bar are no longer animated.
  • Gradients in the menus, context menus, command bar, tab well, window titles, Intellisense and toolbars become solid color fills.
  • Gradients and stippling in the environment (main window) background, and new project dialog become solid color fills.
  • Highlights when the user hovers over a collapsible region on the editor outlining margin are turned off.
  • Updating of the text editor's vertical scroll bar is performed at a slower rate.
  • Thenew NET4 VisualScrollableAreaClip WPF API is used to improve line-scrolling in the editor.

In addition, regardless of whether “Enable rich client visual experience” is turned on or off, during rapid scrolling in the text editor, VS 2010:

  • Postpones selected visual updates (e.g. the outlining margin and underline squiggles) until Idle.
  • Reduces the number of layouts during thumb-tracking in the vertical scrollbar (when the scroll thumb is moved rapidly up or down).

Resources:

Below are related useful blogs:

Attached sample:

Below (also attached) sample code show how you can listen to the various Remote Desktop events and as discussed earlier you can use these events to trigger your app "RDP" features.
In my sample, the window background is comprised of animating gradient. The app detects a Remote Desktop session connection and then switches the window background to be a solid color and vice versa. 
If I keep the animated background, then the entire window is invalidated and would have to be transmitted over the wire on each frame (e.g. 60 frame-per-seconds on most monitors).
With animation removed and by using a solid color instead no network traffic is needed.
Here is the network traffic I measured when I was experimenting with my sample:

App runs for 5 seconds (1280x1024)

When background animates

When background uses solid color  

Amount of RDP traffic over 5 sec:

11 Mb 

5 bytes 

As you can see this would make a huge difference in low-bandwidth scenarios.

image

Figure 1- Animated background when run locally

image

Figure 2- Solid background when over Remote Desktop

    1: <Window x:Class="Animation_Sample_For_RDP.Window1"
    2:         xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    3:         xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" 
    4:         Title="Sample RDP-aware WPF application" Height="600" Width="750"
    5:         Loaded="Window_Loaded" Unloaded="Window_Unloaded"   
    6:         xmlns:src="clr-namespace:Animation_Sample_For_RDP" >
    7:  
    8:     <Window.Resources>
    9:         <src:RemoteDesktopClass x:Key="myDataSource" />
   10:     </Window.Resources>
   11:     <Window.Template>
   12:         <ControlTemplate TargetType="{x:Type src:Window1}">
   13:             <Border Name="myBorder">
   14:                 <Border.Background>
   15:                     <LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
   16:                         <GradientStop Color="Green" Offset="0"/>
   17:                         <GradientStop Color="LightBlue" Offset="0.2"  x:Name="Foo"/>
   18:                         <GradientStop Color="Blue" Offset="1"/>
   19:                     </LinearGradientBrush>
   20:                 </Border.Background>
   21:                 <StackPanel Margin="20,0,0,0">
   22:                     <StackPanel.DataContext>
   23:                         <Binding Source="{StaticResource myDataSource}"/>
   24:                     </StackPanel.DataContext>
   25:                     <StackPanel Margin="20,0,0,0"  Orientation="Horizontal">
   26:                         <TextBlock Margin="0,15,0,0" FontWeight="Bold">Remote Desktop Session status:</TextBlock>
   27:                         <TextBlock Margin="5,15,0,0" Name="RD_status"  
   28:                               Text="{Binding Path=RemoteDesktopStatus, UpdateSourceTrigger=PropertyChanged}"/>
   29:                     </StackPanel>
   30:                     <StackPanel Margin="20,0,0,0"  Orientation="Horizontal">
   31:                         <TextBlock Margin="0,15,0,0" FontWeight="Bold">In Remote Desktop Session:</TextBlock>
   32:                         <TextBlock Margin="5,15,0,0" 
   33:                               Text="{Binding Path=IsRemoteDesktopSession, UpdateSourceTrigger=PropertyChanged}"/>
   34:                     </StackPanel>
   35:               </StackPanel>
   36:             </Border>
   37:             <ControlTemplate.Triggers>
   38:                 <EventTrigger RoutedEvent="Window.Loaded">
   39:                     <BeginStoryboard>
   40:                         <Storyboard AutoReverse="True" BeginTime="0" >
   41:                             <DoubleAnimation Storyboard.TargetName="Foo"  Storyboard.TargetProperty="Offset"  
   42:                                 AutoReverse="True" From="0.1" To="0.9" Duration="0:0:3" RepeatBehavior="Forever"/>
   43:                         </Storyboard>
   44:                     </BeginStoryboard>
   45:                 </EventTrigger>
   46:                 <!--Set to Solid background when a Remote Session was triggered -->
   47:                 <DataTrigger Binding="{Binding Source={StaticResource myDataSource}, 
   48:                     Path=IsRemoteDesktopSession}" Value="true">
   49:                     <Setter TargetName="myBorder" Property="Border.Background" Value="LightBlue"/>
   50:                 </DataTrigger>
   51:             </ControlTemplate.Triggers>
   52:         </ControlTemplate>
   53:     </Window.Template>
   54: </Window> 
    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.Data;
    8: using System.Windows.Documents;
    9: using System.Windows.Input;
   10: using System.Windows.Media;
   11: using System.Windows.Media.Imaging;
   12: using System.Windows.Navigation;
   13: using System.Windows.Shapes;
   14: using System.Windows.Interop;
   15: using System.Runtime.InteropServices;
   16: using System.ComponentModel;
   17: using System.Windows.Threading;
   18:  
   19: namespace Animation_Sample_For_RDP
   20: {
   21:     public partial class Window1 : Window
   22:     {
   23:         [DllImport("wtsapi32.dll", SetLastError = true)]
   24:         static extern bool WTSRegisterSessionNotification(IntPtr hWnd, [MarshalAs(UnmanagedType.U4)] int dwFlags);
   25:         [DllImport("wtsapi32.dll", SetLastError = true)]
   26:         static extern bool WTSUnRegisterSessionNotification(IntPtr hWnd);  // constants passed in the dwFlags parameter
   27:         const int NOTIFY_FOR_THIS_SESSION = 0;
   28:         const int NOTIFY_FOR_ALL_SESSIONS = 1;      // message id to look for when processing the message (see sample code)
   29:         const int WM_WTSSESSION_CHANGE = 0x2b1;     // WParam values that can be received:
   30:         const int WTS_CONSOLE_CONNECT = 0x1;        // A session was connected to the console terminal.
   31:         const int WTS_CONSOLE_DISCONNECT = 0x2;     // A session was disconnected from the console terminal.
   32:         const int WTS_REMOTE_CONNECT = 0x3;         // A session was connected to the remote terminal.
   33:         const int WTS_REMOTE_DISCONNECT = 0x4;      // A session was disconnected from the remote terminal.
   34:         const int WTS_SESSION_LOGON = 0x5;          // A user has logged on to the session.
   35:         const int WTS_SESSION_LOGOFF = 0x6;         // A user has logged off the session.
   36:         const int WTS_SESSION_LOCK = 0x7;           // A session has been locked.
   37:         const int WTS_SESSION_UNLOCK = 0x8;         // A session has been unlocked.
   38:         const int WTS_SESSION_REMOTE_CONTROL = 0x9; // A session has changed its remote controlled status.
   39:         public enum SystemMetric
   40:         {
   41:             SM_REMOTESESSION = 0x1000,
   42:             SM_REMOTECONTROL = 0x2001,
   43:         }
   44:         [DllImport("user32.dll")]
   45:         static extern int GetSystemMetrics(SystemMetric smIndex);
   46:         static IntPtr hwnd;
   47:         static Window1 myClass;
   48:         public Window1()
   49:         {
   50:             InitializeComponent();
   51:             myClass = this;
   52:         }
   53:         private void Window_Loaded(object sender, RoutedEventArgs e)
   54:         {
   55:             Window w = Application.Current.MainWindow;
   56:  
   57:             WindowInteropHelper wih = new WindowInteropHelper(this);
   58:             hwnd = wih.Handle;
   59:  
   60:             if (!WTSRegisterSessionNotification(hwnd, NOTIFY_FOR_THIS_SESSION))
   61:                 MessageBox.Show("WTSRegisterSessionNotification failure");
   62:             HwndSource MainWindowHwndSource = PresentationSource.FromVisual(w) as HwndSource;
   63:             if (MainWindowHwndSource != null)
   64:                 MainWindowHwndSource.AddHook(new HwndSourceHook(MainWindowHwndMessageFilter));
   65:             else
   66:                 Console.WriteLine("MainWindowHwndSource == null");
   67:  
   68:             if (GetSystemMetrics(SystemMetric.SM_REMOTECONTROL) != 0)
   69:                 Console.WriteLine("Session is currently remotely controlled.");
   70:             else
   71:                 Console.WriteLine("Session is NOT currently remotely controlled.");
   72:             if (GetSystemMetrics(SystemMetric.SM_REMOTESESSION) != 0)
   73:             {
   74:                 Console.WriteLine("Process is associated with a Terminal Services client session.");
   75:                 SetRemoteSessionStatus("WTS_CONSOLE_CONNECT");
   76:                 SetRemoteSessionState(true);    // we are over Remote Session !
   77:             }
   78:             else
   79:             {
   80:                 Console.WriteLine("Process is NOT associated with a Terminal Services client session.");
   81:                 SetRemoteSessionState(false); // we are connected locally !
   82:             }
   83:         }
   84:         static void SetRemoteSessionStatus(string status)
   85:         {
   86:             myClass.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() =>
   87:             {
   88:                 RemoteDesktopClass db = myClass.FindResource("myDataSource") as RemoteDesktopClass;
   89:                 db.RemoteDesktopStatus = status;
   90:             }));
   91:         }
   92:         static void SetRemoteSessionState(bool b)
   93:         {
   94:             myClass.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() =>
   95:             {
   96:                 RemoteDesktopClass db = myClass.FindResource("myDataSource") as RemoteDesktopClass;
   97:                 db.IsRemoteDesktopSession = b;
   98:  
   99:             }));
  100:         }
  101:         static IntPtr MainWindowHwndMessageFilter(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
  102:         {
  103:             switch (msg)
  104:             {
  105:                 case WM_WTSSESSION_CHANGE:
  106:  
  107:                     Console.Write("WM_WTSSESSION_CHANGE ");
  108:                     int param = ((int)wParam);
  109:                     switch (param)
  110:                     {
  111:                         case WTS_CONSOLE_CONNECT:
  112:                             Console.Write("WTS_CONSOLE_CONNECT\n");
  113:                             SetRemoteSessionStatus("WTS_CONSOLE_CONNECT");
  114:                             SetRemoteSessionState(false); // we are connected locally !
  115:                             break;
  116:                         case WTS_CONSOLE_DISCONNECT:
  117:                             Console.Write("WTS_CONSOLE_DISCONNECT\n");
  118:                             SetRemoteSessionStatus("WTS_CONSOLE_DISCONNECT");
  119:                             break;
  120:                         case WTS_REMOTE_CONNECT:
  121:                             Console.Write("WTS_REMOTE_CONNECT\n");
  122:                             SetRemoteSessionStatus("WTS_REMOTE_CONNECT");
  123:                             SetRemoteSessionState(true); // we are over Remote Session !
  124:                             break;
  125:                         case WTS_REMOTE_DISCONNECT:
  126:                             Console.Write("WTS_REMOTE_DISCONNECT\n");
  127:                             SetRemoteSessionStatus("WTS_REMOTE_DISCONNECT");
  128:                             break;
  129:                         case WTS_SESSION_LOGON:
  130:                             Console.Write("WTS_SESSION_LOGON\n");
  131:                             SetRemoteSessionStatus("WTS_SESSION_LOGON");
  132:                             break;
  133:                         case WTS_SESSION_LOGOFF:
  134:                             Console.Write("WTS_SESSION_LOGOFF\n");
  135:                             SetRemoteSessionStatus("WTS_SESSION_LOGOFF");
  136:                             break;
  137:                         case WTS_SESSION_LOCK:
  138:                             Console.Write("WTS_SESSION_LOCK\n");
  139:                             SetRemoteSessionStatus("WTS_SESSION_LOCK");
  140:                             break;
  141:                         case WTS_SESSION_UNLOCK:
  142:                             Console.Write("WTS_SESSION_UNLOCK\n");
  143:                             SetRemoteSessionStatus("WTS_SESSION_UNLOCK");
  144:                             break;
  145:                         case WTS_SESSION_REMOTE_CONTROL:
  146:                             Console.Write("WTS_SESSION_REMOTE_CONTROL\n");
  147:                             SetRemoteSessionStatus("WTS_SESSION_REMOTE_CONTROL");
  148:                             break;
  149:                     }
  150:                     break;
  151:                 default:
  152:                     break;
  153:             }
  154:             return IntPtr.Zero;
  155:         }
  156:         private void Window_Unloaded(object sender, RoutedEventArgs e)
  157:         {
  158:             if (!WTSUnRegisterSessionNotification(hwnd))
  159:                 Console.WriteLine("WTSUnRegisterSessionNotification failure");
  160:         }
  161:     }
  162:     public class RemoteDesktopClass : INotifyPropertyChanged
  163:     {
  164:         string _RemoteDesktopStatus;
  165:         bool _IsRemoteDesktopSession;
  166:         public RemoteDesktopClass()
  167:         {
  168:         }
  169:         public RemoteDesktopClass(string value)
  170:         {
  171:             this.RemoteDesktopStatus = value;
  172:         }
  173:         public string RemoteDesktopStatus
  174:         {
  175:             get { return _RemoteDesktopStatus; }
  176:             set
  177:             {
  178:                 _RemoteDesktopStatus = value;
  179:                 OnPropertyChanged("RemoteDesktopStatus");
  180:             }
  181:         }
  182:         public bool IsRemoteDesktopSession
  183:         {
  184:             get { return _IsRemoteDesktopSession; }
  185:             set
  186:             {
  187:                 _IsRemoteDesktopSession = value;
  188:                 OnPropertyChanged("IsRemoteDesktopSession");
  189:             }
  190:         }
  191:         public event PropertyChangedEventHandler PropertyChanged;  //OnPropertyChanged -update property value in binding
  192:         private void OnPropertyChanged(String info)
  193:         {
  194:             if (PropertyChanged != null)
  195:                 PropertyChanged(this, new PropertyChangedEventArgs(info));
  196:         }
  197:     }
  198: }

Animation_Sample_For RDP.zip