主頁 > 軟體設計 > 使用Layered分層視窗實作視頻會議中的桌面區域共享

使用Layered分層視窗實作視頻會議中的桌面區域共享

2021-10-15 22:47:49 軟體設計

目錄

1、專案背景與需求

2、帶透明區域視窗的實作思路

3、duilib界面庫

4、呼叫UpdateLayeredWindow介面回傳失敗

5、點擊標題欄無法拖動視窗

6、選擇區域坐標該怎么設定給底層的影像采集編碼層呢?


你用帶有WS_EX_LAYERED風格的Layered分層視窗實作過異形視窗的效果嗎?在很多軟體中都能看到異形視窗的身影,異形視窗以其獨有的視覺效果,被廣泛地采用,以常見的360安全衛士的加速球視窗為例:

異形視窗的邊界一般是各種形狀的圓滑線條,被做成了各種妙趣橫生的各種圖案效果,給用戶帶來了良好的視覺效果和體驗,

其實,視窗在創建時是矩形形狀的,只不過呈現出來的圖案是各種獨特形狀的,除這些形狀區域以外,矩形視窗的其他區域被做成透明效果了,并且滑鼠是可以穿透這些透明的區域的,本文要講一種特殊的異形視窗,下面請看我詳細道來,

1、專案背景與需求

最近公司中標了一個比較大的專案,客戶對我們的會議軟體總體上是很滿意的,但客戶要求在已有的功能上增加一個功能,要在桌面共享功能中增加桌面區域共享的功能,客戶明確提出,這個需求是硬性功能指標,必須實作這個功能后才能完成專案的中標,于是相關部門將開發需求落到了我們研發部,要求我們在最短的時間內盡快地實作這個功能,

這個桌面區域共享,和整個桌面共享及應用視窗共享實作機制時完全一樣的,都是抓取某一個區域的影像,只要UI層告訴采集編碼層要抓取的區域坐標就可以了,所以主要的作業量在UI層,影像采集編碼層不用做大的改動即可實作,所以這個功能點的實作重心就在于UI層的互動實作,即UI層如何選定要分享的區域,然后都需要支持哪些操作,

這個桌面部磁區域的共享,很多友商都支持了,在時間緊急沒有頭緒的情況下,趕緊看一下友商的實作方式,經過對比發現,小魚易聯和ZOOM的會議軟體,該功能的UI互動和實作機制竟然是一模一樣的,至于誰先做出來、誰模仿誰,就不得而知了,于是大概地看了一下他們的實作機制,創建一個特殊的視窗,視窗是有邊界的,然后視窗的中間區域是透明的、且滑鼠可穿透的,如下所示:(這種顯示背景下的截圖)

該特殊視窗的邊界框住的區域就是要分享的區域,即框住的影像就是要分享出去的影像,通過這個視窗實作了待分享區域的選擇,該選擇區域的視窗支持標題欄拖動,支持拖動邊框改變大小,

初步猜測該視窗應該是具有WS_EX_LAYERED風格的分層視窗,呼叫UpdateLayeredWindow系統API將視窗中間區域透明掉,使用SPY++工具查看了一下該視窗的屬性:

確實是分層視窗,并且設定了TOPMOST視窗置頂的屬性,我們有呼叫UpdateLayeredWindow實作透明視窗的經驗,所以本例中這樣的視窗效果我們也能實作,于是基本擬定了當前桌面區域共享的幾個需求點:

1)選擇區域的視窗使用具有WS_EX_LAYERED視窗樣式的分層視窗,呼叫UpdateLayeredWindow實作視窗中間區域的透明及滑鼠穿透;

2)該選擇區域的視窗,支持標題欄拖動視窗,支持拖動視窗邊框改變視窗大小,

2、帶透明區域視窗的實作思路

這種帶透明區域且滑鼠可穿透的視窗,直接設計出一個帶透明區域的圖片,然后呼叫UpdateLayeredWindow系統API,將圖片貼到目標視窗上即可,

具體的實作思路是,先創建帶有WS_EX_LAYERED視窗樣式的分層視窗(目標視窗),將帶透明區域的圖片繪制到記憶體DC上,中間透明區域則全部是RGB(0,0,0)純黑色,然后呼叫UpdateLayeredWindow將記憶體DC畫板中的內容繪制到目標視窗上,中間黑色的區域會變成透明、滑鼠可穿透的區域,圖片的非透明區域則是不透明的,

