下载本文示例代码
原文出处: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)一书的作者。你可以在
网站和 Paul
联系上。 |
本文出自
MSDN Magazine 的
March 2004 期刊,可通过当地
报摊获得,或者最好是 订阅 |
下载本文示例代码