程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> 關於VC++ >> SetForegroundWindow及類保護

SetForegroundWindow及類保護

編輯:關於VC++

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 為虛擬基類 ,以便於你在使用多重繼承時不會出 現問題。相當聰明,不是們嗎?編程快樂!

本文配套源碼

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved