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

顏色支持,控制台應用

編輯:關於VC++

顏色支持

控制台應用

保存用戶設置

我讀了你在 2001年7月 的專欄文章“怎樣創建256色工具條”(編者注:中文譯文參見在線雜志第11期)。然而我聽說,除非你的IE浏覽器為3.0或以後的版本,否則,Windows95 不支持256色工具條,在這種情況下,應用程序將使用一個默認的16位色工具條。我怎樣能夠判斷一個256位色或更高顏色位的工具條是否被支持?

Bayland Park

我喜歡這樣——現在是2004年了,我仍能得到關於 Windows95 的問題。有一種簡單的方法可以知道當前屏幕或窗口支持多少位顏色, 只要獲取窗口或屏幕設備上下文的句柄(HDC),然後調用 GetDeviceCaps (得到設備性能),就可以得到顏色位面(PLANS)和每個象素所含的比特位(BITSPIXEL),兩者相乘就可以得到在某個象素中總的比特位數。CWindowDC dc(NULL);
int nPlanes = dc.GetDeviceCaps(PLANES);
int nBitsPixel = dc.GetDeviceCaps(BITSPIXEL);
int totalBits = nPlanes * nBitsPixel;

通常,對於一個256色系統或者16位、24位或32位的真彩系統來說,其顏色位面是1,每個象素的比特位數是8。GetDeviceCaps 能夠得到許多有用的信息,像邏輯英寸象素的個數、屏幕高寬比率、光柵性能和其它一些信息。我寫過一個小程序ColorBits (如 Figure 1 所示)。你 可從本文開始處的鏈接下載這個程序的源代碼。

Figure 1 ColorBits

我見到過一些即可以作為控制台應用程序運行,也可以作為基於Windows的應用程序運行的程序。即:如果你在命令提示符鍵入程序的名字,它就會作為一個普通的基於 Windows 的應用程序運行;如果你輸入一個命令行選項,如:“-batch”,它就會以批處理模式作為一個控制台應用程序運行,同時所有輸出被定向到控制台。請問這個功能是怎麼做的?

Joe Tadmann

我沒法告訴你其它的應用程序是怎麼這樣做的,因為在 Window 中,總是有多種方法來給操作系統加殼,但是我可以向你展示實現這種功能的一種途徑---通過 Visual Studio.NET 來實現。也許你沒有注意到,你可以在命令提示符鍵入 devenv 來啟動 Visual Studio .NET,這時,Windows 啟動 其圖形用戶界面。但是如果你鍵入 devenv 的同時再加上一個命令行開關,例如,-? 表示幫助,-bulid 表示編譯並生成你的工程,它將以控制台模式運行,沒有用戶交互界面。例如: devenv -build MyProject.sln   

上面這行命令生成解決方案文件 MyProject.sln。

遺憾的是,自從GUI(圖形用戶界面)出現以後,太多的程序員已經忘了命令行的強大功能,這些強大的命令行功能使用戶能以批處理模式從腳本中運行你的應用程序。你可以 確信微軟的那幫家伙是不會在 Visual Studio 中打開某個工程來生成他們的產品的!如果你寫了一個確實能實現某些功能的程序,比如,把.wav文件轉換成.mp3,或者通過計算預測股票, 這些功能適合用批處理接口來實現。如果你的程序沒有人守著就不能成批壓縮收藏的音樂樂曲或分析前一天的股票信息,那你的程序有什麼好的呢?

好,現在言歸正傳,你怎麼能夠實現一個組合了用戶界面/控制台的應用呢?現在幾乎所有的 Windows 程序員都知道,Windows 把 EXE(可執行程序)分成兩 大類:控制台應用和 GUI應用。這種體系結構可以追溯到 Windows 的早期,當時它首先是從 MS_DOS 中發展而來的。如今,你只要給鏈接 器一個開關:/subsystem:Windows 或者 /subsystem:console,你就能生成你想要的那種應用程序。

所以,如果你要建立一組合應用,首要問題是:它是一個控制台應用還是一個GUI應用?最初,你可能認為要建立一個控制台應用,以後它還能夠作為一個 GUI 程序運行。從來沒有誰說過一個控制台應用就不能夠創建窗口或處理消息,控制台之所以是控制台,那是因為當控制台不存在時, Windows 會為控制台應用創建一個控制台。但是這裡有一個問題,如果你在 Windows 下通過在其資源管理器(Explorer)中雙擊或快捷方式運行某個控制台應用程序,Windows 將會創建一個控制台,你可以通過調用 FreeConsole 來銷毀 這個控制台,但是這個控制台窗口會暫時一閃而過,告訴整個世界你其實並不知道你自己做了什麼。

然後,要使它為一個GUI應用。那麼你如何把它寫到控制台呢?有許多文章解釋了怎樣重新路由 printf 或 cout 到控制台,但是, 它們都涉及到創建新控制台窗口的問題,不是使用一個當程序是從命令行被啟動時已經存在的窗口。即使有某個使用現有控制台的途徑,那你又怎麼知道你的應用程序是通過 Windows Explorer 還是通過命令提示符調用的呢?

