Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Topik ini menunjukkan cara menggunakan API Desktop Window Manager (DWM) untuk membuat bingkai jendela kustom untuk aplikasi Anda.
- Pengenalan
- Mengembangkan Kerangka Klien
- Menghapus Bingkai Standar
- gambar di jendela bingkai yang diperluas
- Mengaktifkan Pengujian Benturan untuk Bingkai Kustom
- Lampiran A: Prosedur Jendela Contoh
- Lampiran B: Melukis Judul Keterangan
- Lampiran C: Fungsi HitTestNCA
- Topik terkait
Perkenalan
Di Windows Vista dan yang lebih baru, tampilan area non-klien jendela aplikasi (bilah judul, ikon, batas jendela, dan tombol keterangan) dikontrol oleh DWM. Dengan menggunakan API DWM, Anda dapat mengubah cara DWM merender bingkai jendela.
Salah satu fitur API DWM adalah kemampuan untuk memperluas bingkai aplikasi ke area klien. Ini memungkinkan Anda mengintegrasikan elemen UI klien—seperti toolbar—ke dalam bingkai, memberikan kontrol UI tempat yang lebih menonjol di antarmuka pengguna aplikasi. Misalnya, Windows Internet Explorer 7 di Windows Vista mengintegrasikan bilah navigasi ke bingkai jendela dengan memperluas bagian atas bingkai seperti yang ditunjukkan pada cuplikan layar berikut.
Kemampuan untuk memperluas bingkai jendela juga memungkinkan Anda membuat bingkai kustom sambil mempertahankan tampilan dan nuansa jendela. Misalnya, Microsoft Office Word 2007 menggambar tombol Office dan toolbar Akses Cepat di dalam bingkai kustom sambil menyediakan tombol Minimalkan, Maksimalkan, dan Tutup keterangan standar, seperti yang diperlihatkan dalam cuplikan layar berikut.
Memperluas Bingkai Klien
Fungsionalitas untuk memperluas bingkai ke area klien diekspos oleh fungsiDwmExtendFrameIntoClientArea. Untuk memperluas bingkai, oper gagang jendela target bersama dengan nilai inset margin ke DwmExtendFrameIntoClientArea. Nilai inset margin menentukan seberapa jauh untuk memperluas bingkai di empat sisi jendela.
Kode berikut menunjukkan penggunaan DwmExtendFrameIntoClientArea untuk memperluas bingkai.
// 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;
}
Perhatikan bahwa ekstensi bingkai dilakukan dalam pesan WM_ACTIVATE daripada pesan WM_CREATE. Ini memastikan bahwa ekstensi bingkai ditangani dengan benar ketika jendela berada pada ukuran defaultnya dan ketika dimaksimalkan.
Gambar berikut menunjukkan bingkai jendela standar (di sebelah kiri) dan bingkai jendela yang sama diperluas (di sebelah kanan). Bingkai diperluas menggunakan contoh kode sebelumnya dan latar belakangMicrosoft Visual Studio WNDCLASS/WNDCLASSEX (COLOR_WINDOW +1).
Perbedaan visual antara kedua jendela ini sangat halus. Satu-satunya perbedaan antara keduanya adalah bahwa batas garis hitam tipis dari wilayah klien di jendela di sebelah kiri hilang dari jendela di sebelah kanan. Alasan untuk batas yang hilang ini adalah bahwa itu dimasukkan ke dalam bingkai yang diperluas, tetapi sisa area klien tidak. Agar bingkai yang diperluas terlihat, daerah di bawah setiap sisi bingkainya harus memiliki data piksel dengan nilai alfa 0. Batas hitam di sekitar wilayah klien memiliki data piksel di mana semua nilai warna (merah, hijau, biru, dan alfa) diatur ke 0. Latar belakang lainnya tidak memiliki nilai alfa yang diatur ke 0, sehingga sisa bingkai yang diperluas tidak terlihat.
Cara termudah untuk memastikan bahwa bingkai yang diperpanjang terlihat adalah dengan mengecat seluruh area klien menjadi hitam. Untuk mencapai hal ini, inisialisasi anggota hbrBackground dari struktur WNDCLASS atau WNDCLASSEX ke handle dari BLACK_BRUSH stok. Gambar berikut menunjukkan bingkai standar yang sama (kiri) dan bingkai yang diperluas (kanan) yang ditunjukkan sebelumnya. Namun kali ini, hbrBackground diatur ke handle BLACK_BRUSH yang diperoleh dari fungsi GetStockObject.
Menghapus Bingkai Standar
Setelah memperluas bingkai aplikasi dan membuatnya terlihat, Anda dapat menghapus bingkai standar. Menghapus bingkai standar memungkinkan Anda mengontrol lebar setiap sisi bingkai daripada hanya memperluas bingkai standar.
Untuk menghapus bingkai jendela standar, Anda harus menangani pesan WM_NCCALCSIZE, khususnya ketika nilai wParamTRUE dan nilai yang dikembalikan adalah 0. Dengan demikian, aplikasi Anda menggunakan seluruh wilayah jendela sebagai area klien, menghapus bingkai standar.
Hasil penanganan pesan WM_NCCALCSIZE tidak terlihat sampai area klien perlu diubah ukuran. Hingga saat itu, tampilan awal jendela muncul dengan bingkai standar dan batas yang diperluas. Untuk mengatasi hal ini, Anda harus mengubah ukuran jendela atau melakukan tindakan yang memulai pesan WM_NCCALCSIZE pada saat pembuatan jendela. Ini dapat dicapai dengan menggunakan fungsi SetWindowPos untuk memindahkan jendela Anda dan mengubah ukurannya. Kode berikut menunjukkan panggilan ke SetWindowPos yang memaksa pesan WM_NCCALCSIZE dikirim menggunakan atribut persegi panjang jendela saat ini dan bendera 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;
}
Gambar berikut menunjukkan bingkai standar (kiri) dan bingkai yang baru diperluas tanpa bingkai standar (kanan).
Menggambar di Jendela Bingkai yang Diperluas
Dengan menghapus bingkai standar, Anda kehilangan gambar otomatis ikon dan judul aplikasi. Untuk menambahkannya kembali ke aplikasi, Anda harus menggambarnya sendiri. Untuk melakukan ini, pertama-tama lihat perubahan yang telah terjadi pada area klien Anda.
Dengan penghapusan bingkai standar, area klien Anda sekarang terdiri dari seluruh jendela, termasuk bingkai yang diperluas. Ini termasuk wilayah tempat tombol keterangan digambar. Dalam perbandingan berdampingan berikut, area klien untuk bingkai standar dan bingkai kustom yang diperluas disorot dengan warna merah. Area klien untuk jendela bingkai standar (kiri) adalah wilayah hitam. Pada jendela bingkai yang diperluas (kanan), area klien adalah seluruh jendela.
Karena seluruh jendela adalah area klien Anda, Anda cukup menggambar apa yang Anda inginkan dalam bingkai yang diperluas. Untuk menambahkan judul ke aplikasi Anda, cukup gambar teks di wilayah yang sesuai. Gambar berikut menunjukkan teks bertema yang digambar pada bingkai keterangan kustom. Judul digambar menggunakan fungsi DrawThemeTextEx. Untuk melihat kode yang melukis judul, lihat Lampiran B: Melukis Judul Keterangan.
Nota
Saat menggambar dalam bingkai kustom Anda, berhati-hatilah saat menempatkan kontrol UI. Karena seluruh jendela adalah wilayah klien Anda, Anda harus menyesuaikan penempatan kontrol UI untuk setiap lebar bingkai jika Anda tidak ingin jendela tersebut muncul pada atau dalam bingkai yang diperluas.
Mengaktifkan Tes Tumbukan untuk Bingkai Kustom
Efek samping dari menghapus bingkai standar adalah hilangnya perilaku mengubah ukuran dan pemindahan default. Agar aplikasi Anda dapat meniru perilaku jendela standar dengan benar, Anda harus menerapkan logika untuk menangani pengujian tekan tombol keterangan dan mengubah ukuran/pemindahan bingkai.
Untuk pengujian tekan tombol keterangan, DWM menyediakan fungsiDwmDefWindowProc. Untuk menguji tombol judul dengan benar dalam skenario bingkai kustom, pesan harus terlebih dahulu diteruskan ke DwmDefWindowProc untuk penanganan. DwmDefWindowProc mengembalikan TRUE jika pesan ditangani dan FALSE jika tidak. Jika pesan tidak ditangani oleh DwmDefWindowProc, aplikasi Anda harus menangani pesan itu sendiri atau meneruskan pesan ke DefWindowProc.
Untuk mengubah ukuran dan memindahkan bingkai, aplikasi Anda harus menyediakan logika pendeteksian tabrakan dan menangani pesan pengujian titik bingkai. Pesan uji tekan bingkai dikirimkan kepada Anda melalui pesan WM_NCHITTEST, bahkan jika aplikasi Anda membuat bingkai kustom tanpa bingkai standar. Kode berikut menunjukkan penanganan pesan WM_NCHITTEST saat DwmDefWindowProc tidak menanganinya. Untuk melihat kode dari fungsi HitTestNCA
yang dipanggil, lihat Lampiran C: Fungsi HitTestNCA.
// 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;
}
}
Lampiran A: Contoh Prosedur Jendela
Sampel kode berikut menunjukkan prosedur jendela dan fungsi pekerja pendukungnya yang digunakan untuk membuat aplikasi bingkai kustom.
//
// 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;
}
Lampiran B: Melukis Judul Keterangan
Kode berikut menunjukkan cara melukis judul keterangan pada bingkai yang diperluas. Fungsi ini harus dipanggil dari dalam panggilan BeginPaint dan 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);
}
}
Lampiran C: Fungsi HitTestNCA
Kode berikut menunjukkan fungsi HitTestNCA
yang digunakan dalam Mengaktifkan Pengujian Hit untuk Bingkai Kustom. Fungsi ini menangani logika pengujian hit untuk WM_NCHITTEST ketika DwmDefWindowProc tidak menangani pesan.
// 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];
}
Topik terkait