人们越来越普遍,用户运行远程客户端应用程序,无论是连接到另一台Windows客户机(远程桌面)或到Windows Server(终端服务)。 在这些情况下的源和目标机器都是沟通了一个名为远程桌面协议(RDP协议或)。

在这个博客,我想分享我们的一些调查结果,而测试的Visual Studio 2010(比2010年)以上的RDP,并提供最佳实践,以提高比2010年和你的WPF的应用程序性能的RDP以上基地。

您可能已经看到了公告(索玛 , 贾森詹德, 斯科特Guthris的)的释放公众 的Visual Studio 2010钢筋混凝土  博客在此提及的一些原则,也比2010年执行。

虽然这个博客的重点是微软的远程处理技术在这里讨论应该适用于其他非微软技术的一些想法。

此博客是几页长,第一部分是一个人谁只是想短版本的摘要。 更多详情请按照以下的文件。

综述:最佳实践提高与远程桌面比2010年和WPF性能

一)调整您的远程桌面连接(RDC)的设置。

在缓慢的带宽连接的DSL(如慢)或高延迟的情况(如美国海岸海岸连接到)下面的提示可以提供 PERF的巨大改善 例如,我们的测量 〜4倍的收益 在某些情况下。

  1. 关于Windows7的,更改设置,以使用在洗肾选项显示选项卡的16位彩色,并检查你的表现。 
    这将大大减少字节数的RDP整个网络发送。 
    (视窗XP / Vista已经使用16位默认色)
  2. 关于Windows7的,更改设置,以使用“广域网”或“卫星”为金丽选项体验选项卡喜延时的连接的。 例如,考虑这样做连接时延迟从大西洋岸至太平洋岸的旅行方案(如150 + ms的圆桌会议), 无论 是带宽 (您可以决定行)你的潜伏期做“一平<your_server>”从命令。 这有助于金丽决定如何做的优化和改善PERF的。 
    (视窗XP / Vista的默认情况下没有这些设置)
  3. 在Windows XP / Vista的 安装“远程桌面连接7.0客户端更新 ,并重复上述步骤的。
  4. 禁用所有复选框 (如字体平滑,视觉样式等 不是“位图缓存的持久性”选项卡的刚果民主共和国的经验。 
    这也有助于减少字节数的RDP整个网络发送。
  5. 选择较低的窗口大小 为您的远程桌面选项窗口的刚果民主共和国 显示选项卡。 这也字节的结果数量在减少整个发送的RDP线。

请注意,你在做以上的改善,但牺牲一些视觉反应“漂亮”。 下面是我们上面讨论的摘要:

连接者: 

Win7/R2 

远景警司+ 

XP SP3的 

XP SP2的 

金丽默认安装的版本:  

金丽7.0

金丽6.1

金丽6.1

金丽5.2

对于VS10&WPF的速度较慢的网络中考虑: 如 慢的DSL,  大西洋岸至太平洋岸的连接)

1,采用16位颜色。 
2。 
使用“广域网”/“卫星”在高延迟的情况。 
3。 
更低的用户窗口的大小

安装金丽7.0。重复Win7建议 

安装金丽7.0。重复Win7建议。 

升级到XP3。 重复XP SP3的建议。

乙)优化您的WPF的应用程序必须知道远程桌面。  
     检测时,你正在运行在远程桌面会话,并确保: 
     1。 减少数量,而你的应用程序运行在用户界面更新的RDP,即:

  •  
    • 关闭所有动画在远程桌面会话
    • 确认你没有任何正在运行的隐藏动画
    • 对于动画,你必须保持,使用 DesiredFrameRate 属性,以降低帧速率。
    • 修改控制模板,以消除那些内置动画中的默认样式。
    • 减少重新划分操作太快了用户注意反正。 (在快速文件滚动如不更新“行#'每一行的变化,而不是考虑更新每200毫秒)

     2。 简化内容,重绘经常让洗肾压缩机可以更有效。 
     (例如使用了渐变或图像纯色。) 
    3。 
避免在软件的作业,呈现缓慢。 (例如BitmapEffects及三维) 
    4。 
使用新 VisualScrollableAreaClip NET4的API,如果你的情形包括线滚动。

C电开发),测试您的应用程序,以验证它是否为RDP优化

  1.  
    1.  使用 WpfPerf 射孔工具来检测你的WPF的应用程序区是联合国人故意被宣布无效。
    2. 使用网络仿真工具来模拟慢速连接并验证您的应用程序仍然执行。
    3. 使用网络监控工具来跟踪应用程序,通过观察回归的金额数据传送功能,您对新的电线作为添加到您的应用程序。 考虑添加的RDP自动化测试方案。

历史与背景

WPF中Remoting的体系结构的变化 

