I'm playing around with drawing my own custom controls using the uxTheme library in Windows, and I can't work out why my control doesn't look like the regular Windows control that (supposedly) uses the same theme I'm using:
The above image shows a standard Windows ComboBox (top) and my custom control drawn using the ComboBox theme (bottom). What I can't work out is why the border from my control is a different shape and colour to the standard control.
In my class constructor I open the theme data:
mComboTheme = OpenThemeData( hwnd, L"COMBOBOX" );
And then in the handler for WM_PAINT I'm just drawing two parts of the ComboBox components:
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc;
RECT client;
if( GetUpdateRect( hwnd, &ps.rcPaint, false ))
{
hdc = BeginPaint( hwnd, &ps );
GetClientRect( hwnd, &client );
if( IsThemeBackgroundPartiallyTransparent( mComboTheme, CP_BACKGROUND, CBXS_HOT ))
{
DrawThemeParentBackground( hwnd, hdc, &ps.rcPaint );
}
DrawThemeBackground( mComboTheme, hdc, CP_BACKGROUND, CBXS_HOT, &client, &ps.rcPaint );
client.left = client.right - 20;
DrawThemeBackground( mComboTheme, hdc, CP_DROPDOWNBUTTONRIGHT, CBXSR_HOT, &client, ps.rcPaint );
EndPaint( *this, &ps );
}
break;
}
Any suggestions as to why these two controls don't look the same would be greatly appreciated.
Thanks,
James
You called DrawThemeBackground with CP_BACKGROUND and CP_DROPDOWNBUTTONRIGHT. Perhaps you should also call it with CP_BORDER if you want the border to match the standard combobox?
Related
I'm trying to draw something in the title bar area to represent an X since there is no WS_CAPTION, it just uses WS_EX_TOOLWINDOW | WS_EX_TOPMOST and WS_POPUP|WS_THICKFRAME. But I can't get anything to draw anywhere. I did a test below to just fill it all in red, but nothing changed. What am I doing wrong or missing?
case WM_NCACTIVATE:
case WM_NCPAINT:
{
// call default handler (I've tried it both ways, with and without DefWindowProc)
::DefWindowProc(hwnd, umsg, wparam, lparam);
HDC hdc;
if ((hdc=::GetWindowDC(hwnd))!=NULL) {
// Paint into this DC
RECT rcwin;
if (::GetWindowRect(hwnd, &rcwin)) {
HBRUSH hbrush=::CreateSolidBrush(RGB(255, 0, 0));
if (hbrush) {
rcwin.right-=rcwin.left;
rcwin.bottom-=rcwin.top;
rcwin.left=rcwin.top=0;
::FillRect(hdc, &rcwin, hbrush);
::DeleteObject(hbrush);
}
}
::ReleaseDC(hwnd, hdc);
}
return 0;
}
Based on a Link from Remy about the evil WM_NCPAINT, converted from the pascal version to the C++ version below. It works as well as the link in stackoverflow but again, only if WS_CAPTION is provided. I'm just posting here for completeness.
case WM_NCPAINT:
{
#ifndef DCX_USESTYLE
#define DCX_USESTYLE 0x00010000
#endif
HDC hdc=::GetDCEx(hwnd, 0, DCX_WINDOW|DCX_USESTYLE);
if (hdc) {
RECT rcclient;
::GetClientRect(hwnd, &rcclient);
RECT rcwin;
::GetWindowRect(hwnd, &rcwin);
POINT ptupleft;
ptupleft.x=rcwin.left;
ptupleft.y=rcwin.top;
::MapWindowPoints(0, hwnd, (LPPOINT) &rcwin, (sizeof(RECT)/sizeof(POINT)));
::OffsetRect(&rcclient, -rcwin.left, -rcwin.top);
::OffsetRect(&rcwin, -rcwin.left, -rcwin.top);
HRGN rgntemp=NULL;
if (wparam==NULLREGION || wparam==ERROR) {
::ExcludeClipRect(hdc, rcclient.left, rcclient.top, rcclient.right, rcclient.bottom);
}
else {
rgntemp=::CreateRectRgn(rcclient.left+ptupleft.x, rcclient.top+ptupleft.y, rcclient.right+ptupleft.x, rcclient.bottom+ptupleft.y);
if (::CombineRgn(rgntemp, (HRGN) wparam, rgntemp, RGN_DIFF)==NULLREGION) {
// nothing to paint
}
::OffsetRgn(rgntemp, -ptupleft.x, -ptupleft.y);
::ExtSelectClipRgn(hdc, rgntemp, RGN_AND);
}
HBRUSH hbrush = ::CreateSolidBrush(RGB(255, 0, 0));
::FillRect(hdc, &rcwin, hbrush);
::DeleteObject(hbrush);
::ReleaseDC(hwnd, hdc);
if (rgntemp!=0) {
::DeleteObject(rgntemp);
}
}
return 0;
}
Do not draw directly in WM_NCACTIVATE. If you need to trigger a repaint, you can use RedrawWindow() for that. Do all of the actual drawing in WM_PAINT/WM_NCPAINT.
When drawing in WM_NCPAINT, the documentation says to use GetDCEx() to get the HDC to draw on. The wParam is an HRGN that you can draw within. You can use GetRgnBox() to get the bounding rectangle of the HRGN, if needed.
case WM_NCPAINT: {
::DefWindowProc(hwnd, umsg, wparam, lparam);
HRGN hrgn = (HRGN)wParam;
HDC hdc = ::GetDCEx(hwnd, hrgn, DCX_WINDOW | DCX_INTERSECTRGN);
HBRUSH hbrush = ::CreateSolidBrush(RGB(255, 0, 0));
::FillRgn(hdc, hrgn, hbrush);
::DeleteObject(hbrush);
::ReleaseDC(hwnd, hdc);
return 0;
}
I found on (at least) Win10 that calling ::GetClipboardData() on a CF_DIBV5 created via Alt-PrtScrn (may be a synthesized format) causes the image to be modified (and basically corrupted).
For example, on the handler for ON_WM_CLIPBOARDUPDATE() the simple loop below will cause the corruption (note that you need to use debug mode so the ::GetClipboardData() is not optimized out).
To test, first don't run your app that processes the clipboard, use Alt-PrntScrn to capture data, then paste it to Paint. Now run the app that process the clipboard (with below sample below). Repeat Alt-PrntScrn process and you'll see it's different where the right side of the captured window ends up on the left and not centered in the area.
void CMainFrame::OnClipboardUpdate()
if (::OpenClipboard(AfxGetMainWnd()->m_hWnd)) {
UINT uformat=0;
while ((uformat=::EnumClipboardFormats(uformat))!=0) {
if (uformat==CF_DIBV5) {
// get the data - run in debug mode so not optimized out
HGLOBAL hglobal=::GetClipboardData(uformat);
}
}
// clean up
::CloseClipboard();
}
}
To enable the handler you need to call AddClipboardFormatListener(GetSafeHwnd()); in something like int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) then RemoveClipboardFormatListener(GetSafeHwnd()); on void CMainFrame::OnDestroy()
So is this a bug in Win10 (and other Windows Versions) or should I be doing something else that the sample isn't doing? (I know other formats exist, but the CF_DBIV5 is what I wanted).
I'm on Version 1903 (OS Build 18362.838)
Note the sample pic has right side items on left and some garbage pixels in lower left. I alt-prtscrn while app running, pasted in paint.
My resolution is 2560x1600.
Here's a link to a project that will cause the problem:
Sample Project
You can find the following description in the documentation :
The red, green, and blue bitfield masks for BI_BITFIELD bitmaps
immediately follow the BITMAPINFOHEADER, BITMAPV4HEADER, and
BITMAPV5HEADER structures. The BITMAPV4HEADER and BITMAPV5HEADER
structures contain additional members for red, green, and blue masks
as follows.
When the biCompression member of BITMAPINFOHEADER is set to BI_BITFIELDS and the function receives an argument of type LPBITMAPINFO, the color masks will immediately follow the header. The color table, if present, will follow the color masks. BITMAPCOREHEADER bitmaps do not support color masks.
When you handle CF_DIBV5 correctly you will draw the image successfully. The following is an example of Win32 C++ you can refer to:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static UINT uFormat = (UINT) -1;
HDC hdcMem = NULL;
RECT rc = {0};
BYTE * pData = NULL;
BITMAPV5HEADER *pDibv5Info = NULL;
switch (message)
{
case WM_CLIPBOARDUPDATE:
{
if (IsClipboardFormatAvailable(CF_DIBV5))
{
uFormat = CF_DIBV5;
::CloseClipboard();
GetClientRect(hWnd, &rc);
InvalidateRect(hWnd, &rc, TRUE);
}
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
switch (uFormat)
{
case CF_DIBV5:
hdcMem = CreateCompatibleDC(hdc);
if (hdcMem != NULL)
{
if (::OpenClipboard(hWnd)) {
HANDLE hglobal = ::GetClipboardData(uFormat);
pData = (BYTE*)GlobalLock(hglobal);
if (pData)
{
pDibv5Info = (BITMAPV5HEADER *)pData;
int offset = pDibv5Info->bV5Size + pDibv5Info->bV5ClrUsed * sizeof(RGBQUAD);
if (pDibv5Info->bV5Compression == BI_BITFIELDS)
offset += 3 * sizeof(DWORD); //three DWORD color masks that specify the red, green, and blue components
pData += offset;
SetDIBitsToDevice(hdc, 20, 20, pDibv5Info->bV5Width, pDibv5Info->bV5Height, 0, 0, 0, pDibv5Info->bV5Height, pData, (BITMAPINFO *)pDibv5Info, 0);
}
GlobalUnlock(hglobal);
::CloseClipboard();
}
}
break;
}
EndPaint(hWnd, &ps);
}
break;
}
The correct image drawn in my application window:
I can reproduce the same issue without code:
if (pDibv5Info->bV5Compression == BI_BITFIELDS)
offset += 3 * sizeof(DWORD);
The corrupted image:
With a class derived from CScrollWindowImpl
void Scroll::DoPaint(CDCHandle hDC)
{
if ( _MemDC==NULL)
return;
RECT r;
//I'd like to update r with rcPaint from the DC's PAINTSTRUCT here
hDC.BitBlt(r.left, r.top, r.right-r.left, r.bottom-r.top,
*_MemDC, r.left, r.top, SRCCOPY);
}
What is the most efficient way of painting the window contents with a WTL ScrollWindow?
The CScrollImpl WM_PAINT doesn't pass the CPaintDC to the derived class OnPaint, which has the PAINTSTRUCT m_ps member with the update RECT rcPaint member.
LRESULT CScrollImpl::OnPaint(UINT, WPARAM wParam, LPARAM, BOOL&) {
T* pT = static_cast<T*>(this);
ATLASSERT(::IsWindow(pT->m_hWnd));
if(wParam != NULL) { // The HDC is sometimes passed in
CDCHandle dc = (HDC)wParam;
dc.SetViewportOrg(-m_ptOffset.x, -m_ptOffset.y);
pT->DoPaint(dc);
}
else {
CPaintDC dc(pT->m_hWnd);
dc.SetViewportOrg(-m_ptOffset.x, -m_ptOffset.y);
pT->DoPaint(CDCHandle(dc));
}
return 0;
}
So I've handled the WM_PAINT message and the best I've achieved so far is BitBlt the whole _MemDC when scrolled, but during an unscrolled redraw to only BitBlt the invalidated rectangle.
UPDATE:
Sometimes rcPaint is larger than the rectangle of the MemDc so the increase in efficiency is negligible and buggy.
Is it really the most efficient or not, however note that WTL Samples include Samples\BmpView project which features CScrollWindowImpl use in CBitmapView class, which displays a visible part of a [supposedly large] image. Specifically, it overrides background erase and paint handlers and demostrates how to do BitBlt for the requested for painting part only.
I am working on a open source project in VC++ and want to change the backcolor of a static control..
hwndRenderMessage = CreateWindow(TEXT("STATIC"), Str("MainWindow.BeginMessage"),
WS_CHILDWINDOW|WS_VISIBLE|WS_CLIPSIBLINGS|SS_CENTER,
0, 0, 0, 0, hwndRenderFrame, NULL, hinstMain, NULL);
SendMessage(hwndRenderMessage, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), TRUE);
and the parent control of this control is
hwndRenderFrame = CreateWindow(OBS_RENDERFRAME_CLASS, NULL,
WS_CHILDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN,
0, 0, 0, 0,
hwndMain, NULL, hinstMain, NULL);
if(!hwndRenderFrame)
CrashError(TEXT("Could not create render frame"));
So how to change the Background color of Static Control..
I google it and getting tha same answer use
case WM_CTLCOLORSTATIC:
{
HDC hdcStatic = (HDC) wParam;
SetTextColor(hdcStatic, RGB(0,0,0));
SetBkColor(hdcStatic, RGB(230,230,230));
return (INT_PTR)CreateSolidBrush(RGB(230,230,230));
}
But there is no switch case in the file so what to do??
Acctually i worked on c# but this is the first time on vc++
I downloaded the OBS source code from sourceforge.
The Window Proc is OBS::RenderFrameProc located in WindowStuff.cpp
At the bottom of the proc (but before the "return"), add:
else if(message == WM_CTLCOLORSTATIC ) {
// HERE YOUR CODE
}
EDIT: Changing Button Background
First, an advice: "don't do that". Buttons are very important and common components of the windows GUI, and their look and feel should be consistent in all applications. Users have ways to customize things for their Desktop, as a whole, and this include "accessibility" issues and behavior. Applications that want do it in their "own special way"s only bring problems.
Second, try this code for changing the "Setting..." button background to an ugly green: Add a case in the WM_NOTIFY message processing in OBS::OBSProc, in the switch(wParam)
case ID_SETTINGS:
if(nmh.code == NM_CUSTOMDRAW)
{
LPNMCUSTOMDRAW lpcd = (LPNMCUSTOMDRAW)lParam;
if (lpcd->dwDrawStage == CDDS_PREPAINT )
{
SetDCBrushColor(lpcd->hdc, RGB(0, 255, 0));
SelectObject(lpcd->hdc, GetStockObject(DC_BRUSH));
LONG lBorders = 0;
LONG lElipse = 5;
RoundRect(lpcd->hdc, lpcd->rc.left + lBorders, lpcd- rc.top + lBorders,
lpcd->rc.right - lBorders, lpcd->rc.bottom - lBorders, lElipse, lElipse);
return CDRF_NOTIFYPOSTPAINT;
}
}
break;
An alternative, with more standard borders:
SetDCBrushColor(lpcd->hdc, RGB(0, 255, 0));
SetDCPenColor(lpcd->hdc, RGB(0, 255, 0));
SelectObject(lpcd->hdc, GetStockObject(DC_BRUSH));
SelectObject(lpcd->hdc, GetStockObject(DC_PEN));
LONG lBorders = 3;
To be complete, you may want to check the uItemState member of lpcd, for the CDIS_HOT flag, changing the color accordingly.
You need to put that code in a window procedure. The window procedure looks like this:
LRESULT CALLBACK RenderMessageWndProc(HWND hWnd, UINT message, WPARAM wParam,
LPARAM lParam)
{
switch (message)
{
case WM_CTLCOLORSTATIC:
// your code goes here
return ....
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
And you need to sub-class your window so that it uses this WndProc. Like this:
SetWindowLongPtr(hwndRenderMessage, GWLP_WNDPROC, (LONG_PTR)RenderMessageWndProc);
If you don't know what a window procedure is, or what sub-classing is, then you really need to step back and learn some basics. For instance, Petzold's classic book Programming Windows is still an excellent starting point.
I am using GDI* plus to do custom drawing, but I have a drawing error when my window gets drawn under a windows explorer window, it looks like this:
As u can see just under the explorer window.. the colors are weird.. the top right are buttons and the checkbox is also a button .. "Are you.." is a static control.. they are all inherited and implemented as custom controls.. when receiving WM_PAINT. I also use a buffered image in WM_PAINT.. anyway.. I can't explain this, any ideas? It works fine when not under windows explorer window, as u can see in the left site of the window.
The checkbox is a button control, use a MSG_OCM_DRAWITEM(OnPaintImpl) handler in which I get the DC as so:
LRESULT OnPaintImpl(UINT ctrlID, LPDRAWITEMSTRUCT lpDIS)
{
ATLASSERT(GdiPlus::IsInitialized());
OnPaintGdiPlus(lpDIS->hDC, lpDIS->rcItem, lpDIS->itemState);
return S_OK;
}
and in my OnPaintGdiPlus(HDC hDC, CRect rc, UINT nState) I do this:
CMemoryDC dcMem(hDC, rc);
Graphics graphics(dcMem);
Rect rcClient = GdiPlus::GetRect(rc);
Everything else is just calling basic drawing functions from the graphics.
In the dialog I get WM_PAINT and handle it here:
LRESULT OnPaintImpl(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
PAINTSTRUCT ps;
HDC hDC = BeginPaint(&ps);
if (ps.rcPaint.right || ps.rcPaint.bottom) // draw rect is defined
{
if (GdiPlus::IsInitialized())
OnPaintGdiPlus(hDC, ps, GetClientRect(m_hWnd));
else
::MessageBox(m_hWnd, L"Graphics mode not initialized properly!", L"Graphics", MB_OK | MB_ICONWARNING);
}
EndPaint(&ps);
return S_OK;
}
Based on this hDC I create a Graphics object and paint using it.
Any other stuff I should add here?