【摘】Windows系統下的多顯示器模式開發日記

來源:互聯網
上載者:User
  這幾天研究了一下Windows系統的多顯示器模式的編程,實現了Windows下支援10顯示器模式的通用com組件,這裡做一個整理和回顧,希望能對再這方面開發的兄弟們有些啟發和協助:

 (一) Windows系統下的多顯示器模式的原理     Microsoft新的作業系統(Windows 98/Windows 2000/Windows XP)內建了對多監視器的支援,即使用者可以在一台電腦上安裝多個顯示卡並接上多個顯示器,然後把這些顯示器的顯示地區組織成一個大的虛擬Windows案頭。每一個顯示地區的底部都有系統工作列,我們可以在任何一個顯示地區內增加案頭捷徑,這樣就可以在第一個顯示地區上用Visual C++編程,同時在第二個顯示地區上開啟Internet Explorer上網——再也不用進行麻煩的切換了。   多顯示器模式的原理實際上很簡單,主要還是要靠作業系統的支援,比如WinXP就支援10個顯示器,本文所使用的調試和開發環境都是以WinXP為主,其餘的原理都相同慢慢調試就行了. Windows提供的多顯示器模式主要有以下三個功能:

  1.更大的Windows案頭:在多顯示器模式下,可以把多個顯示器的顯示地區結合在一起來顯示Windows案頭,不管這些顯示器的尺寸、物理位置、解析度和重新整理頻率是否相同。當我們運行一個應用程式時,程式的主視窗可以位於任何一個顯示器的顯示地區內,也可以跨多個顯示地區。我們也可以把一個程式的視窗從一個顯示地區移到另一個顯示地區中。  2. 螢幕複製或遠程顯示:我們可以讓兩個顯示器顯示相同的內容。在進行培訓或者向眾人進行示範時,這個特點是很有用的。利用這個特性,技術支援人員還可以對應用程式進行遠程監視和調試。  3.多重獨立顯示:在以上的兩種模式下,所有的顯示地區都是Windows虛擬桌面的一部分,但是在多重獨立顯示模式下,應用程式訪問的顯示器並不屬於Windows虛擬桌面。假設系統的第二個顯示器是一個高解析度的大尺寸顯示器,我們可以把它用做CAD應用程式的專用顯示。通過在CAD應用程式中調用新的Windows API,我們可以藉助GDI在上面畫圖。獨立顯示器的顯示地區沒有案頭上的任何對象(工作列和捷徑),它與Windows案頭是獨立的。這可以避免Windows案頭對應用程式輸出的任何幹擾,我們也不用擔心會在無意中把其它的視窗拽到獨立顯示的顯示地區中,這種方式就好像為應用程式提供了一個專用的顯示器。