但本例中的視窗大小是可變的,需要支持拖動視窗邊框改變大小的,所以不能直接將整個圖片貼到視窗上,因為UCD組(美工組)提供的圖片是固定大小的,不能做縮放操作的,所以我們要從圖片中摳出視窗left、top、right、bottom四個方向上的邊框區域,然后分塊貼到記憶體DC上即可,只要保證中間要透明的區域是黑色的色塊就可以了,貼圖及呼叫UpdateLayeredWindow的相關代碼如下所示:

#define   DESKTOP_SHARE_AREA_SEL_WND_EDGESIZE_TOP_HEIGHT       27
#define   DESKTOP_SHARE_AREA_SEL_WND_EDGESIZE_LEFT_WIDTH       5
#define   DESKTOP_SHARE_AREA_SEL_WND_EDGESIZE_RIGHT_WIDTH      5
#define   DESKTOP_SHARE_AREA_SEL_WND_EDGESIZE_BOTTOM_HEIGHT    5


void CDesktopShareAreaSelDlg::Update()
{
	if ( m_BkImg.IsNull() )
	{
		return;
	}

	RECT rcWnd;
	::GetWindowRect(m_hWnd, &rcWnd);
	int nWndWidth = rcWnd.right - rcWnd.left;
	int nWndHeight = rcWnd.bottom - rcWnd.top;

	// Create the alpha blending bitmap
	BITMAPINFO bmi;        // bitmap header

	ZeroMemory(&bmi, sizeof(BITMAPINFO));
	bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
	bmi.bmiHeader.biWidth = nWndWidth;
	bmi.bmiHeader.biHeight = nWndHeight;
	bmi.bmiHeader.biPlanes = 1;
	bmi.bmiHeader.biBitCount = 32;         // four 8-bit components
	bmi.bmiHeader.biCompression = BI_RGB;
	bmi.bmiHeader.biSizeImage = nWndWidth * nWndHeight * 4;

	BYTE *pvBits;          // pointer to DIB section
	HBITMAP hbitmap = CreateDIBSection(NULL, &bmi, DIB_RGB_COLORS, (void **)&pvBits, NULL, 0);
	if (pvBits == NULL) {
		return;
	}

	ZeroMemory(pvBits, bmi.bmiHeader.biSizeImage);

	// 創建記憶體DC
	HDC hMemDC = CreateCompatibleDC(NULL);
	HBITMAP hOriBmp = (HBITMAP)SelectObject(hMemDC, hbitmap);

	int nImgWidth = m_BkImg.GetWidth();
	int nImgHeight = m_BkImg.GetHeight();

	// 將視窗left、top、right、bottom四個方向上的圖片繪制到視窗邊框的位置
	m_BkImg.Draw( hMemDC, 0, 0, DESKTOP_SHARE_AREA_SEL_WND_EDGESIZE_LEFT_WIDTH, nWndHeight, 0, 0, DESKTOP_SHARE_AREA_SEL_WND_EDGESIZE_LEFT_WIDTH, nImgHeight );
	m_BkImg.Draw( hMemDC, nWndWidth - DESKTOP_SHARE_AREA_SEL_WND_EDGESIZE_RIGHT_WIDTH, 0, DESKTOP_SHARE_AREA_SEL_WND_EDGESIZE_RIGHT_WIDTH, nWndHeight, nImgWidth-DESKTOP_SHARE_AREA_SEL_WND_EDGESIZE_RIGHT_WIDTH, 0, DESKTOP_SHARE_AREA_SEL_WND_EDGESIZE_RIGHT_WIDTH, nImgHeight );
	m_BkImg.Draw( hMemDC, 0, 0, nWndWidth, DESKTOP_SHARE_AREA_SEL_WND_EDGESIZE_TOP_HEIGHT, 0, 0, nImgWidth, DESKTOP_SHARE_AREA_SEL_WND_EDGESIZE_TOP_HEIGHT );
	m_BkImg.Draw( hMemDC, 0, nWndHeight-DESKTOP_SHARE_AREA_SEL_WND_EDGESIZE_BOTTOM_HEIGHT, nWndWidth, DESKTOP_SHARE_AREA_SEL_WND_EDGESIZE_BOTTOM_HEIGHT, 0, nImgHeight-DESKTOP_SHARE_AREA_SEL_WND_EDGESIZE_BOTTOM_HEIGHT, nImgWidth, DESKTOP_SHARE_AREA_SEL_WND_EDGESIZE_BOTTOM_HEIGHT );

	POINT ptDst = {rcWnd.left, rcWnd.top};
	POINT ptSrc = {0, 0};
	SIZE WndSize = {nWndWidth, nWndHeight};
	BLENDFUNCTION blendPixelFunction= { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA };

	BOOL bRet= UpdateLayeredWindow(m_hWnd, NULL, &ptDst, &WndSize, hMemDC,
		&ptSrc, 0, &blendPixelFunction, ULW_ALPHA);

	DWORD dwRet = GetLastError();

	InvalidateRect( m_hWnd, &rcWnd, TRUE );
	//_ASSERT(bRet); // something was wrong....

	// Delete used resources
	SelectObject(hMemDC, hOriBmp);
	DeleteObject(hbitmap);
	DeleteDC(hMemDC);
}

