使用 Win32 的带有主题的透明单选按钮控件
我正在尝试在启用主题时仅使用 Win32 制作具有透明背景的单选按钮控件。这样做的原因是允许将单选按钮放置在图像上并显示图像(而不是灰色的默认控件背景)。
开箱即用的情况是,该控件将具有灰色的默认控件背景,并且通过处理 WM_CTLCOLORSTATIC
或 WM_CTLCOLORBTN
来更改此背景的标准方法(如下所示)不起作用:
case WM_CTLCOLORSTATIC:
hdcStatic = (HDC)wParam;
SetTextColor(hdcStatic, RGB(0,0,0));
SetBkMode(hdcStatic,TRANSPARENT);
return (LRESULT)GetStockObject(NULL_BRUSH);
break;
到目前为止,我的研究表明,所有者抽奖是实现这一目标的唯一方法。我已经成功地使用“所有者绘制”单选按钮实现了大部分功能 - 通过下面的代码,我有一个单选按钮和透明背景(背景在 WM_CTLCOLORBTN
中设置)。但是,使用此方法会切断无线电检查的边缘 - 我可以通过取消对函数 DrawThemeParentBackgroundEx
的调用的注释来恢复它们,但这会破坏透明度。
void DrawRadioControl(HWND hwnd, HTHEME hTheme, HDC dc, bool checked, RECT rcItem)
{
if (hTheme)
{
static const int cb_size = 13;
RECT bgRect, textRect;
HFONT font = (HFONT)SendMessageW(hwnd, WM_GETFONT, 0, 0);
WCHAR *text = L"Experiment";
DWORD state = ((checked) ? RBS_CHECKEDNORMAL : RBS_UNCHECKEDNORMAL) | ((bMouseOverButton) ? RBS_HOT : 0);
GetClientRect(hwnd, &bgRect);
GetThemeBackgroundContentRect(hTheme, dc, BP_RADIOBUTTON, state, &bgRect, &textRect);
DWORD dtFlags = DT_VCENTER | DT_SINGLELINE;
if (dtFlags & DT_SINGLELINE) /* Center the checkbox / radio button to the text. */
bgRect.top = bgRect.top + (textRect.bottom - textRect.top - cb_size) / 2;
/* adjust for the check/radio marker */
bgRect.bottom = bgRect.top + cb_size;
bgRect.right = bgRect.left + cb_size;
textRect.left = bgRect.right + 6;
//Uncommenting this line will fix the button corners but breaks transparency
//DrawThemeParentBackgroundEx(hwnd, dc, DTPB_USECTLCOLORSTATIC, NULL);
DrawThemeBackground(hTheme, dc, BP_RADIOBUTTON, state, &bgRect, NULL);
if (text)
{
DrawThemeText(hTheme, dc, BP_RADIOBUTTON, state, text, lstrlenW(text), dtFlags, 0, &textRect);
}
}
else
{
// Code for rendering the radio when themes are not present
}
}
上面的方法是从 WM_DRAWITEM 调用的,如下所示:
case WM_DRAWITEM:
{
LPDRAWITEMSTRUCT pDIS = (LPDRAWITEMSTRUCT)lParam;
hTheme = OpenThemeData(hDlg, L"BUTTON");
HDC dc = pDIS->hDC;
wchar_t sCaption[100];
GetWindowText(GetDlgItem(hDlg, pDIS->CtlID), sCaption, 100);
std::wstring staticText(sCaption);
DrawRadioControl(pDIS->hwndItem, hTheme, dc, radio_group.IsButtonChecked(pDIS->CtlID), pDIS->rcItem, staticText);
SetBkMode(dc, TRANSPARENT);
SetTextColor(hdcStatic, RGB(0,0,0));
return TRUE;
}
所以我的问题是我想的两个部分:
- 我是否错过了其他一些方法来实现我想要的结果?
- 是否可以用我的代码修复按钮剪角问题并且仍然具有透明背景
I am trying to make a radio button control with a transparent background using only Win32 when themes are enabled. The reason for doing this is to allow a radio button to be placed over an image and have the image show (rather than the grey default control background).
What happens out of the box is that the control will have the grey default control background and the standard method of changing this by handling either WM_CTLCOLORSTATIC
or WM_CTLCOLORBTN
as shown below does not work:
case WM_CTLCOLORSTATIC:
hdcStatic = (HDC)wParam;
SetTextColor(hdcStatic, RGB(0,0,0));
SetBkMode(hdcStatic,TRANSPARENT);
return (LRESULT)GetStockObject(NULL_BRUSH);
break;
My research so far indicates that Owner Draw is the only way to achieve this. I've managed to get most of the way with an Owner Draw radio button - with the code below I have a radio button and a transparent background (the background is set in WM_CTLCOLORBTN
). However, the edges of the radio check are cut off using this method - I can get them back by uncommenting the call to the function DrawThemeParentBackgroundEx
but this breaks the transparency.
void DrawRadioControl(HWND hwnd, HTHEME hTheme, HDC dc, bool checked, RECT rcItem)
{
if (hTheme)
{
static const int cb_size = 13;
RECT bgRect, textRect;
HFONT font = (HFONT)SendMessageW(hwnd, WM_GETFONT, 0, 0);
WCHAR *text = L"Experiment";
DWORD state = ((checked) ? RBS_CHECKEDNORMAL : RBS_UNCHECKEDNORMAL) | ((bMouseOverButton) ? RBS_HOT : 0);
GetClientRect(hwnd, &bgRect);
GetThemeBackgroundContentRect(hTheme, dc, BP_RADIOBUTTON, state, &bgRect, &textRect);
DWORD dtFlags = DT_VCENTER | DT_SINGLELINE;
if (dtFlags & DT_SINGLELINE) /* Center the checkbox / radio button to the text. */
bgRect.top = bgRect.top + (textRect.bottom - textRect.top - cb_size) / 2;
/* adjust for the check/radio marker */
bgRect.bottom = bgRect.top + cb_size;
bgRect.right = bgRect.left + cb_size;
textRect.left = bgRect.right + 6;
//Uncommenting this line will fix the button corners but breaks transparency
//DrawThemeParentBackgroundEx(hwnd, dc, DTPB_USECTLCOLORSTATIC, NULL);
DrawThemeBackground(hTheme, dc, BP_RADIOBUTTON, state, &bgRect, NULL);
if (text)
{
DrawThemeText(hTheme, dc, BP_RADIOBUTTON, state, text, lstrlenW(text), dtFlags, 0, &textRect);
}
}
else
{
// Code for rendering the radio when themes are not present
}
}
The method above is called from WM_DRAWITEM as shown below:
case WM_DRAWITEM:
{
LPDRAWITEMSTRUCT pDIS = (LPDRAWITEMSTRUCT)lParam;
hTheme = OpenThemeData(hDlg, L"BUTTON");
HDC dc = pDIS->hDC;
wchar_t sCaption[100];
GetWindowText(GetDlgItem(hDlg, pDIS->CtlID), sCaption, 100);
std::wstring staticText(sCaption);
DrawRadioControl(pDIS->hwndItem, hTheme, dc, radio_group.IsButtonChecked(pDIS->CtlID), pDIS->rcItem, staticText);
SetBkMode(dc, TRANSPARENT);
SetTextColor(hdcStatic, RGB(0,0,0));
return TRUE;
}
So my question is two parts I suppose:
- Have I missed some other way to achieve my desired result?
- Is it possible to fix the clipped button corners issue with my code and still have a transparent background
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
经过近三个月的断断续续的研究,我终于找到了一个令我满意的解决方案。我最终发现,由于某种原因,单选按钮边缘没有被 WM_DRAWITEM 中的例程绘制,但是如果我使单选按钮控件的父级在控件周围的矩形中无效,它们就会出现。
由于我找不到一个好的示例,因此我提供了完整的代码(在我自己的解决方案中,我已将所有者绘制的控件封装到他们自己的类中,因此您需要提供一些详细信息,例如是否检查按钮或不是)
这是单选按钮的创建(将其添加到父窗口),还设置 GWL_UserData 并子类化单选按钮:
因为它是所有者绘制,所以我们需要在父窗口过程中处理 WM_DRAWITEM 消息。
这是 DrawControl 方法 - 它可以访问类级别变量以允许管理状态,因为对于所有者绘制,这不会自动处理。
接下来是用于子类化单选按钮控件的窗口过程 - 这
与所有 Windows 消息一起调用,并在传递未处理的消息之前处理几个消息
进入默认过程。
最后,难题的最后一小部分是您需要在正确的时间使控件无效(重绘它)。我最终发现,使父级无效可以让绘图 100% 正确地工作。这导致了闪烁,直到我意识到我可以通过仅使与无线电检查一样大的矩形无效来逃脱,而不是像我以前那样与包括文本在内的整个控件一样大。
大量的代码和工作本来应该很简单 - 在背景图像上绘制主题单选按钮。希望答案能够减轻其他人的痛苦!
* 对此的一个重要警告是,它仅适用于背景上方的所有者控件(例如填充矩形或图像)100% 正确。不过没关系,因为只有在背景上绘制无线电控件时才需要它。
After looking at this on and off for nearly three months I've finally found a solution that I'm pleased with. What I eventually found was that the radio button edges were for some reason not being drawn by the routine within WM_DRAWITEM but that if I invalidated the radio button control's parent in a rectangle around the control, they appeared.
Since I could not find a single good example of this I'm providing the full code (in my own solution I have encapsulated my owner drawn controls into their own class, so you will need to provide some details such as whether the button is checked or not)
This is the creation of the radiobutton (adding it to the parent window) also setting GWL_UserData and subclassing the radiobutton:
Since it is owner draw we need to handle the WM_DRAWITEM message in the parent window proc.
Here is the DrawControl method - it has access to class level variables to allow state to be managed since with owner draw this is not handled automatically.
Next is the window proc that is used to subclass the radio button control - this
is called with all windows messages and handles several before then passing unhandled
ones on to the default proc.
Finally the last little piece of the puzzle is that you need to invalidate the control (redraw it) at the right times. I eventually found that invalidating the parent allowed the drawing to work 100% correctly. This was causing flicker until I realised that I could get away by only invalidating a rectangle as big as the radio check, rather than as big as the whole control including text as I had been.
A lot of code and effort for something that should be simple - drawing a themed radio button over a background image. Hopefully the answer will save someone else some pain!
* One big caveat with this is it only works 100% correctly for owner controls that are over a background (such as a fill rectangle or an image). That is ok though, since it is only needed when drawing the radio control over a background.
我前段时间也做过这个。我记得关键是像往常一样创建(单选)按钮。父级必须是对话框或窗口,而不是选项卡控件。您可以采用不同的方式,但我为对话框创建了一个内存 dc (m_mdc) 并在其上绘制了背景。然后为您的对话框添加
OnCtlColorStatic
和OnCtlColorBtn
:该代码使用了一些类似于 MFC 的内部类和函数,但我认为您应该明白这一点。正如您所看到的,它从内存直流中绘制这些控件的背景,这是关键。
尝试一下,看看是否有效!
编辑:如果您向对话框添加选项卡控件并将控件放在选项卡上(我的应用程序中就是这种情况),您必须捕获它的背景并将其复制到对话框的内存 dc 中。这是一个有点丑陋的黑客,但它有效,即使机器正在运行一些使用渐变选项卡背景的奢侈主题:
另一个有趣的点,虽然我们现在很忙,但要让它全部不闪烁,创建父级和子级(按钮、静态、选项卡等)具有 WS_CLIPCHILDREN 和 WS_CLIPSIBLINGS 样式。创建顺序很重要:首先创建放置在选项卡上的控件,然后创建选项卡控件。不是相反(尽管感觉更直观)。这是因为选项卡控件应该剪掉被其上的控件遮挡的区域:)
I've done this some time ago as well. I remember the key was to just create the (radio) buttons as usual. The parent must be the dialog or window, not a tab control. You could do it differently but I created a memory dc (m_mdc) for the dialog and painted the background on that. Then add the
OnCtlColorStatic
andOnCtlColorBtn
for your dialog:The code uses some in-house classes and functions similar to MFC, but I think you should get the idea. As you can see it draws the background of these controls from the memory dc, that's key.
Give this a try and see if it works!
EDIT: If you add a tab control to the dialog and put the controls on the tab (that was the case in my app) you must capture it's background and copy it to the memory dc of the dialog. It's a bit of an ugly hack but it works, even if the machine is running some extravagant theme that uses a gradient tab background:
Another interesting point, while we're busy now, to get it all non-flickering create the parent and children (buttons, statics, tabs etc) with the WS_CLIPCHILDREN and WS_CLIPSIBLINGS style. The the order of creation is essential: First create the controls you put on the tabs, then create the tab control. Not the other way around (although it feels more intuitive). That because the tab control should clip the area obscured by the controls on it :)
我不能立即尝试这个,但据我记得,你不需要所有者抽奖。您需要执行以下操作:
WM_ERASEBKGND
返回 1。WM_CTLCOLORSTATIC
调用DrawThemeParentBackground
来绘制背景。WM_CTLCOLORSTATIC
返回GetStockObject(NULL_BRUSH)
。I can't immediately try this out, but so far as I recall, you don't need owner draw. You need to do this:
WM_ERASEBKGND
.DrawThemeParentBackground
fromWM_CTLCOLORSTATIC
to draw the background there.GetStockObject(NULL_BRUSH)
fromWM_CTLCOLORSTATIC
.他们的形象已关闭。
BS_PATTERN 样式 CreateBrushIndirect
通常的方案 - 我们返回该画笔的句柄以回复 COLOR -
消息(WM_CTLCOLORSTATIC)。
image to them closed.
BS_PATTERN style CreateBrushIndirect
usual scheme - we return handle to this brush in reply to COLOR -
the message (WM_CTLCOLORSTATIC).
我不知道为什么你做得这么困难,这最好通过 CustomDrawing 解决
这是我的 MFC 处理程序,用于在 CTabCtrl 控件上绘制笔记本。我不太确定为什么需要膨胀矩形,因为如果我不这样做,就会绘制黑色边框。
MS 制造的另一个概念性错误是恕我直言,我必须覆盖 PreErase 绘图阶段而不是 PostErase。但如果我稍后这样做,复选框就会消失。
I have no idea why you are doing it so difficult, this is best solved via CustomDrawing
This is my MFC Handler to draw a Notebook on a CTabCtrl control. I'm not really sure why i need to Inflate the Rectangle, because if i don't do it a black border is drawn.
And another conceptional bug MS made is IMHO that i have to overwrite the PreErase drawing phase instead of the PostErase. But if i do the later the checkbox is gone.