Aangepast vensterframe met DWM
In dit onderwerp ziet u hoe u de DWM-API's (Desktop Window Manager) gebruikt om aangepaste vensterframes voor uw toepassing te maken.
- Inleiding
- het clientframe uitbreiden
- Het standaardframe verwijderen
- Tekening in het Uitgebreide Kader Venster
- Hit Testing inschakelen voor het aangepaste frame
- bijlage A: voorbeeldvensterprocedure
- Bijlage B: Het Schilderen van de Bijschrifttitel
- bijlage C: HitTestNCA functie
- Verwante onderwerpen
Introductie
In Windows Vista en hoger wordt het uiterlijk van de niet-clientgebieden van toepassingsvensters (de titelbalk, het pictogram, de vensterrand en de bijschriftknoppen) bepaald door de DWM. Met behulp van de DWM-API's kunt u de manier wijzigen waarop de DWM het frame van een venster weergeeft.
Een van de DWM-API's is de mogelijkheid om het toepassingsframe uit te breiden naar het clientgebied. Hiermee kunt u een element van de gebruikersinterface van de client( zoals een werkbalk) integreren in het frame, zodat de ui-besturingselementen een prominentere plaats hebben in de gebruikersinterface van de toepassing. Windows Internet Explorer 7 op Windows Vista integreert bijvoorbeeld de navigatiebalk in het vensterframe door de bovenkant van het frame uit te breiden, zoals wordt weergegeven in de volgende schermafbeelding.
Met de mogelijkheid om het vensterframe uit te breiden, kunt u ook aangepaste frames maken terwijl het uiterlijk van het venster behouden blijft. Microsoft Office Word 2007 tekent bijvoorbeeld de Office-knop en de Snelle toegang-werkbalk in het aangepaste frame, terwijl de standaardknoppen Minimaliseren, Maximaliseren en Sluiten zijn inbegrepen, zoals te zien is in de volgende schermafbeelding.
Het clientframe uitbreiden
De functionaliteit om het frame uit te breiden naar het clientgebied wordt weergegeven door de DwmExtendFrameIntoClientArea functie. Om het frame uit te breiden, geeft u de houder van het doelvenster samen met de waarden voor de marge-inzet door aan DwmExtendFrameIntoClientArea. De marge-insetwaarden bepalen hoe ver het frame aan de vier zijden van het venster moet worden uitgebreid.
De volgende code demonstreert het gebruik van DwmExtendFrameIntoClientArea om het frame uit te breiden.
// Handle the window activation.
if (message == WM_ACTIVATE)
{
// Extend the frame into the client area.
MARGINS margins;
margins.cxLeftWidth = LEFTEXTENDWIDTH; // 8
margins.cxRightWidth = RIGHTEXTENDWIDTH; // 8
margins.cyBottomHeight = BOTTOMEXTENDWIDTH; // 20
margins.cyTopHeight = TOPEXTENDWIDTH; // 27
hr = DwmExtendFrameIntoClientArea(hWnd, &margins);
if (!SUCCEEDED(hr))
{
// Handle the error.
}
fCallDWP = true;
lRet = 0;
}
De frameextensie wordt uitgevoerd in het WM_ACTIVATE bericht in plaats van het WM_CREATE bericht. Dit zorgt ervoor dat de frameextensie correct wordt verwerkt wanneer het venster de standaardgrootte heeft en wanneer het is gemaximaliseerd.
In de volgende afbeelding ziet u een standaardvensterkader (aan de linkerkant) en hetzelfde vensterframe uitgebreid (aan de rechterkant). Het frame wordt uitgebreid met behulp van het vorige codevoorbeeld en de standaard Microsoft Visual Studio WNDCLASS/WNDCLASSEX achtergrond (COLOR_WINDOW +1).
Het visuele verschil tussen deze twee vensters is zeer subtiel. Het enige verschil tussen de twee is dat de dunne zwarte lijnrand van de clientregio in het venster aan de linkerkant ontbreekt in het venster aan de rechterkant. De reden voor deze ontbrekende rand is dat deze is opgenomen in het uitgebreide frame, maar de rest van het cliëntgebied niet. Om de uitgebreide kaders zichtbaar te maken, moeten de onderliggende regio's van de zijden van deze uitgebreide kaders pixelgegevens hebben met een alfawaarde van 0. De zwarte rand rond het clientgebied bevat pixelgegevens waarin alle kleurwaarden (rood, groen, blauw en alfa) zijn ingesteld op 0. De rest van de achtergrond heeft de alfawaarde niet ingesteld op 0, dus de rest van het uitgebreide frame is niet zichtbaar.
De eenvoudigste manier om ervoor te zorgen dat de uitgebreide frames zichtbaar zijn, is om het hele clientgebied zwart in te kleuren. Hiervoor initialiseert u het hbrBackground lid van uw WNDCLASS of WNDCLASSEX structuur naar de handle van de stock BLACK_BRUSH. In de volgende afbeelding ziet u hetzelfde standaardframe (links) en uitgebreid frame (rechts) dat eerder werd weergegeven. Deze keer wordt echter hbrBackground- ingesteld op de BLACK_BRUSH ingang die is verkregen van de GetStockObject-functie.
Het standaardframe verwijderen
Nadat u het frame van uw toepassing hebt uitgebreid en zichtbaar hebt gemaakt, kunt u het standaardframe verwijderen. Door het standaardframe te verwijderen, kunt u de breedte van elke zijde van het frame beheren in plaats van het standaardframe uit te breiden.
Als u het standaardvensterframe wilt verwijderen, moet u het WM_NCCALCSIZE bericht afhandelen, met name wanneer de bijbehorende wParam--waarde is WAAR en de retourwaarde 0 is. Als u dit doet, gebruikt uw toepassing het hele venstergebied als het clientgebied, waardoor het standaardframe wordt verwijderd.
De resultaten van het verwerken van het WM_NCCALCSIZE bericht zijn pas zichtbaar als de grootte van het clientgebied moet worden gewijzigd. Tot die tijd wordt de eerste weergave van het venster weergegeven met het standaardframe en de uitgebreide randen. U kunt dit oplossen door het formaat van het venster te wijzigen of een actie uit te voeren waarmee een WM_NCCALCSIZE bericht wordt gestart op het moment dat het venster is gemaakt. Dit kan worden bereikt met behulp van de functie SetWindowPos om het venster te verplaatsen en het formaat ervan te wijzigen. De volgende code toont een aanroep naar SetWindowPos waarmee een WM_NCCALCSIZE bericht wordt verzonden met behulp van de huidige rechthoekattributen van het venster en de vlag SWP_FRAMECHANGED.
// Handle window creation.
if (message == WM_CREATE)
{
RECT rcClient;
GetWindowRect(hWnd, &rcClient);
// Inform the application of the frame change.
SetWindowPos(hWnd,
NULL,
rcClient.left, rcClient.top,
RECTWIDTH(rcClient), RECTHEIGHT(rcClient),
SWP_FRAMECHANGED);
fCallDWP = true;
lRet = 0;
}
In de volgende afbeelding ziet u het standaardframe (links) en het nieuwe uitgebreide frame zonder het standaardframe (rechts).
Tekenen in het Uitgebreid Frame-venster
Als u het standaardframe verwijdert, verliest u de automatische tekening van het toepassingspictogram en de titel. Als u deze weer wilt toevoegen aan uw toepassing, moet u ze zelf tekenen. Hiervoor bekijkt u eerst de wijziging die is opgetreden in uw clientgebied.
Met het verwijderen van het standaardframe bestaat uw clientgebied nu uit het hele venster, inclusief het uitgebreide frame. Dit omvat de regio waar de bijschriftknoppen worden getekend. In de volgende vergelijking naast elkaar wordt het clientgebied voor zowel het standaardframe als het aangepaste uitgebreide frame rood gemarkeerd. Het clientgebied voor het standaardframevenster (links) is de zwarte regio. In het uitgebreide framevenster (rechts) is het clientgebied het hele venster.
Omdat het hele venster uw clientgebied is, kunt u gewoon tekenen wat u wilt in het uitgebreide frame. Als u een titel aan uw toepassing wilt toevoegen, tekent u gewoon tekst in de juiste regio. In de volgende afbeelding ziet u tekst met thema's die zijn getekend in het aangepaste bijschriftframe. De titel wordt getekend met behulp van de functie DrawThemeTextEx. Zie bijlage B: De titel van het bijschrift schilderenom de code weer te geven waarmee de titel wordt geschilderd.
Notitie
Wanneer u in uw aangepaste frame tekent, moet u voorzichtig zijn bij het plaatsen van UI-besturingselementen. Omdat het hele venster uw clientregio is, moet u de plaatsing van ui-besturingselementen aanpassen voor elke framebreedte als u niet wilt dat deze worden weergegeven in of in het uitgebreide frame.
Hittests inschakelen voor het aangepaste frame
Een neveneffect van het verwijderen van het standaardframe is het verlies van het standaardgrootte- en verplaatsingsgedrag. Om uw toepassing het standaardvenstergedrag correct te laten emuleren, moet u logica implementeren om de knop Bijschriften te verwerken en het formaat van het frame te wijzigen/verplaatsen.
Voor hit testing voor bijschriftknoppen biedt DWM de DwmDefWindowProc functie aan. Als u de ondertitelingsknoppen in aangepaste framescenario's goed wilt testen, moeten berichten eerst worden doorgegeven aan DwmDefWindowProc- voor verwerking. DwmDefWindowProc retourneert TRUE als een bericht wordt verwerkt en FALSE als dat niet zo is. Als het bericht niet wordt verwerkt door DwmDefWindowProc-, moet uw toepassing het bericht zelf afhandelen of het bericht doorgeven aan DefWindowProc-.
Voor het aanpassen en verplaatsen van de framegrootte moet uw toepassing de logica voor hittests bieden en berichten voor frametreffers verwerken. Testberichten voor frametreffers worden naar u verzonden via het WM_NCHITTEST bericht, zelfs als uw toepassing een aangepast frame maakt zonder het standaardframe. De volgende code laat zien hoe het WM_NCHITTEST bericht wordt verwerkt wanneer DwmDefWindowProc- dit niet verwerkt. Zie Bijlage C: HitTestNCA Functionom de code van de aangeroepen HitTestNCA
functie te zien.
// Handle hit testing in the NCA if not handled by DwmDefWindowProc.
if ((message == WM_NCHITTEST) && (lRet == 0))
{
lRet = HitTestNCA(hWnd, wParam, lParam);
if (lRet != HTNOWHERE)
{
fCallDWP = false;
}
}
Bijlage A: Voorbeeldvensterprocedure
In het volgende codevoorbeeld ziet u een vensterprocedure en de ondersteunende werkfuncties die worden gebruikt om een aangepaste frametoepassing te maken.
//
// Main WinProc.
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
bool fCallDWP = true;
BOOL fDwmEnabled = FALSE;
LRESULT lRet = 0;
HRESULT hr = S_OK;
// Winproc worker for custom frame issues.
hr = DwmIsCompositionEnabled(&fDwmEnabled);
if (SUCCEEDED(hr))
{
lRet = CustomCaptionProc(hWnd, message, wParam, lParam, &fCallDWP);
}
// Winproc worker for the rest of the application.
if (fCallDWP)
{
lRet = AppWinProc(hWnd, message, wParam, lParam);
}
return lRet;
}
//
// Message handler for handling the custom caption messages.
//
LRESULT CustomCaptionProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, bool* pfCallDWP)
{
LRESULT lRet = 0;
HRESULT hr = S_OK;
bool fCallDWP = true; // Pass on to DefWindowProc?
fCallDWP = !DwmDefWindowProc(hWnd, message, wParam, lParam, &lRet);
// Handle window creation.
if (message == WM_CREATE)
{
RECT rcClient;
GetWindowRect(hWnd, &rcClient);
// Inform application of the frame change.
SetWindowPos(hWnd,
NULL,
rcClient.left, rcClient.top,
RECTWIDTH(rcClient), RECTHEIGHT(rcClient),
SWP_FRAMECHANGED);
fCallDWP = true;
lRet = 0;
}
// Handle window activation.
if (message == WM_ACTIVATE)
{
// Extend the frame into the client area.
MARGINS margins;
margins.cxLeftWidth = LEFTEXTENDWIDTH; // 8
margins.cxRightWidth = RIGHTEXTENDWIDTH; // 8
margins.cyBottomHeight = BOTTOMEXTENDWIDTH; // 20
margins.cyTopHeight = TOPEXTENDWIDTH; // 27
hr = DwmExtendFrameIntoClientArea(hWnd, &margins);
if (!SUCCEEDED(hr))
{
// Handle error.
}
fCallDWP = true;
lRet = 0;
}
if (message == WM_PAINT)
{
HDC hdc;
{
PAINTSTRUCT ps;
hdc = BeginPaint(hWnd, &ps);
PaintCustomCaption(hWnd, hdc);
EndPaint(hWnd, &ps);
}
fCallDWP = true;
lRet = 0;
}
// Handle the non-client size message.
if ((message == WM_NCCALCSIZE) && (wParam == TRUE))
{
// Calculate new NCCALCSIZE_PARAMS based on custom NCA inset.
NCCALCSIZE_PARAMS *pncsp = reinterpret_cast<NCCALCSIZE_PARAMS*>(lParam);
pncsp->rgrc[0].left = pncsp->rgrc[0].left + 0;
pncsp->rgrc[0].top = pncsp->rgrc[0].top + 0;
pncsp->rgrc[0].right = pncsp->rgrc[0].right - 0;
pncsp->rgrc[0].bottom = pncsp->rgrc[0].bottom - 0;
lRet = 0;
// No need to pass the message on to the DefWindowProc.
fCallDWP = false;
}
// Handle hit testing in the NCA if not handled by DwmDefWindowProc.
if ((message == WM_NCHITTEST) && (lRet == 0))
{
lRet = HitTestNCA(hWnd, wParam, lParam);
if (lRet != HTNOWHERE)
{
fCallDWP = false;
}
}
*pfCallDWP = fCallDWP;
return lRet;
}
//
// Message handler for the application.
//
LRESULT AppWinProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
HRESULT hr;
LRESULT result = 0;
switch (message)
{
case WM_CREATE:
{}
break;
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// Parse the menu selections:
switch (wmId)
{
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_PAINT:
{
hdc = BeginPaint(hWnd, &ps);
PaintCustomCaption(hWnd, hdc);
// Add any drawing code here...
EndPaint(hWnd, &ps);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
Bijlage B: De titel van het bijschrift schilderen
De volgende code laat zien hoe u een bijschrifttitel op het uitgebreide frame kunt schilderen. Deze functie moet worden aangeroepen vanuit de -aanroep BeginPaint en de -aanroep EndPaint.
// Paint the title on the custom frame.
void PaintCustomCaption(HWND hWnd, HDC hdc)
{
RECT rcClient;
GetClientRect(hWnd, &rcClient);
HTHEME hTheme = OpenThemeData(NULL, L"CompositedWindow::Window");
if (hTheme)
{
HDC hdcPaint = CreateCompatibleDC(hdc);
if (hdcPaint)
{
int cx = RECTWIDTH(rcClient);
int cy = RECTHEIGHT(rcClient);
// Define the BITMAPINFO structure used to draw text.
// Note that biHeight is negative. This is done because
// DrawThemeTextEx() needs the bitmap to be in top-to-bottom
// order.
BITMAPINFO dib = { 0 };
dib.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
dib.bmiHeader.biWidth = cx;
dib.bmiHeader.biHeight = -cy;
dib.bmiHeader.biPlanes = 1;
dib.bmiHeader.biBitCount = BIT_COUNT;
dib.bmiHeader.biCompression = BI_RGB;
HBITMAP hbm = CreateDIBSection(hdc, &dib, DIB_RGB_COLORS, NULL, NULL, 0);
if (hbm)
{
HBITMAP hbmOld = (HBITMAP)SelectObject(hdcPaint, hbm);
// Setup the theme drawing options.
DTTOPTS DttOpts = {sizeof(DTTOPTS)};
DttOpts.dwFlags = DTT_COMPOSITED | DTT_GLOWSIZE;
DttOpts.iGlowSize = 15;
// Select a font.
LOGFONT lgFont;
HFONT hFontOld = NULL;
if (SUCCEEDED(GetThemeSysFont(hTheme, TMT_CAPTIONFONT, &lgFont)))
{
HFONT hFont = CreateFontIndirect(&lgFont);
hFontOld = (HFONT) SelectObject(hdcPaint, hFont);
}
// Draw the title.
RECT rcPaint = rcClient;
rcPaint.top += 8;
rcPaint.right -= 125;
rcPaint.left += 8;
rcPaint.bottom = 50;
DrawThemeTextEx(hTheme,
hdcPaint,
0, 0,
szTitle,
-1,
DT_LEFT | DT_WORD_ELLIPSIS,
&rcPaint,
&DttOpts);
// Blit text to the frame.
BitBlt(hdc, 0, 0, cx, cy, hdcPaint, 0, 0, SRCCOPY);
SelectObject(hdcPaint, hbmOld);
if (hFontOld)
{
SelectObject(hdcPaint, hFontOld);
}
DeleteObject(hbm);
}
DeleteDC(hdcPaint);
}
CloseThemeData(hTheme);
}
}
Bijlage C: HitTestNCA, functie
De volgende code toont de HitTestNCA
functie die wordt gebruikt in het inschakelen van Hit Testing voor het aangepaste frame. Met deze functie wordt de logica voor hittesting van de WM_NCHITTEST afgehandeld wanneer DwmDefWindowProc het bericht niet afhandelt.
// Hit test the frame for resizing and moving.
LRESULT HitTestNCA(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
// Get the point coordinates for the hit test.
POINT ptMouse = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
// Get the window rectangle.
RECT rcWindow;
GetWindowRect(hWnd, &rcWindow);
// Get the frame rectangle, adjusted for the style without a caption.
RECT rcFrame = { 0 };
AdjustWindowRectEx(&rcFrame, WS_OVERLAPPEDWINDOW & ~WS_CAPTION, FALSE, NULL);
// Determine if the hit test is for resizing. Default middle (1,1).
USHORT uRow = 1;
USHORT uCol = 1;
bool fOnResizeBorder = false;
// Determine if the point is at the top or bottom of the window.
if (ptMouse.y >= rcWindow.top && ptMouse.y < rcWindow.top + TOPEXTENDWIDTH)
{
fOnResizeBorder = (ptMouse.y < (rcWindow.top - rcFrame.top));
uRow = 0;
}
else if (ptMouse.y < rcWindow.bottom && ptMouse.y >= rcWindow.bottom - BOTTOMEXTENDWIDTH)
{
uRow = 2;
}
// Determine if the point is at the left or right of the window.
if (ptMouse.x >= rcWindow.left && ptMouse.x < rcWindow.left + LEFTEXTENDWIDTH)
{
uCol = 0; // left side
}
else if (ptMouse.x < rcWindow.right && ptMouse.x >= rcWindow.right - RIGHTEXTENDWIDTH)
{
uCol = 2; // right side
}
// Hit test (HTTOPLEFT, ... HTBOTTOMRIGHT)
LRESULT hitTests[3][3] =
{
{ HTTOPLEFT, fOnResizeBorder ? HTTOP : HTCAPTION, HTTOPRIGHT },
{ HTLEFT, HTNOWHERE, HTRIGHT },
{ HTBOTTOMLEFT, HTBOTTOM, HTBOTTOMRIGHT },
};
return hitTests[uRow][uCol];
}
Verwante onderwerpen
-
Overzicht van Bureaubladvensterbeheer