上述介面要在WM_SIZE訊息中呼叫,即視窗大小改變時,要重新繪制視窗:

LRESULT CDesktopShareAreaSelDlg::HandleMessage( UINT message, WPARAM wParam, LPARAM lParam )
{
	TNotifyUI msg;
	if ( WM_SIZE == message )
	{
		Update();
	}
	else if ( WM_PAINT == message )
	{
		Update();
		return true;
	}
}

3、duilib界面庫

本文涉及到的很多代碼,都和duilib有關,所以此處需要簡單地說明一下,我們程式UI使用的是開源的duilib界面庫,是基于微軟directui思想的一套界面庫,

做UI的朋友應該大部分都知道這個duilib界面庫,現在很多公司都在用,比如百度、華為、網易、愛奇藝、ZOOM等,這些公司的Windows開發招聘崗位上有對熟悉duilib庫的要求,當然,這些廠商會對duilib進行深入的優化和改進,解決了很多bug,我們這邊也不例外,也使用程序中也發現了很多問題,也對代碼進行了一些優化和改進,

4、呼叫UpdateLayeredWindow介面回傳失敗

在創建CDesktopAreaShareDlg視窗時設定了WS_EX_LAYERED分層視窗的風格,結果運行顯示出來的視窗并沒有顯示圖片邊框,視窗中間也不是透明、滑鼠可穿透的,除錯代碼發現,UpdateLayeredWindow函式呼叫失敗了,GetLastError回傳的錯誤碼是87

到VS的錯誤查找工具中搜索了一下:

該錯誤碼的含義是:引數錯誤,但詳細檢查了一下傳入的引數值,并沒有例外非法的值,這個就有點奇怪了,

首先創建的位圖是包含Alpha通道的32位位圖,如果是非32位位圖則可能導致UpdateLayeredWindow函式呼叫失敗,其次傳入到UpdateLayeredWindow介面中的引數都沒問題的,可為啥還會失敗呢?
難道是WS_EX_LAYERED視窗風格設定失敗了?于是想用SPY++抓了一下視窗屬性,看看視窗到底有沒有WS_EX_LAYERED風格,但是該視窗因為呼叫UpdateLayeredWindow失敗了,桌面上根本沒顯示這個視窗,沒關系,我們可以直接到SPY++中搜索該視窗,通過創建視窗時使用的類名搜索(只填充類名,將其他輸入框清空):

搜索到該視窗,查看視窗屬性中的視窗風格:

果然沒有WS_EX_LAYERED風格,這就奇怪了,明明設定了WS_EX_LAYERED視窗風格,為啥沒生效呢?

這個視窗是繼承duilib框架中的CAppWidnow(我們自行封裝的,duilib開源代碼是沒有的)通用視窗類的,好像該通用視窗類中有個設定透明度的選項,可能是處理視窗透明度的代碼將WS_EX_LAYERED風格給取消了,

于是到duilib的代碼中,找到設定透明度的代碼,確實是這個設定透明度的介面將WS_EX_LAYERED風格取消了:

void CPaintManagerUI::SetTransparent(int nOpacity)
{
	if (NULL == m_hWndPaint)
	{
		return;
	}

	typedef BOOL(__stdcall *PFUNCSETLAYEREDWINDOWATTR)(HWND, COLORREF, BYTE, DWORD);
	PFUNCSETLAYEREDWINDOWATTR fSetLayeredWindowAttributes;

	HMODULE hUser32 = ::GetModuleHandle(_T("User32.dll"));
	if (hUser32)
	{
		fSetLayeredWindowAttributes =
			(PFUNCSETLAYEREDWINDOWATTR)::GetProcAddress(hUser32, "SetLayeredWindowAttributes");

		if (NULL == fSetLayeredWindowAttributes)
		{
			return;
		}
	}

	DWORD dwStyle = ::GetWindowLong(m_hWndPaint, GWL_EXSTYLE);
	DWORD dwNewStyle = dwStyle;
	if (nOpacity >= 0 && nOpacity < 255)
	{
		dwNewStyle |= WS_EX_LAYERED;
	}
	else
	{
		dwNewStyle &= ~WS_EX_LAYERED;
	}

	if (dwStyle != dwNewStyle)
	{
		::SetWindowLong(m_hWndPaint, GWL_EXSTYLE, dwNewStyle);
	}

	fSetLayeredWindowAttributes(m_hWndPaint, 0, nOpacity, LWA_ALPHA);
}

我們給該視窗設定的透明度為255,即不透明,所以上述代碼中發現傳入的透明度引數nOpacity為255,就將WS_EX_LAYERED風格給取消了,此處的代碼是有問題的,將視窗的透明度設定為不透明,不應該將WS_EX_LAYERED風格取消掉,因為可能會呼叫處理分層視窗的其他系統API函式,比如我們本案例用到的UpdateLayeredWindow,所以要將這個取消WS_EX_LAYERED風格的代碼注釋掉,
但代碼注釋后運行,還是有問題,UpdateLayeredWindow介面還是執行失敗了,lasterror值依舊是87,依稀記得,好像設定視窗透明度的介面SetLayeredWindowAttributes和處理異形視窗的介面UpdateLayeredWindow是不能同時呼叫的,如上的代碼所示,設定透明度的介面CPaintManagerUI::SetTransparent中不管是否設定了有效的透明度(小于255),都呼叫了SetLayeredWindowAttributes,所以這點也是不合理的,應該設定不透明時,就不應該呼叫SetLayeredWindowAttributes,修改后的代碼

重新運行代碼后,發現有效果了,視窗邊界圖片顯示出來了,視窗的中間區域也是可穿透的了,

5、點擊標題欄無法拖動視窗

對于視窗支持標題欄拖動、視窗支持拖動邊框改變視窗大小,duilib中的視窗框架都支持,只要在xml檔案中設定兩個屬性就可以了,對于標題欄拖動,設定caption屬性即可,即caption="0,0,0,27";對于視窗邊界可拖動,設定sizebox屬性即可,即sizebox="5,5,5,5",視窗對應的xml檔案如下:

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Window size="794,500" mininfo="400,250" sizebox="5,5,5,5" caption="0,0,0,27" shadow="false">
	<Font name="微軟雅黑" size="12" />
  <Default name="VScrollBar" value="width=&quot;10&quot; showbutton1=&quot;false&quot; showbutton2=&quot;false&quot; thumbnormalimage=&quot;res='IDB_DEFAULT_SCROLL_BAR' restype='png' source='20,0,30,21' &quot; bknormalimage=&quot;res='IDB_DEFAULT_SCROLL_BAR' restype='png' source='30,0,40,10' &quot; bkhotimage=&quot;res='IDB_DEFAULT_SCROLL_BAR' restype='png' source='30,0,40,10' &quot; bkpushedimage=&quot;res='IDB_DEFAULT_SCROLL_BAR' restype='png' source='30,0,40,10' &quot; bkdisabledimage=&quot;res='IDB_DEFAULT_SCROLL_BAR' restype='png' source='30,0,40,10' &quot; " />
  <VerticalLayout name="desktopareaselwnd" bkcolor="#FFFF0000">
  </VerticalLayout>
</Window>

添加屬性后,我們測驗了一下效果,視窗邊界拖動視窗大小是沒問題的,但標題欄是無法拖動視窗的,當滑鼠移動到視窗中時,會產生WM_NCHITTEST訊息,按講移到視窗標題欄區域時,應該回傳HTCAPTION,以支持視窗的拖動,

于是到duilib框架代碼中查看CAppWindow類處理WM_NCHITTEST訊息的代碼:

