Taxes: Remote Desktop Connection and painting - The Old New Thinghttps://devblogs.microsoft.com/oldnewthing/20060103-12/?p=32793
Raymond Chen 2006年01月03日
开发成本:远程桌面连接和绘制
当用户通过远程桌面连接进行连接时,视频操作会通过网络传输到客户端进行显示。由于网络的延迟较高,且带宽远远低于本地PCI或AGP总线,您需要适应屏幕绘制成本的变化。
如果您在屏幕上绘制一条线,那么“绘制线条”的命令会通过网络发送到客户端。如果您绘制文本,会发送一个“绘制文本”的命令(以及要绘制的文本)。到目前为止,一切都很好。
但是,如果您将一个位图复制到屏幕上,那么整个位图都需要通过网络传输。
让我们编写一个示例程序来说明这一点。从我们的新草稿程序开始,并进行以下更改:
void Window::Register()
{
WNDCLASS wc;
wc.style = CS_VREDRAW | CS_HREDRAW;
wc.lpfnWndProc = Window::s_WndProc;
...
}
class RootWindow : public Window
{
public:
virtual LPCTSTR ClassName() { return TEXT("Scratch"); }
static RootWindow *Create();
protected:
LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam);
LRESULT OnCreate();
void PaintContent(PAINTSTRUCT *pps);
void Draw(HDC hdc, PAINTSTRUCT *pps);
private:
HWND m_hwndChild;
};
void RootWindow::Draw(HDC hdc, PAINTSTRUCT *pps)
{
FillRect(hdc, &pps->rcPaint, (HBRUSH)(COLOR_WINDOW + 1));
RECT rc;
GetClientRect(m_hwnd, &rc);
for (int i = -10; i < 10; i++)
{
TextOut(hdc, 0, i * 15 + rc.bottom / 2, TEXT("Blah blah"), 9);
}
}
void RootWindow::PaintContent(PAINTSTRUCT *pps)
{
Draw(pps->hdc, pps);
}
LRESULT RootWindow::HandleMessage(
UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
...
case WM_ERASEBKGND: return 1;
...
}
}
这里有一个奇怪的分工;PaintContent
方法实际上并不执行任何绘制操作,而是将绘制任务委托给Draw
方法来完成。(您很快就会明白原因。)
确保启用了“拖动时显示窗口内容”的选项,然后运行这个程序并垂直调整其大小。
哎呀,多么难看的闪烁。
我们通过传统的双缓冲技术来修复这个问题。
void RootWindow::PaintContent(PAINTSTRUCT *pps)
{
if (!IsRectEmpty(&pps->rcPaint))
{
HDC hdc = CreateCompatibleDC(pps->hdc);
if (hdc)
{
int x = pps->rcPaint.left;
int y = pps->rcPaint.top;
int cx = pps->rcPaint.right - pps->rcPaint.left;
int cy = pps->rcPaint.bottom - pps->rcPaint.top;
HBITMAP hbm = CreateCompatibleBitmap(pps->hdc, cx, cy);
if (hbm)
{
HBITMAP hbmPrev = SelectBitmap(hdc, hbm);
SetWindowOrgEx(hdc, x, y, NULL);
Draw(hdc, pps);
BitBlt(pps->hdc, x, y, cx, cy, hdc, x, y, SRCCOPY);
SelectObject(hdc, hbmPrev);
DeleteObject(hbm);
}
DeleteDC(hdc);
}
}
}
我们的新PaintContent
方法创建了一个离屏位图,并请求Draw
方法将其绘制到其中。一旦完成,结果就会一次性地复制到屏幕上,从而避免了闪烁。
如果您运行这个程序,您会发现它在调整大小时非常平滑。
现在,通过远程桌面连接连接到计算机,然后再次运行它。
由于远程桌面连接禁用了“拖动时显示窗口内容”的选项,您不能通过调整大小来触发重绘,而是应该将程序最大化和恢复几次。
请注意,在您最大化窗口时,窗口调整大小之前会有一段较长的延迟。
这是因为我们在BitBlt
调用中,通过远程桌面连接传输了一个巨大的位图。
回到PaintContent
方法的旧版本,即只调用Draw
的那个版本,并通过远程桌面连接运行它。
啊,这个快多了。
这是因为简化的版本不会通过远程桌面连接传输一个巨大的位图;它只是发送了二十个TextOut
调用,这些调用涉及一个相当短的文本字符串。
这些调用所占用的带宽远少于一个1024×768的位图。
我们有一种方法在远程桌面连接上更快,另一种方法在本地运行时更快。
我们应该使用哪种方法?
我们两者都用,根据程序是否通过远程桌面连接运行来选择我们的绘图方法。
void RootWindow::PaintContent(PAINTSTRUCT *pps)
{
if (GetSystemMetrics(SM_REMOTESESSION))
{
Draw(pps->hdc, pps);
}
else if (!IsRectEmpty(&pps->rcPaint))
{
...如前所述...
}
}
现在我们得到了最佳效果。
当在本地运行时,我们使用双缓冲绘图,这样可以无闪烁地绘制,但当通过远程桌面连接运行时,我们使用简单的Draw
方法直接绘制到屏幕上,而不是绘制到离屏位图。
这是一个适应远程桌面连接的非常简单的例子。
在一个更复杂的世界里,您可能需要根据两种绘图风格的需要,处理更复杂的数据结构,或者您可能需要根据程序是否通过远程桌面连接运行来开启或关闭与绘图相关的后台活动。
由于用户可以动态地连接和断开连接,您不能仅仅假设程序启动时远程桌面连接的状态将一直保持不变。
下次我们将看到如何适应一个不断变化的环境。