在此之前的。NET Framework 3.5 SP1的(NET 3.5的SP1)和比较,Vista的DWM的远程之间到Vista上,利用一个WPF原始的远程协议的习俗。 在所有其他情况下被远程内容为位图。 
首先是3.5版本的NET SP1的(包括净4),WPF的应用程序的内容呈现在服务器上使用的软件光栅,然后遥控器位图中的内容作为 所有 案件。 
注意:

位图是由高度压缩的基本金丽,改变只堆栈和地区正在更新。 还要注意的是WPF中目前没有有效的闭塞的支持,实例,以便为 更新动画是WPF的完全不透明的背后隐藏的其他因素将迫使失效的RDP。

当应用程序使用GDI(如许多Win32和WinForms应用程序一样),只有GDI的原语是远程。 在许多情况下,这可以比WPF的应用程序的远程遥控器,因为WPF的应用程序有效位图这通常导致更多的内容正在通过类似的GDI比1的应用程序网络发送。 额外的资料可能会导致较慢的性能取决于网络带宽和规模和更新的频率。 
然而,在大多数情况下,在合理快速连接,这不是一个问题,在某些情况下(如复杂的3D场景)远程处理位图,甚至可以提出一个优势。 
显着的性能问题可以出现在低带宽的情况下,当有远程的,必须大量数据。 举例来说,快速滚动的文本文件,播放视频,或动画很多。

视窗7和Windows服务器2008 R2的变化

Windows服务器2008 R2和Windows 7作业系统包括一个远程桌面大为改善,所谓的RDP 7.0。 阅读更多的 的RDP白色纸

除了这些改进,Windows 7的“远程桌面连接”(RDC)的客户几乎没有其他变化。 有趣的,它可以对WPF的性能影响很大,我们将在稍后详细讨论如下:

  •  
  • 32位颜色深度是 新的默认 (斯塔/ XP的例子,有16位默认的颜色为)
  • 用户可以选择“高经验,作为在一个选项卡的选项潜伏”

 

image image

 

事实证明,使用32位色彩深度,可以改善许多情况下,但 不是所有的情况会改善

图表(即取 的RDP白色纸张)表明,PowerPoint中的情况会产生更好的RDP使用RDP 7.0性能与32bpp色彩深度以上的RDP 7.0/6.1支持的16bpp。 但是,你最好使用支持的16bpp如果你更关心更好Word/IE8滚动研究。

2010年在Visual Studio中的场景RDP是有利于切换到16位的连接速度慢的颜色之一。

image 

重要的是要注意,在远程桌面情况下,即使客户可以指定它要使用32位颜色,可以覆盖此服务器并设置一个最大颜色深度。

默认情况下Windows 2008服务器/视窗2008 R2的限制设置为16 bpp的颜色(在W2k8R2见:“开始->”管理工具->“遥控桌面服务” - >“远程桌面会话主机配置”。右键单击连接名称上市)。   
image

最佳调谐的RDP使用的Visual Studio 2010

正如您可能已经知道,很多(但不是全部)的UI组件是比2010年建成使用的第一时间WPF的4.0。 这意味着,超过的RDP,比2010年的遥控器为用户界面使用的是内置WPF和偏远的GDI对非WPF的UI的原语部分的位图。该编辑器是用户界面功能的VS 2010是基于WPF的一个。

在比2010年的发展,我们工作的非常努力,以优化读者,特别是在连续滚动的表现。 通过优化对当地情况的编辑,我们也改善了RDP的情况。 其中一些我们使用下面列出的原则。

我们使用网络仿真器的工具,以模拟和测量的速度较慢的网络线路上传输的数据量在编辑器中滚动。 我们还征求用户对世界远程到VS 2010谁在雷德蒙德机器和比较丰富的经验,Visual Studio 2008的副作用,由方对方的反馈。

我们的一些主要结果如下: 
过 快速的网络 几乎没有延迟

  • 相较于2010年的RDP性能大约 匹配 机器性能与GPU的性能平均比2010年本地。

  • 相较于2010年的RDP表现 几乎场比赛 的表现比2008年的RDP

过 慢的网络 具有高延迟 (512 kbps的,我们模拟,单程75毫秒延迟模仿一个典型的海岸到海岸连接):

  • 相较于2010年的RDP性能 显着慢 比VS08如果默认的32位彩色和“局域网(10Mbps或更高)” 保存 在Windows7的对Windows7的连接。

  • 相较于2010年的RDP表现只是 稍微慢 比VS08当我们改变了对16位颜色和选择“广域网(10Mbps或更高的延迟高)”或“卫星(2Mbps的或延迟较高,高)”。

图表并显示下面的表格我们可以比2010年提高了滚动PERF的〜74% ( 从82秒到22秒的网络)上缓慢 刚刚采摘的权利金丽设置

image 

 

页面滚动方案

RDP6/Vista局域网设置支持的16bpp

