Treeview node with transparent text background instead of default white - c++

I am using custom draw to try and create transparent tree view ( for now I am testing when Visual Styles are enabled ).
My CDDS_PREPAINT handler works fine, tree has parent's background bitmap drawn properly.
I tried to add CDDS_ITEMPREPAINT handler where I use SetBkColor( ((LPNMCUSTOMDRAW)lParam)->hdc, TRANSPARENT ); and return CDRF_NEWFONT, but that failed. Node is drawn with default white background.
How can I make item's text background transparent?
Thank you.
Best regards.
Below is the illustrative code snippet:
switch( ((LPNMCUSTOMDRAW)lParam)->dwDrawStage )
{
case CDDS_PREPAINT:
{
DrawThemeParentBackground(
((LPNMCUSTOMDRAW)lParam)->hdr.hwndFrom,
((LPNMCUSTOMDRAW)lParam)->hdc,
&((LPNMCUSTOMDRAW)lParam)->rc );
// since tree is in dialog box we need below statement
SetWindowLongPtr( hDlg, DWLP_MSGRESULT, (LONG_PTR)CDRF_NOTIFYITEMDRAW );
return TRUE;
}
break;
case CDDS_ITEMPREPAINT : // how to properly handle this ???
{
SetBkMode( ((LPNMCUSTOMDRAW)lParam)->hdc, TRANSPARENT );
SetWindowLongPtr( hDlg, DWLP_MSGRESULT, (LONG_PTR)CDRF_NEWFONT );
return TRUE;
}
break;
}

Unfortunately, this is not easily possible without drawing the items yourself, sorry.
It turns out the Tree View Control has traditionally used ExtTextOut() function internally to draw the item titles. This function takes an explicit flags parameter, where value ETO_OPAQUE says that "the current background color should be used to fill the rectangle".
Because this option is passed as an extra flag and not determined by looking at the current GDI background mode, you can't use SetBkMode() in a custom draw handler to work around this. The background color property of a DC does not support alpha channel either, so it cannot be just set transparent.
Since Common Controls version 6.0 and themed window decorations, this is a bit different, but still not useful in this case: the control just calls DrawThemeBackground() with the TVP_TREEITEM part and the rest is handled according to the theme being used. I see e.g in the default Aero theme in Windows 8.1 that there is this entry:
ClassID | PartID | StateID | Property | Value
---------+--------------+--------------+-----------------+--------------
TreeView | TVP_TREEITEM | TREIS_NORMAL | FILLCOLOR:COLOR | 255, 255, 255
So, what are the alternatives?
There is always an option to return CDRF_SKIPDEFAULT for the CDDS_ITEMPREPAINT event and do all the display operations yourself. But then you need to take care of drawing even the lines, [+] boxes, selection and focus rectangles and everything else.
Another option might be to leave the title text stored in the control empty, then add it in CDDS_ITEMPOSTPAINT - i.e. use TVM_GETITEMRECT with wParam=TRUE to get the text rectangle and draw the real text there after everything else has been already painted by the control. But this method fails too, because some tiny opaque rectangle is apparently drawn even for empty text. You'd need to erase that artifact first, only then proceed with drawing the text youself. Coming up with a code which does work for all combinations of item state seemed tricky.

Related

How to clear label caption?

