可选择文本的只读组合框






4.52/5 (13投票s)
2004年11月7日
7分钟阅读

104791

2752
一个组合框,其行为类似于完全禁用的组合框,但允许您选择文本。
引言
在编程领域,很少有如此多的精力被投入到如此小的产出中。这个问题是那些必须解决的“痒点”之一;尽管微不足道,但除非解决,否则它无法被搁置——这是只有真正的挑剔者才能理解的东西。问题在于创建一个“禁用”的组合框,该组合框允许您选择编辑框中的文本,或者如果您愿意,也可以选择只读组合框。
背景
根本问题在于,一旦您通过常规方法(禁用)将组合框设为只读,您就无法对其文本进行任何操作。这令人沮丧,因为有时选择和复制很有用。这里有一篇Tim McColl的文章,它改变了禁用组合框的编辑颜色,使其更易读,但却不允许选择。在我自己的一个项目中,我确实设法在不实际禁用组合框的情况下使编辑框中的文本可选择;我只是在“只读”模式下阻止了对箭头鼠标点击和编辑框键盘输入。但随后问题演变成了一个美学问题:箭头仍然令人沮丧地“启用”。
我最初为此问题发布的(被认为是决定性的)解决方案,坦白说,有点丑陋,它包括将组合框重新创建为“简单”组合框,并进行各种邪恶的巫术操作,使其看起来像一个下拉式组合框。这个想法来自Rich Frank,他使用了Paul S. Vickery的RecreateComboBox函数来在简单和下拉式组合框之间切换组合框类型,从而在他不想要任何编辑时隐藏箭头——这并非我想要的,但想象其余部分并不难——只需绘制箭头,大功告成。
这一切都非常狡猾,但它缺乏优雅表明一定有更好的方法。确实有,而且显而易见。当组合框自身绘制时,只需屏蔽掉箭头部分,以防止绘制启用的箭头,然后改画禁用的箭头。
实际上,这个解决方案确实带来了另一个小问题,即下拉列表式组合框不再适用于只读处理,因为它们没有编辑控件,也没有任何其他可能允许选择的子控件。这很容易解决,但涉及一个稍微不干净的技巧。下拉式和下拉列表式组合框都需要以下拉式样式创建,这样我们总能有一个编辑控件来操作。然后,处理程序中的一个标志会确定组合框类型。处理方式几乎相同:只读模式相同,对于可编辑的下拉列表,我们唯一要做的就是阻止更多的按键,以防止文本更改。
如果您想知道我说的“处理程序”是什么意思,它指的是我决定避免在派生类中实现此功能,而是使用子类化。请参阅下面的无需继承部分。
工作原理
首先,我们需要做的是当我们在只读模式下时,让正常的“启用”箭头消失。一旦你知道怎么做,它就会非常简单。首先,你需要知道组合框箭头的确切位置和尺寸,这不像听起来那么简单。我花了很长时间研究边框和边缘,试图让它在所有我能找到的Windows XP主题以及Windows经典模式下都能正常工作,但不行。它从未完全正确。我尝试了主题度量函数,但出于某种原因,我一直没能成功。然后我发现了GetComboBoxInfo()
,然后我狠狠地踢了自己。这个函数在我生活中一直去哪儿了?它只需三行代码就能提供所有尺寸。
有了这些信息,屏蔽函数就变得轻而易举了。
void CReadOnlyComboHandler::MaskComboArrow (CDC& dc) { CRect rectArrow; if (m_bReadOnly) { rectArrow = GetComboArrowRect(); // Exclude the arrow area from the paint update region dc.ExcludeClipRect(&rectArrow); } }
你只需在调用标准的绘制函数之前调用它,然后一切就绪。好的,既然箭头消失了,剩下的就是绘制一个新的、禁用的箭头。唯一复杂的是处理XP主题。为了做到这一点,我使用了David Yuheng Zhao的Visual XP Styles类的一个修改版本,它安全地封装了对XP主题DLL的调用。我做了一些更改,特别是,在检查主题是否正在使用的例程中。我发现需要检查应用程序加载的ComCtl32 DLL的版本以及其他检查,才能确定主题是否已激活。在这里,顺便说一下,在GetComboArrowRect()
中,我们利用了那个漂亮的函数GetComboBoxInfo()
。
void CReadOnlyComboHandler::DrawDisabledDropArrow(CDC* pDC) { ASSERT(m_pCombo != NULL); // If read-only, draw a disabled down-arrow if (m_bReadOnly && m_pCombo && pDC) { CRect rectArrow = GetComboArrowRect(); // Now make the arrow rect the clipping region // and paint the arrow CRgn rgnArrow; rgnArrow.CreateRectRgnIndirect(&rectArrow); pDC->SelectClipRgn(&rgnArrow); // Draw control at RHS of control // If app is themed... if (CVisualStylesXP::GetInstance()->IsAppUsingThemes()) { HTHEME hTheme = CVisualStylesXP::GetInstance()->OpenThemeData(m_pCombo->GetSafeHwnd(), L"COMBOBOX"); CVisualStylesXP::GetInstance()->DrawThemeBackground(hTheme, pDC->GetSafeHdc(), CP_DROPDOWNBUTTON, CBXS_DISABLED, &rectArrow, NULL); CVisualStylesXP::GetInstance()->CloseThemeData(hTheme); } else { // Do the draw pDC->DrawFrameControl(rectArrow, DFC_SCROLL, DFCS_SCROLLDOWN | DFCS_INACTIVE); } }
另一件值得评论的事是,为了阻止箭头键用于更改组合框选择,我们还需要为编辑框安装一个处理程序,并在编辑框处于只读状态时用它来阻止这些按键。以前,我使用的是一个派生编辑类,但现在这种方法更干净。按键处理现在也更精细;您始终可以使用箭头键进行选择,并且在下拉列表模式下,即使您无法编辑,也可以使用向上/向下键更改选择。Ctrl-C(或本地的相应组合键)也应该可以正常复制,而且我认为我设法阻止了所有退格键和删除键。作为最后的润色,编辑处理程序在只读模式下还会阻止剪切和粘贴命令。
一个注意事项:代码只期望一个下拉式或下拉列表式组合框。将其与“简单”组合框一起使用意义不大;我不确定结果是否会安全无害。我建议不要这样做。事实上,与任何非下拉式组合框类型一起使用都有些没意义,因为下拉列表样式没有编辑控件,因此无法选择文本,而这正是本次练习的重点。
在此更新中,我已将CComboBox
指针更改为HWND
,以避免当该类在DLL中使用时出现窗口映射问题。
无需继承
从v1.1版本开始,所有这些都实现在一个“插件”处理程序类中,而不是作为派生组合框类。这允许您将只读功能添加到任何组合框类,包括您自己的派生MuchBetterSuperWidgetCombo
类。嗯,至少这是美好的理论。为此,处理程序会用自己的自定义过程替换组合框的WindowProc
,以进行必要的修改,并对组合框的编辑控件执行相同的操作。我基本上是从GipsySoft的BuddyButton复制了这个想法,但将其实现为一个类而不是一个全局函数。我还包含了一个用于子类化组合框编辑控件的功能,因为这是我需要做的。SubclassComboEdit()
和UnsubclassComboEdit()
将执行您的子类化以及钩子和取消钩子我的处理程序,以使一切正常工作。为组合框添加相同的功能也很简单,但我还没有这样做。
使用代码
真的很容易。您只需创建CReadOnlyComboHandler
类的一个实例,并通过调用Init(pMyCombo)
来初始化您的组合框。您可以在处理程序上调用SetReadOnly(true)
来使其变为只读。如果您想要一个“下拉列表”风格的组合框,请使用下拉式样式创建组合框,然后调用SetDropListMode(true)
。当然,您也可以选择将处理程序包含在您自己派生的组合框类中。我想我应该为那些没有自己派生类并且不想跟踪组合框处理程序的人提供一个包含处理程序的派生组合框类。也许明年吧 :-)。
我很想听听人们的经验,以及有关错误、问题、改进等的报告。
欠款
为了实现如此微薄的目标,我查阅或使用了大量他人的代码。特别是:
- (用于我的原始方法) Paul S. Vickery的RecreateComboBox函数。
- David Yuheng Zhao的Visual XP Styles类。
- (间接) Chris Maunder的GridCtrl中的
GridCellCombo
类。 - 来自GipsySoft的伙伴按钮代码。
历史
- v1.0 - 2004年11月8日 - 发布。
- v1.1 - 2004年11月21日 - 重构为外部处理程序类。兼容下拉列表式组合框。
- v1.3 - 2005年11月22日 - 大幅延迟的更新。组合框不再重新创建,通过屏蔽修复箭头。现在能正确获取所有XP主题的箭头尺寸。将组合框改为
HWND
。