C++ QA 专栏:列表视图模式,SetForegroundWindow 及类保护

王朝vc·作者佚名  2006-01-17
窄屏简体版  字體: |||超大  

C++ Q&A 专栏...

原著:Paul DiLascia

翻译:彭德奎

原文出处:MSDN Magazine Mar 2004(C++ Q&A)

原代码下载:CQA0403.exe (185KB)

1、列表视图模式

2、SetForegroundWindow

3、类保护

我试图使用C++/MFC自定义文件打开对话框。是否有一种办法能在打开/保存对话框启动时改变列表视图的类型?启动时默认的是列表视图,

这个视图没什么用。我希望程序启动对话框时采用详细资料视图,或最好是用户最后一次使用的视图。您能推荐一种方法吗?

Udi Mishan

当然,在 Windows 中总是有办法的。当我第一次看你的问题时,我想那很容易。只要在 WM_INITDIALOG

消息处理函数中获取列表视图,然后将其视图模式设置为详细资料即可。但在 Windows 中你经常会碰到逻辑上可行,实际做起来行不通。上述做法有三个问题。

问题一是获取列表视图。个别读者已经问到过这个问题,因为它显得有价值了。使用Microsoft

Spy++你可以发现,列表控制不是对话框的直接子类,它是孙子类。Spy++运行的屏幕截图 Figure

1 显示了文件打开对话框的真实的窗口层次。你可以看到,主对话框有一个子窗口,类的名字为 SHELLDLL_DefView。接着依次包括文件和文件夹的列表控制。(我第一次提到SHELLDLL_DefView 是在2002年2月专栏)SHELLDLL_DefView的 ID 是 lst2

(值为 0x0461, 在 dlgs.h 中定义),但它不是列表框或列表控制。真正的 SysListView32 是 SHELLDLL_DefView 的

孩子,子 ID 为 1。

Figure 1 窗口层次

问题二是当你的对话框获得 WM_INITDIALOG 时,结合列表控制/SHELLDLL_DefView的窗口还不存在。当你得到CDN_INITDONE时,它依然不存在

,尽管这个消息的意思是打开对话框已完成初始化。好了,实验是最好的证明:要想Windows做了什么,唯一的途径是做一个实验,或是阅读 MSDN 杂志。Figure 2

是我编写的用来说服自己确信列表控制并不存在的测试对话框。CMyOpenDlg 有一个函数叫 SetListView,顾名思义。此函数也显示 TRACE 诊断信息

,指示它能否找到列表控制。Figure 3 是 TRACE 流输出的结果,当 WM_INITDIALOG 或 CDN_INITDONE 到来时列表视图不存在。两种情况下 GetDlgItem 都返回 NULL。那么你该怎么做呢?最简单的

做法是让你的对话框给自己发个消息:

BOOL CMyOpenDlg::OnInitDialog()

{

CFileDialog::OnInitDialog();

PostMessage(MYWM_POSTINIT,0,0);

return TRUE;

}

Figure 3 Trace

MYWM_POSTINIT是我自己定义的消息(WM_USER+1),其处理函数调用 SetListView,因为 OnInitDialog 用

PostMessage 发送消息,而不是 SendMessage。Windows 一直到所有的其它未决的消息已经处理之后才会处理 MYWM_POSTINIT

消息。到那时,打开对话框已经设置妥当,而且 SetListView 成功地获得列表控制。

第三个问题,一旦你最后拥有了列表控制,你怎样设置它的视图模式?你可能想你所要做的只是调用SetView/LVM_SETVIEW。唉,这样做行不通。列表视图为空白。

难道是一个画面刷新问题吗?也许吧。但如果你仔细想一想,你将意识到发送 LVM_SETVIEW 充满危险。记得SHELLDLL_DefView窗口吗?不难想象它维持着

有关它的列表控制的某种状态信息。如果你直接操作列表控制,它怎样知道你做了什么?无论怎样,它都不工作。你必须另辟蹊径。

Figure 4 使用 Spy++

幸运的事,这种情况找到解决方法并不难。Spy++ 又一次帮上大忙。用 Spy++ 进行深入探究后,揭示出当用户操作下拉菜单,如 Figure

4 所示,选择不同的视图时会发生什么。对话框发送了一个 WM_COMMAND 消息给 SHELLDLL_DefView,命令ID= 0x702c

。所以为了将视图改为详细资料视图,你要做的是发送一个 WM_COMMAND 消息到外壳窗口,而不是列表控制:

CWnd* pshellwnd = dlg->GetDlgItem(lst2);

pshellwnd-SendMessage(WM_COMMAND, ODM_VIEW_DETAIL); // 0x702c

经历了所有的惊愕以及重重艰难险阻,最终的解决方案很简单。你可以用 Spy++ 来检验每个视图模式的命令代码。我是个大好人,为你做了这些。结果参见 Figure 5 。我在 Windows