I am using label with OnCtrlcolor event:
I have set the background color of the label to be the same as the form,
if (iD == IDCmylabel)
{
pDC->SetTextColor(blue);
COLORREF normal = RGB(245, 245, 245);
pDC->SetBkColor(normal);
return (HBRUSH)GetStockObject(NULL_BRUSH);
}
So I was thinking to use toogle:
SetWindowTextW("abc..."); // will show the color as expected.
SetWindowTextW(nullptr); // will remove the text color.
However this is not working for me (the caption didn't redraw because it still there).
How do I fix this?
NULL_BRUSH is a brush that instructs the system to turn any painting operations that use that brush into no-ops. Using it doesn't actually make the control transparent. It just appears to be transparent until (part of it) has been painted.
If you want a control that has a particular background color, irrespective of the the size of text displayed, you're going to have to provide a solid color brush.
The easiest way to do this would be to return a DC_BRUSH, with an accompanying call to SetDCBrushColor to request the color, i.e.
if (iD == IDCmylabel) {
pDC->SetTextColor(blue);
COLORREF normal = RGB(245, 245, 245);
// Still required so that the text background matches that of the rest
pDC->SetBkColor(normal);
// Request brush color for the control background
pDC->SetDCBrushColor(normal);
// Note: Stock objects do not need to be freed by client code
return (HBRUSH)GetStockObject(DC_BRUSH);
}
With that you can call SetWindowText with arbitrary parameters and get the result you are looking for.
Someone needs to erase the background under the old text.
You are returning NULL_BRUSH, so "erase background" does nothing.
Return the solid brush of the color RGB(245, 245, 245). You may also need to call Invalidate for that window after setting new text.

How Do I Set the Background Color of buttons including a Checkbox button?

How Do I Set the Background Color of buttons including a Checkbox button?
I struggled to find the answer to this today - thinking it should be simple to answer this, but the information I stumbled on was less than helpful, so at the risk of duplicating stuff that's out there but I couldn't find, I'll make this quick'n'dirty how-to...
All 'Button' class windows send WM_CTLCOLORSTATIC to their parent window, which can then call ::SetBkColor((HDC)wParam, rgbBkColor), and return a brush for that color.
If this is all using system colors, then the brush handle doesn't need to be managed, you can simply ask for the ::GetSysColor(sysIndex), and return the ::GetSysColorBrush(sysIndex) for the returned brush.
If you're using a custom color, then you'll need to create your own brush and manage the handle for that.
I needed this code for a Message Box replacement, which has the upper part using a white background, and the lower part using a gray background, per the Windows standard message box. So my static control (icon) needed to be white, while my other buttons (including a "Don't ask again" checkbox) needed to have a gray background (checkboxes normally have a white background).
So, I handle WM_ERASEBKGND to paint the two portions of the background correctly, and then I handle WM_CLTLCOLORSTATIC to ensure that all buttons are properly "transparent" for the background that they appear on. In my case, the I used a "Static" control for the icon, which draws its background in gray, and a couple of push-buttons plus a checkbox button - which a checkbox button always paints its background in white, so both required a fix.
My example is using MFC, but hopefully you can translate that trivially enough for your purposes:
// add to the message map:
ON_MESSAGE(WM_CTLCOLORSTATIC, OnCtlColorStatic)
// create the implementation:
LRESULT CRTFMessageBox::OnCtlColorStatic(WPARAM wParam, LPARAM lParam)
{
// buttons and static controls (icon) send WM_CTLCOLORSTATIC, so we can force them to use the correct background color here...
const HDC hdc = (HDC)wParam;
const int idc = ::GetDlgCtrlID((HWND)lParam);
// choose a system color or brush based on if this is icon (static) or another control (a button)
const int idx = idc == IDC_STATIC ? COLOR_WINDOW : COLOR_3DFACE;
// select system color
::SetBkColor(hdc, GetSysColor(idx));
// return system brush (which we don't need to delete!)
return (LRESULT)GetSysColorBrush(idx);
}

How can I erase the window background in an owner drawn static control?

I am currently developing a kind of "WinMerge" clone, and currently I am trying to implement a custom scrollbar which should later represent both compared files as a rectangle in the background each.
This is what it looks like at the startup:
However, after scrolling around a little bit, this is what I end up with:
As you can clearly see, only those parts look correct which I explicitely paint over in my paint routine:
void LocationPane::OnPaint(CDCHandle dc)
{
DefWindowProc();
dc = GetDC();
DrawLocationPaneFigures(dc);
}
This is how my control is configured in my .rc file:
CONTROL "",IDC_LOCATIONPANE,"Static",SS_OWNERDRAW | SS_NOTIFY | WS_BORDER | WS_GROUP,7,21,91,541
As you can see, it is an owner drawn control.
How can I erase the background for this control while repainting it?
A static control with SS_OWNERDRAW style receives a WM_DRAWITEM message when it needs to be redrawn.
So first you need to replace your OnPaint() handler by a handler for WM_DRAWITEM. Instead of calling GetDC() use the device context supplied to you in the DRAWITEMSTRUCT.
To erase the background it's generally best to do it as part of the regular painting code to reduce flickering (by calling FillRect() for instance).
I suggest to always draw the whole client area of your control. Then you may handle WM_ERASEBKGND to return TRUE without calling DefWindowProc() to reduce flickering even more.

Set dialog window's border color

The root question here is this: How do I set the color for the border around a window (more specifically, a dialog window)?
I have a dialog window which pops up with an alert. Due to the critical safety nature of the alert, there is a requirement that some parts of the window be red, including the dialog's window-border. When I got this requirement in, I thought it was a good idea. Seems reasonable and simple enough.
The application uses X/motif for its graphics. I started by making other requested parts red, such as the acknowledgement button. Getting everything else done was simple enough by changing graphics contexts and color resources.
The dialog's border, however, has been a pain. There is an XmNborderColor resource, so I tried changing that. It didn't seem to work. Eventually, after trying to set it for different widgets (frame and it's ancestors), I did the following out of desparation:
Widget w = button;
for(int i = 0; i <= 20; i += 1)
{
printf("i = %d, w = %d\n", i, w);
if(w <= 0) break;
XtVaSetValues( w, XmNborderColor, border, NULL);
w = XtParent(w);
}
I did that to just set it on everything from the button to the root and everything between.
After doing some more research, I realized that I might need to be changing the window attributes instead, such as via XChangeWindowAttributes(display, window, mask, values). The structure for the values includes a border_pixel, which I'm assuming is the border color but can't find confirmation on that - documentation just says it's for setting "the border pixel." Fortunately, there is a convenience function for setting just the border pixel so that you do not need to pass an entire values structure; the convenience function that changes only the border pixel is XSetWindowBorder(display, window, border_pixel).
So I wanted to try that. I now have:
// control_area is the widget containing the other
XSetWindowBorder(XtDisplay(shell), window, border);
shell is set elsewhere with the following function:
Widget myClass :: createShell( Widget parent, string title )
{
while( !XtIsApplicationShell(parent) )
{
parent = XtParent( parent );
}
shell = XtVaCreatePopupShell( name, xmDialogShellWidgetClass, parent,
XtNvisual, visual, // visual, colormap, depth are class member variables
XtNcolormap, colormap,
XtNdepth, depth,
NULL );
XtVaSetValues( shell,
XmNmwmDecorations, MWM_DECOR_BORDER,
XtNtitle, const_cast<char*> (title.c_str()),
XmNmwmFunctions, NO_FUNCTIONS,
XmNresizePolicy, XmRESIZE_NONE,
NULL );
return shell;
}
From another place in the code, it looks like window might be a reference to the root window - maybe that's the problem?
I am surprised by the lack of information about this and by how difficult it has been to find a direct answer.
How do I set the border color? If I should be using XSetWindowBorder(), what am I doing wrong? If it's because my window variable might not be referencing the correct window, how do I get the a reference to the correct window knowing the shell and contained widgets (maybe I should make a separate question out of this one if this question doesn't get traction)?
The colour of the border is most likely controlled by your window manager unless you are running without a window manager. You need to override the default colour for that specific window in your ~/.Xdefaults file. Something like:
[title]*bordercolor: red
Where [title] is the same as the string you pass to createShell().

Listview flickers on Win32 dialog when removing and re-adding all items and all columns

Consider a plain Win32 dialog with listview control (in report mode) written in C++. Upon a certain event all items and all columns are deleted and new columns and items are created. Basically, as content changes, columns are automatically generated based on content.
When old items/columns are removed and new ones added, listview flickers like hell. I have tried WM_SETREDRAW and LockWindowUpdate() with no change to visual experience.
I have even set extended listview style LVS_EX_DOUBLEBUFFER and that didn't help at all.
The parent dialog has WS_CLIPCHILDREN set.
Any suggestions how to make this work with as little flicker as possible? I am thinking of using two listviews, alternating visibility, using the hidden one as a back buffer but this sounds like an overkill. There must be an easy way.
The default list control painting is pretty flawed. But there is a simple trick to implement your own double-buffering technique:
CMyListCtrl::OnPaint()
{
CRect rcClient;
GetClientRect(rcClient);
CPaintDC dc(this);
CDC dcMem;
dcMem.CreateCompatibleDC(&dc);
CBitmap bmMem;
bmMem.CreateCompatibleBitmap(&dc, rcClient.Width(), rcClient.Height());
CBitmap* pbmOld = dcMem.SelectObject(&bmMem);
dcMem.FillSolidRect(rcClient, ::GetSysColor(COLOR_WINDOW));
this->DefWindowProc(WM_PAINT, (WPARAM)dcMem.m_hDC, (LPARAM)0);
dc.BitBlt(0,0,rcClient.Width(), rcClient.Height(), &dcMem, 0, 0, SRCCOPY);
dcMem.SelectObject(pbmOld);
CHeaderCtrl* pCtrl = this->GetHeaderCtrl();
if (::IsWindow(pCtrl->GetSafeHWnd())
{
CRect aHeaderRect;
pCtrl->GetClientRect(&aHeaderRect);
pCtrl->RedrawWindow(&aHeaderRect);
}
}
This will create a bitmap and then call the default window procedure to paint the list control into the bitmap and then blitting the contents of the bitmap into the paint DC.
You should also add a handler for WM_ERASEBKGND:
BOOL CMyListCtrl::OnEraseBkgnd(CDC* pDC)
{
return TRUE;
}
This will stop the control from always erasing the background before a redraw.
You can optimize the OnPaint further if you add a member variable for the bitmap and only (re)create it when the size of the window changed (because always creating a bitmap may be costly depending on the size of the window).
This should work pretty well.
After trying many things and most of all humbagumba's suggestions I have come to a very simple conclusion. LockWindowUpdate is everybody's friend in this sort of situation. I am not sure how come it failed to work for me the first time but after custom painting failed to deliver in all situations I have tried LockWindowUpdate once again and it worked!
Basically, just wrap all work on listview in a LockWindowUpdate(hWnd) and LockWindowUpdate(NULL) and things work beautifully. There is not even a scrollbar flicker any more.
Just make sure not to nest LockWindowUpdate as only one window can be locked at a time.