Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1829213
  • 博文数量: 950
  • 博客积分: 10000
  • 博客等级: 上将
  • 技术积分: 13070
  • 用 户 组: 普通用户
  • 注册时间: 2008-08-04 09:23
文章分类

全部博文(950)

文章存档

2011年(1)

2008年(949)

我的朋友
C Q

分类: C/C++

2008-08-04 09:32:34

下载本文示例代码

原文出处: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 期刊,可通过当地 报摊获得,或者最好是 订阅 下载本文示例代码
阅读(242) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~