(二)理解虛擬桌面(Virtual Desktop)及其座標 既然是要對多顯示器模式進行編程和開發,那麼我們就要首先理解Windows的虛擬桌面(Virtual Desktop)及其座標了.這是我們編程開發的基礎,理解了一切就很順利了,幾乎沒有什麼難度.  在單顯示器系統中,實際Windows案頭的形狀和大小與顯示器是相同的。在多顯示器模式下,每一個顯示器實際上是一個大虛擬桌面的一個“子視窗”。  我們可以通過控制台中的顯示器屬性對每一個顯示器的顯示地區的大小(解析度)和相對位置進行調整,所有這些顯示地區互相串連但並不重疊。圖一中的顯示器1是主顯示器,主顯示器的作用是確定虛擬桌面的座標。不管主顯示器的位置如何,它的顯示地區的左上方的座標定為虛擬座標的零點(0,0),右下角的座標是(X-1,Y-1)(假設主顯示器的解析度為X×Y),其餘顯示地區的座標由它和主顯示器的相對位置決定。通常虛擬桌面中顯示地區的相對位置和實際顯示器的物理相對位置是相同的。因為所有顯示地區必須相連,因此可以用一個包含所有顯示地區的最小矩形來表示虛擬桌面的大小。圖一中的矩形邊界代表了虛擬桌面的範圍。  因為虛擬桌面中的座標系統必須是連續的,因此第二個顯示地區的座標是主顯示器的顯示地區的繼續。假設兩個顯示器都使用1024×768的解析度,並且第二個顯示器位於第一個顯示器(主顯示器)的正右方,則第二個顯示地區的座標是從(1024,0)到(2047,767)。  但是並不是所有的顯示地區都具有相同的解析度,而且這些顯示地區也不一定是底邊對齊的。就像圖一中顯示的那樣,你真正能看到的有效顯示地區是紅色+蘭色+紫色的不規則地區,而黃色地區雖然也屬於虛擬桌面的一部分,但它不屬於任何一個顯示地區,這部分也叫做無效地區。一中所示,假設顯示器1的解析度是1024×768,顯示器2的解析度為800×600,顯示器3的解析度為640×480。零點的位置中所示,顯示器1的座標為(0,0)到(1023,767),顯示器2的座標為(-800,168)到(-1,767),顯示器3的座標是(1024,0)到(1663,479)。而(-800,0)到(-1,167)以及(1024,480)到(1663,767)這兩塊無效地區是不能顯示任何資訊的,系統不會允許使用者把滑鼠移動到這兩個地區。需要注意的是無效地區是包括在虛擬桌面中的,因此圖一中的虛擬桌面的大小是從(-800,0)到(1663,767)。    我在編程開發的過程中就使用了2個顯示器,一個是自己的筆記本,解析度為1024×768作為主顯示器,另外一個由於比較懶,直接找了一個小巧的NEC12寸螢幕的小黑白顯示器,不是為了別的搬著方便啊,這個NEC黑白支援解析度800×600,強吧.如我是直接設定了延伸桌面,兩個顯示器就都可以使用了在這裡要注意主顯示器和副顯示器的區別,其實主顯示器和副顯示器你是可以進行任意調整的.(三)系統支援編程開發的API  Microsoft為支援多顯示器模式提供了一些新的API調用,下面具體介紹它們的功能:  1.HMONITOR MonitorFromPoint(POINT pt,DWORD dwFlags)  MonitorFromPoint返回包含特定點(pt)的一個顯示器控制代碼。如果pt不屬於任何一個顯示器,返回的顯示器控制代碼由dwFlags標誌決定:MONITOR_DEFAULTTONULL時返回NULL,MONITOR_DEFAULTTOPRIMARY時返回代表主顯示器的HMONITOR控制代碼,MONITOR_DEFAULTTONEAREST時返回最靠近pt點的顯示器的HMONITOR控制代碼。 2.HMONITOR MonitorFromRect(LPCRECT lprc,DWORD dwFlags)  MonitorFromRect返回包含lprc代表的矩形的顯示器控制代碼;如果包含此矩形的顯示地區不止一個,則返回包含矩形最大部分的顯示器控制代碼;如果矩形不屬於任何一個顯示地區,返回的控制代碼由dwFlags決定,規則與MonitorFromPoint相同。  3. HMONITOR MonitorFromWindow(HWND hwnd,DWORD dwFlags)  與MonitorFromRect類似,但輸入是一個代表視窗的控制代碼hwnd而不是指向矩形的指標。  4. BOOL GetMonitorInfo(HMONITOR hMonitor,LPMONITORINFO lpmi)  GetMonitorInfo返回由hMonitor代表的顯示器的有關資訊,這些資訊儲存在指向MONITORINFO結構的指標——lpmi中。這些資訊包括用RECT結構表示的顯示器的顯示地區的大小(如果這個顯示器不是主顯示器,RECT的座標可能為負數),以及用RECT結構表示的顯示器的工作區域的大小,工作區域是顯示地區中除去系統工作列和應用程式捷徑欄所剩下的地區,還能夠判斷此顯示器是否為主顯示器,並返回一個標誌。  5.BOOL EnumDisplayMonitors(HDC hdc,LPCRECT lprcClip,MONITORENUMPROC lpfnEnum,LPARAM dwData)  hdc是一個代表顯示裝置環境的控制代碼,lprcClip是指向一個矩形地區的指標。把這個矩形地區和裝置環境中的可見地區取交集,得到的地區可能分布在多個顯示器的顯示地區中,EnumDisplayMonitors對每一個包含交集的顯示地區調用一次MonitorEnumProc類型的函數。DwData為傳遞給MonitorEnumProc函數的資料。  6.BOOL CALLBACK MonitorEnumProc(HMONITOR hmonitor,HDC hdcMonitor,LPRC lprcMonitor, DWORD dwData)  MonitorEnumProc是一個被EnumDisplayMonitors函數調用的回呼函數,它的內容可以由使用者自訂。利用這兩個函數,使用者在進行跨多個顯示器的顯示時就可以利用每一個顯示器的不同的顯示特性。  當然,並不是所有畫圖程式都必須調用這兩個函數,這時你假設所有的顯示器都使用同樣顏色的解析度。  7.EnumDisplayDevices(LPVOID lpReserved,int iDeviceNum,DISPLAY_DEVICE×pDisplayDevice,DWORD dwFlags)  EnumDisplayDevices列出系統中某個顯示裝置(以iDeviceNum為序號)的資訊。與GetMonitorInfo相比,GetMonitorInfo對應的顯示器必須是Windows虛擬桌面的一部分,而EnumDisplayDevices可以列出包括處於獨立顯示模式下的系統所安裝的所有顯示器的資訊。它返回的資訊儲存在DISPLAY_DEVICE結構中,包括顯示裝置名稱、對顯示裝置的描述和顯示裝置的狀態。  此外,一些原有的API調用如SystemParametersInfo和GetSystemMetrics也加入了對多顯示器模式的支援。比如調用GetSystemMetrics時,如果用SM_XVIRTUALSCREEN、SM_YVIRTUALSCREEN、SM_CXVIRTUALSCREEN和SM_CYVIRTUALSCREEN,得到的是虛擬桌面左上方的座標和整個的長度和寬度。  我們在編程時特別要注意座標的變化:首先單顯示器下負座標或大於SM_CXSCREEN和SM_CYSCREEN部分的視窗將被隱藏,而在多顯示器模式下這些都是合法的。其次在確定應用程式視窗和對話方塊的位置時,要選擇正確的顯示器和正確的全域座標(虛擬桌面座標)。最後,在恢複原來儲存的視窗之前,要檢查一下這些視窗座標的有效性。     這些都可以在微軟的MSDN上去查出來,需要仔細的看一看,每個API都親自試一試.    大家可以參考MSND的一篇文章"How to Exploit Multiple Monitor Support in Memphis and Windows NT 5.0",說的很詳細. 

