1.更大的Windows桌面:在多顯示器模式下,可以把多個顯示器的顯示區域結合在一起來顯示 Windows桌面,不管這些顯示器的尺寸、物理位置、分辨率和刷新頻率是否相同。當我們運行一個應用程序時,程序的主窗口可以位於任何一個顯示器的顯示區域內,也可以跨多個顯示區域。我們也可以把一個程序的窗口從一個顯示區域移到另一個顯示區域中。
2. 屏幕複製或遠程顯示:我們可以讓兩個顯示器顯示相同的內容。在進行培訓或者向衆人進行演示時,這個特點是很有用的。利用這個特性,技術支持人員還可以對應用程序進行遠程監視和調試。
3.多重獨立顯示:在以上的兩種模式下,所有的顯示區域都是Windows虛擬桌面的一部分,但是在多重獨立顯示模式下,應用程序訪問的顯示器並不屬於Windows虛擬桌面。假設系統的第二個顯示器是一個高分辨率的大尺寸顯示器,我們可以把它用做 CAD應用程序的專用顯示。通過在CAD應用程序中調用新的Windows API,我們可以藉助GDI在上面畫圖。獨立顯示器的顯示區域沒有桌面上的任何對象(任務欄和快捷方式),它與Windows桌面是獨立的。這可以避免 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). 獲取指定屏幕的座標原點 -left
Syntax: Short GetScreenLeft( Short ScreenNo) ;
Input : ScreenNo -- 指定屏幕的序號, 0 -- m_monitorNum-1;
Return: Screen Left in Pixel;
Decription: 獲取 ScreenNo 指定屏幕的座標原點 -left 。
程序流程圖:與圖 2 相同,只是最後一步返回 dm.dmPosition.x.
(5). 獲取指定屏幕的座標原點 -top
Syntax: Short GetScreenLeft( Short ScreenNo) ;
Input : ScreenNo -- 指定屏幕的序號, 0 -- m_monitorNum-1;
Return: Screen Top in Pixel;
Decription: 獲取 ScreenNo 指定屏幕的座標原點 -top 。
程序流程圖:與圖 2 相同,只是最後一步返回 dm.dmPosition.y.
(6). 獲取主屏幕 --Primary Screen
Syntax: Short GetPrimaryScreen();
Input: Null;
Return: Primary Screen No, 0 -- m_monitorNum - 1
Description: 獲取主屏幕的序號。
程序流程:依次判斷那一個屏幕的原點是 (0, 0).
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註冊一下就可以使用了.
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
frmOutScreen.Top = 8
frmOutScreen.WindowsMediaPlayer1.Left = 0
frmOutScreen.WindowsMediaPlayer1.Top = 0
frmOutScreen.WindowsMediaPlayer1.Width = frmMediaplay.Width
frmOutScreen.WindowsMediaPlayer1.Height = frmMediaplay.Height
frmOutScreen.Refresh