XP 中对它们进行了调用;Windows 的其它版本有一或两个别的代码。Figure 2 是 CMyOpenDlg 的源码,它以详细资料视图模式调用文件打开对话框。

至于如何保存不同用户会话的列表模式,我将它作为一个练习由你自己解决;实现细节很简单。如通常一样,你可以从本文顶部的链接处下载完整的测试程序源代码。

我想在 .NET 框架中用 C# 编写一个程序,该程序要激活另一个窗口。在 Windows/MFC

中我可以调用 SetActiveWindow 函数来实现。在.NET 框架中我该怎么做呢?

John McCormick

你可以调用 Form.Activate 来激活你自己的窗体,但惊奇的是,在 .NET

Framework 中没有函数可以激活属于另外一个进程或程序的窗体。不要害怕,任何时候,只要.NET

Framework无法满足你的需要,你通常都可以使用托管(interop)机制直接与Windows 交互。目前情况下,你需要的函数是 SetForegroundWindow。它带唯一的参数——你想激活的窗

口的句柄(HWND).

using System.Runtime.InteropServices;

public class MyClass {

[DllImport("user32.dll")]

public static extern void

SetForegroundWindow(IntPtr hwnd);

}

在你的代码中使用此托管申明,并且假设你已经拥有了你希望激活的窗口的句柄,你要做的是调用 SetForegroundWindow:

IntPtr hwnd = // get HWND

SetForegroundWindow(hwnd);

你怎样获得窗口句柄呢?根据你的程序的工作方式,有许多方法可以做到,但最通用的一种方法是调用 FindWindow,你可以用这个 API

函数由窗口的标题或类名获得窗口句柄,在此你又要在 C# 中用到托管:

public class MyClass {

[DllImport("user32.dll")]

public static extern IntPtr

FindWindow(String classname, String title);

}

classname 是 Window 注册的窗口类的名字,title 是窗口标题。这些参数只能有一个为 NULL,不能全为 NULL。

我怎样才能在编译时阻止其它的类从我的 C++ 类派生? 例如,我有一个类:

class MyClass { };

如果有人试图像这样申明一个类class Derived : public MyClass { };

我希望此时编译器抛出一个错误,可以实现吗?

Asha Udupa

在C#中,有一个关键字正好是你想要的: sealed。当你的C#类被限定为 sealed

类型,相当于你告诉编译器无人能从这个类派生其它类。 例如,

sealed class MyClass { ... }

意思是说没有人能从 MyClass 派生出别的类。在.NET Framework中许多(一些人说太多)类自身是密封的。

但你的问题的是 C++,而不是C#。啊哈,C++中可没有 sealed 这个关键字(至少现在还没有——我得到消息它不久将被加入到官方标准中)。但有一个相当简单的方法完成同一个目的

。只要你把构造函数申明为 private 即可:

class MyClass {

private:

MyClass() { ... }

MyClass(int arg) { ... }

};

这样便没有办法从 MyClass 派生新类。因为没法实例化它。等一等——如果没法实例化你的类。那别人如何使用它呢?问得好。答案是你必须添加静态函数

来创建类的实例。

class MyClass {

public:

static MyClass* CreateInstance() {

return new MyClass();

}

private:

MyClass() { }

};

现在任何人可以调用 MyClass::CreateInstance 来创建你的类的实例,但没有人能从它派生。这种方法在大多情况下工作得很好,可它有一个

缺点:你很难在堆栈中创建 MyClass 实例。要解决这个问题,你需要一个稍微复杂的解决方案:

class MakeSealed {

private:

MakeSealed () { }

friend class MyClass;

};

class MyClass : virtual MakeSealed { };

现在除了 MakeSealed 的友元类 MyClass 以外,无人能创建 MakeSealed 实例。你能在堆栈中创建 MyClass 的实例

,但你不能从MyClass 派生。你可以使用 MakeSealed 来使其它成为密封类,但同时必须添加它们为友元。MyClass 以 MakeSealed 为虚拟基类

,以便于你在使用多重继承时不会出现问题。相当聪明,不是们吗?编程快乐!

使用 cppqa@microsoft.com 发送你的问题和评论给 Paul

作者简介

Paul DiLascia 是一个自由作家,顾问和 Web/UI 方面资深的设计师。他是 Windows++: Writing Reusable

Windows Code in C++ (Addison-Wesley, 1992)一书的作者。你可以在

http://www.dilascia.com 网站和 Paul

联系上。

本文出自

MSDN Magazine

March 2004 期刊,可通过当地

报摊获得,或者最好是 订阅

本文由 VCKBASE MTT 翻译

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
 
 
© 2005- 王朝網路 版權所有 導航