LRESULT CAppWindow::OnNcHitTest( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled )
{
	POINT pt; 
	pt.x = GET_X_LPARAM( lParam ); 
	pt.y = GET_Y_LPARAM( lParam );
	::ScreenToClient( *this, &pt );

	RECT rcClient;
	::GetClientRect( *this, &rcClient );
    
	// 改變大小
	if( !::IsZoomed(*this) ) 
	{
		RECT rcSizeBox = m_pm.GetSizeBox();
		if( pt.y < rcClient.top + rcSizeBox.top ) 
		{
			if( pt.x < rcClient.left + rcSizeBox.left ) return HTTOPLEFT;
			if( pt.x > rcClient.right - rcSizeBox.right ) return HTTOPRIGHT;
			return HTTOP;
		}
		else if( pt.y > rcClient.bottom - rcSizeBox.bottom ) 
		{
			if( pt.x < rcClient.left + rcSizeBox.left ) return HTBOTTOMLEFT;
			if( pt.x > rcClient.right - rcSizeBox.right ) return HTBOTTOMRIGHT;
			return HTBOTTOM;
		}
		if( pt.x < rcClient.left + rcSizeBox.left ) return HTLEFT;
		if( pt.x > rcClient.right - rcSizeBox.right ) return HTRIGHT;
	}

	// 標題欄回應
	RECT rcCaption = m_pm.GetCaptionRect();
	if( pt.x >= rcClient.left + rcCaption.left 
	  && pt.x < rcClient.right - rcCaption.right 
	  && pt.y >= rcCaption.top  
	  && pt.y < rcCaption.bottom ) 
	{
		// 考慮到標題欄區域會放置控制元件,比如常見的右上角的最小化、最大化和關閉按鈕,
		// 所以要將按鈕等控制元件過濾掉
		CControlUI* pControl = static_cast<CControlUI*>( m_pm.FindControl( pt ) );
		if( pControl 
			&& _tcscmp(pControl->GetClass(), _T("ButtonUI")) != 0 
			&& _tcscmp(pControl->GetClass(), _T("OptionUI")) != 0 
			&& _tcscmp(pControl->GetClass(), _T("TextUI")) != 0 )
		{
			return HTCAPTION;
		}
	}

	return HTCLIENT;
}

打斷點除錯發現,在判斷滑鼠位置落在標題欄區域時,會判斷滑鼠是否落在某個dui控制元件中,如果回傳的dui控制元件指標為空(滑鼠點擊點是否落在視窗的dui控制元件上),是不會回傳HTCAPTION值的,于是在CDesktopAreaShareDlg視窗類的HandleMesssge中攔截WM_HITTEST訊息,將代碼修改一下,去掉是否落在控制元件中的判斷,我們這個視窗比較簡單,也比較特殊:

LRESULT CDesktopShareAreaSelDlg::HandleMessage( UINT message, WPARAM wParam, LPARAM lParam )
{
	TNotifyUI msg;
	if ( WM_SIZE == message )
	{
		Update();
	}
	else if ( WM_PAINT == message )
	{
		Update();
		return true;
	}
	else if ( WM_NCHITTEST == message)
	{
		POINT pt; 
		pt.x = GET_X_LPARAM( lParam ); 
		pt.y = GET_Y_LPARAM( lParam );
		::ScreenToClient( m_hWnd, &pt );

		RECT rcClient;
		::GetClientRect( m_hWnd, &rcClient );

		// 改變大小
		if( !::IsZoomed(m_hWnd) ) 
		{
			RECT rcSizeBox = m_pm.GetSizeBox();
			if( pt.y < rcClient.top + rcSizeBox.top ) 
			{
				if( pt.x < rcClient.left + rcSizeBox.left ) return HTTOPLEFT;
				if( pt.x > rcClient.right - rcSizeBox.right ) return HTTOPRIGHT;
				return HTTOP;
			}
			else if( pt.y > rcClient.bottom - rcSizeBox.bottom ) 
			{
				if( pt.x < rcClient.left + rcSizeBox.left ) return HTBOTTOMLEFT;
				if( pt.x > rcClient.right - rcSizeBox.right ) return HTBOTTOMRIGHT;
				return HTBOTTOM;
			}
			if( pt.x < rcClient.left + rcSizeBox.left ) return HTLEFT;
			if( pt.x > rcClient.right - rcSizeBox.right ) return HTRIGHT;
		}

		// 標題欄回應
		RECT rcCaption = m_pm.GetCaptionRect();
		if( pt.x >= rcClient.left + rcCaption.left 
			&& pt.x < rcClient.right - rcCaption.right 
			&& pt.y >= rcCaption.top  
			&& pt.y < rcCaption.bottom ) 
		{
			return HTCAPTION;			
		}
	}

	return CAppWindow::HandleMessage( message, wParam, lParam );
}