Win7支持的16bpp - 局域网设置

Win7 32bpp - 局域网设置

Win7支持的16bpp - 广域网设置

Win7 32bpp - 广域网设置

Win7支持的16bpp - SAT的设置

Win7 32bpp - SAT的设置

相较于2010年

28sec

28

82

22

75

22

65

 

我们看到,如使用菜单,Intelisense等其他比2010年的情况类似的结果,

总之,要改善你的VS 2010以上的RDP PERF的考虑:

  1. 在Window 7:
    • 如果运行通过慢速连接(如DSL连接缓慢,例如768kbs或更低),金丽设置更改为使用16位颜色。 这可以大大减少对网络发送的字节数跨越了RDP。 (视窗XP / Vista已经使用16位的默认颜色的)

    •  如果运行在高延迟连接,例如当从沿海海岸连接到 (如150 + ms的往返延迟时间), 设置“广域网(10Mbps或更高的延迟高)”或“卫星(2Mbps的延迟较高或高)“。 考虑这样做,只要你有延迟, 甚至 如果带宽为快! 
      这有助于相关远程桌面客户端决定如何做优化和改善其PERF的。 
      您可以确定您的潜伏期命令行做“一平<your_server>”的。 (视窗XP / Vista中没有这些设置在默认情况下)

  2. 在Windows XP / Vista的: 
    安装“远程桌面连接7.0客户端更新“ ,并重复上述步骤的。 除了提供改善的性能,安装的RDP 7.0还允许您选择“高延迟连接”中的“远程桌面”选项卡界面的经验是不可用在RDC 6.1/5.2。

  3.  禁用所有复选框 (如字体平滑,视觉样式等) 不是“位图缓存的持久性” 选项卡中的经验。 这也有助于减少对网络发送的字节数跨越了RDP。

  4. 考虑选择桌面窗口的大小为您的远程 设置的刚果民主共和国。 这将通过线路发送较小的尺寸位图。

上述措施应有助于 特别是 在低带宽的情况。 一如往常检查后,你的表现确实改善了这些变化。 您可能需要恢复这些变化如果您使用其他应用程序或连接不同的网络特性,另一个目标机器。

优化您的RDP WPF的应用程序

除了选择正确的洗肾前面提到的设置,减少的内容,需要数额将通过线路发送的关键是获得更好的性能。

