Windows DPI变更后CComboBox自定义控件下拉项不显示问题求助
自定义可变高度自绘组合框高DPI下显示异常问题
我基于CComboBox实现了带有CBS_OWNERDRAWVARIABLE样式的自定义组合框:
- DPI为96(系统「缩放与布局」设为100%)时,下拉项显示正常
- DPI大于96(缩放比例超过100%,如125%)时,下拉项无法显示
- 将样式改为
CBS_OWNERDRAWFIXED后,所有场景下均显示正常
显示效果:
- 缩放100%正常效果:

- 缩放125%异常效果:

以下是DrawItem和MeasureItem的代码片段:
void CMyComboBox::MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct) { if (lpMeasureItemStruct->itemID != -1) { lpMeasureItemStruct->itemHeight = 16; } } void CMyComboBox::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) { ASSERT(lpDrawItemStruct->CtlType == ODT_COMBOBOX); LPCTSTR lpszText = (LPCTSTR)lpDrawItemStruct->itemData; ASSERT(lpszText != NULL); CDC dc; dc.Attach(lpDrawItemStruct->hDC); // Save these value to restore them when done drawing. COLORREF crOldTextColor = dc.GetTextColor(); COLORREF crOldBkColor = dc.GetBkColor(); // If this item is selected, set the background color // and the text color to appropriate values. Erase // the rect by filling it with the background color. if ((lpDrawItemStruct->itemAction & ODA_SELECT) && (lpDrawItemStruct->itemState & ODS_SELECTED)) { dc.SetTextColor(::GetSysColor(COLOR_HIGHLIGHTTEXT)); dc.SetBkColor(::GetSysColor(COLOR_HIGHLIGHT)); dc.FillSolidRect(&lpDrawItemStruct->rcItem, ::GetSysColor(COLOR_HIGHLIGHT)); } else { dc.FillSolidRect(&lpDrawItemStruct->rcItem, crOldBkColor); } // Draw the text. dc.DrawText( lpszText, (int)_tcslen(lpszText), &lpDrawItemStruct->rcItem, DT_CENTER | DT_SINGLELINE | DT_VCENTER); // Reset the background color and the text color back to their // original values. dc.SetTextColor(crOldTextColor); dc.SetBkColor(crOldBkColor); dc.Detach(); }
问题原因
核心问题是MeasureItem中硬编码的itemHeight未做DPI缩放适配。在高DPI环境下,系统会对控件的布局坐标进行缩放,但固定的16像素高度没有按当前DPI比例放大,导致下拉项实际显示高度被压缩到几乎不可见(比如125%缩放时,16px逻辑像素对应12.8px物理像素,可能被系统布局裁剪)。而CBS_OWNERDRAWFIXED样式下,系统会自动处理高度的DPI缩放,因此显示正常。
解决方案
方案一:对itemHeight做DPI缩放适配
在MeasureItem中根据当前窗口的DPI值,计算缩放后的项高度:
void CMyComboBox::MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct) { if (lpMeasureItemStruct->itemID != -1) { // 获取当前窗口的DPI(适用于Per-Monitor DPI感知程序) UINT dpi = GetDpiForWindow(m_hWnd); // 以96为基准DPI,计算缩放后的高度 int scaledHeight = MulDiv(16, dpi, 96); lpMeasureItemStruct->itemHeight = scaledHeight; } }
如果程序是系统DPI感知(而非Per-Monitor),可通过屏幕DC获取DPI:
void CMyComboBox::MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct) { if (lpMeasureItemStruct->itemID != -1) { CDC dc; dc.Attach(::GetDC(NULL)); UINT dpiY = dc.GetDeviceCaps(LOGPIXELSY); dc.Detach(); int scaledHeight = MulDiv(16, dpiY, 96); lpMeasureItemStruct->itemHeight = scaledHeight; } }
方案二:开启Per-Monitor DPI感知
在应用程序清单文件中添加Per-Monitor V2 DPI感知配置,确保系统能正确向控件传递当前显示器的DPI信息:
<application xmlns="urn:schemas-microsoft-com:asm.v3"> <windowsSettings> <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">True/PM</dpiAware> <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness> </windowsSettings> </application>
内容的提问来源于stack exchange,提问作者latosvarun
相关产品推荐
相关产品推荐