對于我們當前的視窗,只要落在視窗標題欄區域就直接回傳HTCAPTION值了,不用再去做其他的判斷了,

至于為啥會出現滑鼠落在的控制元件指標回傳NULL的情況呢?我們的視窗類CDesktopAreaShareDlg的xml檔案中已經配置了一個垂直根布局(只有這個根布局):

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Window size="794,500" mininfo="400,250" sizebox="5,5,5,5" caption="0,0,0,27" shadow="false">
	<Font name="微軟雅黑" size="12" />
  <Default name="VScrollBar" value="width=&quot;10&quot; showbutton1=&quot;false&quot; showbutton2=&quot;false&quot; thumbnormalimage=&quot;res='IDB_DEFAULT_SCROLL_BAR' restype='png' source='20,0,30,21' &quot; bknormalimage=&quot;res='IDB_DEFAULT_SCROLL_BAR' restype='png' source='30,0,40,10' &quot; bkhotimage=&quot;res='IDB_DEFAULT_SCROLL_BAR' restype='png' source='30,0,40,10' &quot; bkpushedimage=&quot;res='IDB_DEFAULT_SCROLL_BAR' restype='png' source='30,0,40,10' &quot; bkdisabledimage=&quot;res='IDB_DEFAULT_SCROLL_BAR' restype='png' source='30,0,40,10' &quot; " />
  <VerticalLayout name="desktopareaselwnd" bkcolor="#FFFF0000">
  </VerticalLayout>
</Window>

按講根布局會鋪滿整個視窗的,滑鼠肯定是落在這個根布局上,于是在CAppWidnow處理WM_NCHITTEST訊息的介面中打斷點單步除錯發現,根布局的區域位置m_rcItem為(0,0,0,0),即根布局的區域大小為0,所以不會滑鼠肯定不會落在根布局上,

奧,原來是這樣的,我們xml中控制元件是在所在視窗收到WM_PAINT訊息時去布局控制元件(給控制元件設定位置)的,但一旦對視窗呼叫UpdateLayeredWindow后視窗就不會產生WM_PAINT訊息的,我們會在xml中設定視窗的大小,我們在呼叫CreateWIndow(Ex)將視窗創建起來收WM_CREATE訊息時區加載決議xml檔案,會優先得到xml中設定的視窗大小,會呼叫SetWindowPos去設定視窗的大小,視窗大小會發生變化,就會產生WM_SIZE訊息,而CDesktopAreaShareDlg視窗在收到這個訊息時就會呼叫UpdateLayeredWindow,這樣視窗后面就不會再產生WM_PAINT訊息了,所以CDesktopAreaShareDlg視窗就沒有排布xml中控制元件的機會了,所以根布局控制元件的大小始終是0,

6、選擇區域坐標該怎么設定給底層的影像采集編碼層呢?

選擇區域視窗大小可拖動,選擇可以拖動標題欄拖動視窗,我們在視窗移動時或者視窗大小發生變化時,將選擇區域的坐標實時的設定給影像采集編碼層?如果由UI層呼叫介面將選擇區域的坐標設定給媒控層,則存在兩個問題:
1) 何時觸發呼叫設定選擇區域坐標給影像采集編碼層的介面?在視窗移動時,在視窗大小發生變化時都要觸發,這樣介面呼叫會非常地頻繁,
2) UI層需要呼叫組件API層的介面,然后再經過組件層內部的多層后,再設定到采集編碼庫
中,這些層與層之間的介面都是異步的,很難保證選擇區域的大小,實時地通知給采集編碼層,
其實,有個最好的辦法,UI層給采集編碼層設定用來選擇區域視窗句柄,并將選擇視窗四個邊界的寬度或高度設定給采集編碼層,如下所示:

這樣媒控層可以在每次截圖時實時去獲取選擇視窗的坐標,然后將選擇視窗邊界寬度與高度減掉,就能實時地得到選擇區域的坐標了,這應該是最合理的方式!函式呼叫需要額外的開銷,所以采用這種方式,既可以滿足實時性獲取要求,也能減少函式呼叫的開銷!

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/316709.html

標籤:其他

上一篇:萬字總結《Python零基礎入門》——小白也能看懂的入門教程(建議收藏)

下一篇:【C++從0到1】新手都能看懂的C++入門(中篇)類入門,建議收藏!!!

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more