(四)實現多螢幕編程的組件設計 這個組件參考了網上的許多資料,這裡先向那些無私的同行表示感謝,我做的工作只是將他們的成果進行了系統化的整理...... 組件的設計流程如下: (1).初始化程式Syntax:: MScreenInfo();Description : 組件建構函式,初始化組件,擷取系統螢幕資訊,設定組件屬性。   (2). 擷取指定螢幕的寬度Syntax: Short GetScreenWidth( Short ScreenNo) ;Input : ScreenNo -- 指定螢幕的序號,0 -- m_monitorNum-1;Return: Screen Width in Pixel;Decription: 擷取ScreenNo指定螢幕的寬度。   (3). 擷取指定螢幕的高度Syntax: Short GetScreenHeight( Short ScreenNo) ;Input : ScreenNo -- 指定螢幕的序號,0 -- m_monitorNum-1;Return: Screen Height in Pixel;Decription: 擷取ScreenNo指定螢幕的高度。程式流程圖:與圖2相同,只是最後一步返回dm.dmPelsHeight. (4). 擷取指定螢幕的座標原點-leftSyntax: Short GetScreenLeft( Short ScreenNo) ;Input : ScreenNo -- 指定螢幕的序號,0 -- m_monitorNum-1;Return: Screen Left in Pixel;Decription: 擷取ScreenNo指定螢幕的座標原點-left。程式流程圖:與圖2相同,只是最後一步返回dm.dmPosition.x. (5). 擷取指定螢幕的座標原點-topSyntax: Short GetScreenLeft( Short ScreenNo) ;Input : ScreenNo -- 指定螢幕的序號,0 -- m_monitorNum-1;Return: Screen Top in Pixel;Decription: 擷取ScreenNo指定螢幕的座標原點-top。程式流程圖:與圖2相同,只是最後一步返回dm.dmPosition.y. (6). 擷取主畫面--Primary ScreenSyntax: Short GetPrimaryScreen();Input: Null;Return: Primary Screen No, 0 -- m_monitorNum - 1Description: 擷取主畫面的序號。程式流程:依次判斷那一個螢幕的原點是(0, 0).  (五)組件開發的實現和主要代碼 1 開發環境 作業系統: WindowsXP   編程環境: VC 6.0 2 組件介面如下  3 主要代碼// 獲得顯示器的數量CMScreenInfoCtrl::CMScreenInfoCtrl()
{
 InitializeIIDs(&IID_DMScreenInfo, &IID_DMScreenInfoEvents); // 找出顯示器的總數量
 int  i;
 BOOL flag;
 DISPLAY_DEVICE dd; i = 0;
 flag = true;
    ZeroMemory(&dd, sizeof(dd));
    dd.cb = sizeof(dd);
 do
 {
  flag = EnumDisplayDevices(NULL, i, &dd, 0);
  if (flag) i += 1;
 } while (flag); m_monitorNum = i;  // 總數量
}// 獲得顯示區寬度short CMScreenInfoCtrl::GetScreenWidth(short ScreenNo)
{
 if (ScreenNo < 0 || ScreenNo >= m_monitorNum) return 0; BOOL flag;
 DISPLAY_DEVICE dd;    ZeroMemory(&dd, sizeof(dd));
    dd.cb = sizeof(dd);
 flag = EnumDisplayDevices(NULL, ScreenNo, &dd, 0); if (!flag) return 0; DEVMODE dm;
 ZeroMemory(&dm, sizeof(dm));
 dm.dmSize = sizeof(dm);
 flag = EnumDisplaySettings((char*)dd.DeviceName,ENUM_CURRENT_SETTINGS, &dm); if (!flag) return 0; return (short) dm.dmPelsWidth;
}// 設定顯示區寬度void CMScreenInfoCtrl::SetScreenWidth(short ScreenNo, short nNewValue)
{
 SetModifiedFlag();
}// 獲得顯示區寬度short CMScreenInfoCtrl::GetScreenHeight(short ScreenNo)
{
 if (ScreenNo < 0 || ScreenNo >= m_monitorNum) return 0; BOOL flag;
 DISPLAY_DEVICE dd;    ZeroMemory(&dd, sizeof(dd));
    dd.cb = sizeof(dd);
 flag = EnumDisplayDevices(NULL, ScreenNo, &dd, 0); if (!flag) return 0; DEVMODE dm;
 ZeroMemory(&dm, sizeof(dm));
 dm.dmSize = sizeof(dm);
 flag = EnumDisplaySettings((char*)dd.DeviceName,ENUM_CURRENT_SETTINGS, &dm); if (!flag) return 0; return (short) dm.dmPelsHeight;
}// 設定顯示區高度void CMScreenInfoCtrl::SetScreenHeight(short ScreenNo, short nNewValue)
{
 SetModifiedFlag();
}// 獲得顯示區Y座標short CMScreenInfoCtrl::GetScreenTop(short ScreenNo)
{
 if (ScreenNo < 0 || ScreenNo >= m_monitorNum) return -1; BOOL flag;
 DISPLAY_DEVICE dd;    ZeroMemory(&dd, sizeof(dd));
    dd.cb = sizeof(dd);
 flag = EnumDisplayDevices(NULL, ScreenNo, &dd, 0); if (!flag) return -1; DEVMODE dm;
 ZeroMemory(&dm, sizeof(dm));
 dm.dmSize = sizeof(dm);
 flag = EnumDisplaySettings((char*)dd.DeviceName,ENUM_CURRENT_SETTINGS, &dm); if (!flag) return -1; return (short) dm.dmPosition.y ;
}// 設定顯示區Y座標void CMScreenInfoCtrl::SetScreenTop(short ScreenNo, short nNewValue)
{
 SetModifiedFlag();
}// 獲得顯示區X座標short CMScreenInfoCtrl::GetScreenLeft(short ScreenNo)
{
 if (ScreenNo < 0 || ScreenNo >= m_monitorNum) return -1; BOOL flag;
 DISPLAY_DEVICE dd;    ZeroMemory(&dd, sizeof(dd));
    dd.cb = sizeof(dd);
 flag = EnumDisplayDevices(NULL, ScreenNo, &dd, 0); if (!flag) return -1; DEVMODE dm;
 ZeroMemory(&dm, sizeof(dm));
 dm.dmSize = sizeof(dm);
 flag = EnumDisplaySettings((char*)dd.DeviceName,ENUM_CURRENT_SETTINGS, &dm); if (!flag) return -1; return (short) dm.dmPosition.x ;
}// 設定顯示區X座標void CMScreenInfoCtrl::SetScreenLeft(short ScreenNo, short nNewValue)
{
 SetModifiedFlag();
}// 獲得主顯示區short CMScreenInfoCtrl::GetPrimaryScreen()
{
 // TODO: Add your property handler here
 if (m_monitorNum <= 1) return 0; // if the Screen Top = 0 and Left = 0, then, it's the Primary Screen
 short i;
 for (i=0; i<m_monitorNum; i++)
 {
  if (GetScreenTop(i)==0 && GetScreenLeft(i)==0) return i;
 }
 return 0;
}// 設定主顯示區void CMScreenInfoCtrl::SetPrimaryScreen(short nNewValue)
{
 SetModifiedFlag();
}關鍵的代碼基本就是這些了.(3)組件發布直接編譯成為ocx組件,取名為MutlScreen.ocx使用regsvr32.exe註冊一下就可以使用了.(六)分屏輸出組件的應用 我們使用最簡單的VB來編寫一個小程式實現 1 建立一個VB的工程,引用組件,建立兩個form,分別為frmCtl,和frmOutScreen,我們將frmOutScreen輸出到第二個螢幕上;2 組件使用的代碼如下: Function initMotion()    numScreen = frmCtl.MScreenInfo1.MonitorNum
    primaryScreen = frmCtl.MScreenInfo1.primaryScreen
   
    wScreen1 = frmCtl.MScreenInfo1.screenWidth(0)
    hScreen1 = frmCtl.MScreenInfo1.screenHeight(0)
    topScreen1 = frmCtl.MScreenInfo1.ScreenTop(0)
    leftScreen1 = frmCtl.MScreenInfo1.ScreenLeft(0)
   
    wScreen2 = frmCtl.MScreenInfo1.screenWidth(1)
    hScreen2 = frmCtl.MScreenInfo1.screenHeight(1)
    topScreen2 = frmCtl.MScreenInfo1.ScreenTop(1)
    leftScreen2 = frmCtl.MScreenInfo1.ScreenLeft(1)
   
End Function 3 frmOutScreen的代碼 Private Sub Form_Load()frmOutScreen.Left = leftScreen2 * 15 + 1
frmOutScreen.Top = 8frmOutScreen.WindowState = 2
frmOutScreen.WindowsMediaPlayer1.Left = 0
frmOutScreen.WindowsMediaPlayer1.Top = 0
frmOutScreen.WindowsMediaPlayer1.Width = frmMediaplay.Width
frmOutScreen.WindowsMediaPlayer1.Height = frmMediaplay.Height
frmOutScreen.RefreshEnd Sub 4 編譯運行就可以實現frmOutScreen在第二個顯示器的輸出,你可以加入你的web組件實現瀏覽器在第二個螢幕輸出,看如下我的執行結果  好了,就這些了,要實現更複雜的功能就需要你自己一點點的去調試了.

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.