比如你的应用程序,可以检测到它运行在RDP和减少的内容,需要进行更新和对线位图发送量。 它也可以使用可以压缩位图压缩机金丽如使用纯色主场迎战梯度(更有效的图形元素)。 
此外,由于WPF中呈现的内容在服务器软件,应用程序应避免使用元素,呈现缓慢的软件。 
下面是一些 你可以做什么更具体的指导方针:

  1. 检测如果您的应用程序运行在远程桌面会话。 
    听WM_WTSSESSION_CHANGE /WTS_REMOTE_CONNECT Windows消息。 (见所附样本)
  2. 减少数量,同时更新了用户界面的RDP运行。 
    你甚至可以考虑这样做只有在连接速度慢(关于如何检测连接带宽技术,这里不提供)。 
    共同的概念有:

    • 如果可能的话考虑关闭在远程桌面会话中的所有动画。 
      例如: 
      创建一个窗口的背景渐变动画或视频会导致整个窗口重绘每一帧,并线传输的。

    •  WPF中目前没有忽略你的应用程序部分闭塞,所以关闭所有后面的其他一些不透明的元素隐藏动画。

    •  对于动画,你必须保持,使用 DesiredFrameRate 属性,以降低动画帧速率和减少交通。

  3. 简化控制模板,以消除所有的动画。 
    WPF控件的默认模板中包括许多动画。 审议修改的。 例如: 
     
    • ProgressBar控件的环境动画。
    • 按钮,收音机,复选框,动画等你对他们悬停时
    • 组合框下拉动画时,您可以重新风格,并说 'PopupAnimation =“无”' 
  4. 帮助了RDP压缩机压缩,简化你的应用程序,经常是重绘其他地区更有效地脏。 例如: 
    在梯度或其他诸如图像使用纯色填充。
  5. 由于WPF的呈现在目标机上软件,避免操作,尤其在软件渲染速度慢。 例如:
    • 三维(3D是呈现明显慢于软件)
    • BitmapEffects如模糊和阴影都呈现明显慢于软件
  6. 反正是聪明和减少重新划分的行动中也正在快速的happing用户另行通知。例如,如果用户是通过一个快速滚动文件,您可以更新的“行号”等每200ms的概述,但不改变的每一行。 或显示缩略图工具提示,而在大型数据的快速滚动,滚动发车,而不是整个网格。
  7. 如果您的应用滚动文本,以新优势的 VisualScrollableAreaClip NET4的RDP的API是专门用来改善生产线过度滚动。
  8. 使用 WpfPerf 射孔工具来检测你的什么应用领域正在失效。 您可能会发现那些不打算地区被宣布无效。 
    射孔器将滚动三种颜色(灰色,黄色,紫色)为您地区的每一个应用程序,正在发生变化,需要废票。 更多信息在这里: http://windowsclient.net/wpf/perf/wpf-perf-tool.aspx 

     

    作为 一个例子,在下面的图片我有两个ProgressBars其中一个是隐藏的(左一对)。 
    默认情况下的ProgressBar的环境动画,因而造成对每帧增加网络流量。 
    你可能没有预期为隐藏ProgressBar的是动画,并造成了不必要的废票的RDP(前面提到闭塞的支持不足)。 

    您可能还预期只有2 ProgerssBar生动活泼,以获取更新的长方形。 但是,在一定条件下WPF可以合并成一个较大的一个肮脏的一些地区,这就是为什么你看到下面的图片较大的黄色矩形。 这种影响的程度取决于脏矩形的。 在某些情况下,这可以提供一个作为位图而不是只有一个是多了的RDP传送改善。 在这个应用程序没有控制,但应该认识到这一特点。 
    image   

  9. 使用网络仿真工具(如 http://static.shunra.com/free-trials.php )来模拟不同的网络条件和验证您的应用程序仍然表现良好在低带宽/高延迟的条件。
  10. 使用网络工具来跟踪线回归,在发送的数据量多是。 考虑使用 的wireshark (http://www.wireshark.org/) 网络监视器(http://www.microsoft.com/downloads/details.aspx ?FamilyID = 983b941d - 06cb - 4658 - b7f6 - 3088333d062f&displaylang =恩) 或其他网络工具来衡量你的数据量的应用程序发送多RDP和确定它是不是'过多'。
  11.  考虑加入测试自动化,以确保不会造成新的功能和数据传输废票过多,可能会引起的RDP表现倒退。 
    ETW基础的工具,这是一个自动化的好方法这一点,我希望在未来的博客张贴的例子。

我们如何优化的Visual Studio 2010远程桌面 

Visual Studio 2010的使用上述的方法很多。 
比2010年有一个新的“视觉经验丰富的客户端启用”在工具/选项菜单复选框。 
image 
  

默认情况下,“自动调整视觉经验的基础上的客户端性能”选项被选中和Visual Studio 2010将尝试选择性能优化设置为最佳。 例如,如果Visual Studio 2010检测到它是在一个会话中运行的远程桌面 (以及其他情况,如虚拟机环境或 机器瓦特/低端显卡在 WPF中绘制层 返回0或1) 然后一些视觉影响转向的楼 
这里是一个东西,在这种情况下发生的不完全名单:

  • 动画,例如在开始页面梯度()被关闭。
  • 在状态栏中的图标不再是动画。
  • 在菜单,上下文菜单,命令栏,制表符井,窗口标题,智能感知和工具栏渐变成为纯色填充。
  • 梯度和环境(主窗口)的背景点画,并成为新的项目对话框纯色填充。
  • 当一个编辑在湿陷性黄土地区概述保证金用户悬停被关闭亮点。
  • 文本编辑器的垂直滚动条在执行更新速度较慢。
  •  新的NET4 VisualScrollableAreaClip WPF的空气污染指数 是用来改善在编辑器线滚动。

此外,不论是否“启用视觉经验丰富的客户端”是开启或关闭,在文本编辑器中,相较于2010年快速滚动:

  • 推迟选定的视觉更新(例如概述保证金,并强调波形曲线,直到空闲)。
  • 减少在拇指在垂直滚动条(滚动时拇指移动迅速上涨或下跌跟踪布局数)。

资源:

以下是相关的有用博客: 

附加示例: 

下面(也附后)的代码示例显示如何能听取各种远程桌面事件和前面讨论过你可以使用这些事件来触发您的应用“的RDP”功能。 
在我的示例中,窗口的背景是包括了动画渐变。 应用程序检测到一个远程桌面会话的连接,然后切换窗口背景为纯色,反之亦然。  
如果我把动画背景,那么 整个窗口 是无效,就必须在电线上传输的 每个  显示器(如60帧,每最受秒)。 
随着动画删除并通过使用纯色,而不是 没有 网络流量是必要的。 
这里是我的网络流量测量时,我对自己的样品进行实验:

 

应用程序运行5秒(1280x1024有效)

当背景动画

当使用纯色背景  

交通量的RDP超过5秒:

11兆 

5个字节 

 

正如你可以看到这将使在低带宽情况下的巨大差异。

 

image

 图1 - 动画背景时,在本地运行

image

图2 - 固当通过远程桌面背景

 

 

   1: <Window x:Class="Animation_Sample_For_RDP.Window1"
   2:         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   3:         xmlns:x="http://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: }