有一個新的函數( Windows XP 使用的)正好可以利用:這個函數是 AttachConsole。它允許你將程序“綁定”到其它進程的控制台窗口,如果你用了專用的進程 ID:ATTACH_PARENT_CONSOLE,AttachConsole 將綁定到啟動你的程序的控制台。太好了,但是有兩個問題,第一,AttachConsole 只能在 Windows XP 系統中使用,所以如果你想要你的程序運行在其它版本的 Windows 中,就沒那麼走運了;第二,AttachConsole 工作並不穩定,你可以寫內容到控制台,但是你的程序退出後,命令提示符就亂七八糟了。

簡而言之,基於 Windows 的應用程序要麼是控制台應用,要麼是GUI應用,二者不可兼得。(除非你想寫你自己的啟動代碼,那已非我力所能及)。但是你知道它 是能夠做到的,因為我已經告訴你 Visual Studio 能行,到底怎樣做呢?

如果你看一下 Visual Studio 的安裝目錄,你會發現實際上有兩個程序:devenv.exe和 devenv.com。還記得.com 是什麼嗎? 它可不是 Web,而是可執行程序,在很久很久以前,當你還是小孩子的時候,基於 Windows 的程序有三種內存模式:大內存模式(large)、小內存 模式(small)和巨大內存模式(huge)。其它的模式都被叫做緊湊模式或微小模式,它們產生不同類型的可執行文件,這些可執行文件都以 .com 作為擴展名。(.com文件是一種在加載時不需要固定地址的直接內存映 象,這樣使用起來非常的快,但它們必須很小。) 現在內存模式已經沒有這麼多了,大部分可執行文件都使用PE格式。但是命令解釋器仍然能識別 .com 可執行文件,並且你可以將任何.exe 程序重新 更名為 .com 程序,如把 foo.exe 改成 foo.com,它仍然可以通過輸入名字執行。所以可以用這個技巧去創建兩個程序:foo.com 和 foo.exe。一個是 控制台應用程序,另一個是基於 Windows 應用程序。

Figure 2 在對話框裡顯示進程列表

為了示范它的工作原理,我修改了我在2002年7月專欄文章裡的程序lp(列舉進程)(編者注:中文譯文參見在線雜志第14期文章——“如何獲取某個進程的主窗口以及創建進程的程序名?”),它既可作為 GUI 程序運行,也可作為控制台程序運行。如果你輸入 ListProc 而不用參數,它會在對話框中列出進程,如 Figure 2 所示。如果你鍵入 ListProc -c,它會以控制台模式運行並列出進程,如 Figure 3 所示。ListProc 有兩個主程序文件:ListProc.cpp 是通常的 MFC 應用實現,ListProc-cons.cpp 是控制台應用實現。這兩個程序都調用相同的模塊—— EnumProc,實際的進程列表正是由它產生的。ListProc-cons 處理命令行並顯示控制台信息,沒有用命令行參數的程序通過調用 ShellExecute 啟動程序的 GUI 版本。

// 將 myself.com 改為 myself.exe 並運行
TCHAR lpExeName[_MAX_FNAME];
GetModuleFileName(NULL, lpExeName, _MAX_FNAME);
LPTSTR ext = lpExeName + _tcslen(lpExeName) - 3;
_tcscpy(ext,_T("exe"));
ShellExecute(NULL, _T("open"),
 lpExeName, NULL, NULL, SW_SHOWNORMAL);

Figure 3 在控制台的進程列表

Figure 4 是 ListProc-cons 的全部代碼,Visual Studio 的解決方案包含兩個工程:ListProc和 ListProc-cons。後者有一個定制編譯步驟,是重命名輸出文件 ListProc-cons.exe 為ListProc.com (參見 Figure 5)。當你安裝程序時,要保證把 .com 和 .exe 都放在了相同的目錄下,並且確保你創建的任何快捷方式都指向 .exe 文件。那樣,從 Windows 調用會直接運行.exe 文件,而從控制台調用則運行 .com 文件(如果 .com 和 .exe 都存在於用戶的路徑下, Windows 首選 .com 文件運行)。明白了嗎?

Figure 5 將 .exe 文件重命名為 .com

我正在用 C# 在 Microsoft .NET Framework 和 Windows Forms 下創建一個基於 Windows 的應用。我正嘗試記下窗口的位置,使它每次打開時都能記起它前一次的位置。在.NET Framework 中有沒有特殊的方法可用?我可以用配置文件嗎?

Frank Jacobs

.NET Framework 支持配置文件的概念,它是用 XML 文件來保存應用程序配置信息的,但是這並不是你真正想 要的答案。配置文件是給管理員用來設置你的應用程序的,而不是給用戶保存設置的。為此,你要麼用注冊表、ini文件,要麼自己定制一個數據文件。注冊表不是一個好的選擇 ,因為它難於編輯同時也不容易拷貝。.Net編程明確的目標之一就是 XCOPY 部署,也就是說你可以通過拷貝文件把你的應用直接從 A 處移動到 B 處。所以我建議你使用 INI 文件或者其它數據文件。

如果你要存儲復雜的數據結構,像鏈表、哈希表或你自己私有的數據結構,你最好用序列化(二進制文件或XML文件都可以)來讀寫你專用的文件,如:MyApp.dat。在 .NET Framework 中許多公共類已經可序列化了,並且只要你按照下面的格式聲明你自己的類,也可能很容易地使你自己的類可序列化: [Serializable]
public class MyClass {
 public int myInt;
 public String myStr;
}

對於保存象窗口位置這樣簡單的數據,INI文件是一個比較好的選擇。它簡單明了、可讀性強、容易拷貝並且可以用任何 ASCII 文本編輯器輕松編輯。可是,在 .NET Framework 中還沒有 現成的函數可以直接操作 INI 文件,所以我寫了一個類 IniFile 對這些功能進行了封裝,同時還寫了一個測試程序 SavePos,它使用 IniFile 類來記錄主窗口位置。SavePos 是一個典型的 Windows 窗體應用程序。由主框架類創建一個 IniFile 實例,就像下面這樣:

using Ini;
public class Form1 : System.Windows.Forms.Form
{
 private IniFile iniFile = new IniFile("SavePos.ini",true);
...
}

SavePos.ini 是一個文件名;稍後我將介紹構造函數的第二參數。在窗體被首次被創建,時,還沒有顯示的時候,Form 構造函數加載窗口位置 數據。如:

// in Form1 class
public Form1()
{
 ...
 iniFile.RestoreWinPos(this,"MainWindow");
}

最後,當窗體被銷毀時,Form1 保存窗口位置,代碼如下:

// in Form1 class
protected override void Dispose( bool disposing )
{
 if( disposing ) {
  // save window pos
  iniFile.SaveWinPos(this,"MainWindow");
  ...
}

下面所顯示的代碼是 INI 文件的結果:

[MainWindow]
X=282
Y=442
Width=417
Height=234

如你所見, IniFile 提供了兩個函數,SaveWinPos 和 RestoreWinPos.表示保存/恢復窗口位置。Figure 6 是其實現代碼,非常簡單。保存/恢復函數調用了更多的原始函數,如 GetIntVal 和SetVal 來讀/寫鍵/值對。然後這些函數又用托管服務 (interop services )調用 Win32 API 函數 GetPrivateProfileInt 和 WritePrivateProfileString, 從而完成實際的對 INI 文件的讀/寫。唯一的技巧是當你恢復窗口位置時,如果你想要你的窗體(Form)關注它的位置(Form.Location)(關於這方面更多信息, 參見 2003年4月的專欄文章)。你必須記住用下面這的一行代碼:

form.StartPosition = FormStartPosition.Manual;

機敏的讀者會想起我還欠一個關於對 INI 文件構造函數第二個參數的解釋。Win32 的Get/WriteProfileXxx 都有一個參數是一個 INI 文件名。如果你傳一個相對文件名, 如“foo.ini”,Windows 便在 WINDOWS 目錄下進行查找。如果你給一下絕對路徑名,它便使用這個絕對路徑。你可能要基於每個用戶來保存窗口 的位置(Jans 應該得到她最後一次使用程序的窗口位置,而不 Fred 的),你也許想把 INI 文件放到用於你的程序的用戶應用數據文件夾裡。這就是第二個參數 useAppDataPath 的作用,如果你給它賦值為 true,IniFile 就會在用戶應用數據文件夾裡找 INI 文件。

IniFile 是怎麼做到的呢?這個 Application 類做這這種事情很簡單:Application.UserAppDataPath 保存著用戶數據文件夾的路徑名。這是一個其長無比的路徑名,如:\Base\[CompanyName]\[ProductName]\[ProductVersion],其中 Base 就像C:\Documents and Settings\[username]\Application Data。任何時候其版本管理都是免為其難的, 公共語言運行時添加了一個產品版本號到這個路徑中,從你的視角看,它既是一個特性,同時也是一個災難:如果你在版本號中使用星號(例如:1.0.*)以便讓框架每次編譯都創建一個新的版本號,很多程序員這麼做,那麼你會最終會有上百個文件夾——每次重編譯或運行時都會產生一個新的文件夾。也許微軟的那幫家伙打算從事磁盤生意,你可以從版本號中刪除星號,或者是刪除路徑名中的版本部分,仰或將文件放到別處。和往常一樣,你總是可以從本文頂部的鏈接處得到全部源代碼。

更新

在我2003年11月的專欄文章裡,我談到了怎樣通過枚舉窗口以到彈出菜單的HWND(窗口句柄)以及用特定的類名“#32768”查找窗口。讀者 Jim White 提出下列技巧:如果你用NULL作為窗口句柄調用 GetMenuItemRect,Windows 會返回一個彈出式窗口的 HWND---神奇吧!!這也 恰好證明了即使像我這樣的所謂專家每天也能學到許多東西。Jim 還指出: GetMenuItemRect 的這個技巧應用在“在 Windows 2000 和 Windows XP上很風光,但是在 Windows 98 上就歇菜了,盡管 MSDN上堅持說可以”。

本文配套源碼

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