Невозможно изменить цвет диалоговых окон в моем приложении
Я пытаюсь создать «темный режим» для своего приложения Windows C ++ частично для развлечения, частично для того, чтобы попытаться полностью понять сообщение, передаваемое в MFC, но я сталкиваюсь с некоторыми действительно странными проблемами, которые я нигде не могу найти объяснения.
Я потратил большую часть сегодняшнего дня, пытаясь понять это, и постараюсь изо всех сил процитировать множество источников, которые я просмотрел и попытался реализовать.
Я считаю, что успешно написал обработчики сообщений для обоих WM_CTLCOLOR
и WM_ERASEBKGND
на основе примера кода из этого ответа , но они, похоже, не влияют на мои диалоги. Я сократил код здесь, но надеюсь, что предоставил достаточно, чтобы раскрыть мою проблему. Если этого все еще недостаточно, я могу (неохотно) поделиться всем репо.
SoftwareDlg.h
#ifndef _SOFTWAREDLG_H_INCLUDED_
#define _SOFTWAREDLG_H_INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
class SoftwareDlg : public CDialog
{
// Construction
public:
SoftwareDlg(CWnd* pParent = NULL); // standard constructor
// Dialog Data
//{{AFX_DATA(SoftwareDlg)
enum { IDD = IDD_SOFTWARE_DIALOG };
//}}AFX_DATA
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(SoftwareDlg)
public:
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
//}}AFX_VIRTUAL
// Implementation
protected:
BOOL PreTranslateMessage(MSG* pMsg);
CFont m_font;
CRichEditCtrl m_richEditCtrl;
// Generated message map functions
//{{AFX_MSG(SoftwareDlg)
virtual BOOL OnInitDialog();
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
afx_msg void OnTimer(UINT nIDEvent);
afx_msg void OnDestroy();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
public:
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor);
afx_msg HBRUSH CtlColor(CDC* pDC, UINT nCtlColor);
};
/////////////////////////////////////////////////////////////////////////////
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
/////////////////////////////////////////////////////////////////////////////
#endif
/////////////////////////////////////////////////////////////////////////////
SoftwareDlg.cpp
#include "stdafx.h"
#include <Windows.h>
#include "AboutDlg.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
//Windows Dialog inherited function overrides
/////////////////////////////////////////////////////////////////////////////
// SoftwareDlg dialog
/////////////////////////////////////////////////////////////////////////////
SoftwareDlg::SoftwareDlg(CWnd* pParent /*=NULL*/)
: CDialog(SoftwareDlg::IDD, pParent)
{
//{{AFX_DATA_INIT(SoftwareDlg)
//}}AFX_DATA_INIT
// Note that LoadIcon does not require a subsequent DestroyIcon in Win32
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
void SoftwareDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(SoftwareDlg)
//}}AFX_DATA_MAP
}
BEGIN_MESSAGE_MAP(SoftwareDlg, CDialog)
//{{AFX_MSG_MAP(SoftwareDlg)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_WM_TIMER()
ON_WM_DESTROY()
//}}AFX_MSG_MAP
ON_WM_ERASEBKGND()
ON_WM_CTLCOLOR()
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// SoftwareDlg message handlers
/////////////////////////////////////////////////////////////////////////////
BOOL SoftwareDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// Add "About..." menu item to system menu.
// IDM_ABOUTBOX must be in the system command range.
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
CString strAboutMenu;
strAboutMenu.LoadString(IDS_ABOUTBOX);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
// Set the icon for this dialog. The framework does this automatically
// when the application's main window is not a dialog
SetIcon(m_hIcon, TRUE); // Set big icon
CWnd* pPlaceholder = GetDlgItem(IDC_PLACEHOLDER);
if (pPlaceholder)
{
CRect rect;
pPlaceholder->GetClientRect(&rect);
if (!m_richEditCtrl.Create(WS_VISIBLE | ES_READONLY | ES_MULTILINE | ES_AUTOHSCROLL | WS_HSCROLL | ES_AUTOVSCROLL | WS_VSCROLL, rect, this, 0))
return FALSE;
m_font.CreateFont(-11, 0, 0, 0, FW_REGULAR, 0, 0, 0, BALTIC_CHARSET, 0, 0, 0, 0, "Courier New");
m_richEditCtrl.SetFont(&m_font);
}
m_nTimerID = SetTimer(0x1234, 1000, NULL); //Used by OnTimer function to refresh dialog box & OSD
return TRUE; // return TRUE unless you set the focus to a control
}
void SoftwareDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
if ((nID & 0xFFF0) == IDM_ABOUTBOX)
{
CAboutDlg dlgAbout;
dlgAbout.DoModal();
}
else
{
CDialog::OnSysCommand(nID, lParam);
}
}
/////////////////////////////////////////////////////////////////////////////
// If you add a minimize button to your dialog, you will need the code below
// to draw the icon. For MFC applications using the document/view model,
// this is automatically done for you by the framework.
/////////////////////////////////////////////////////////////////////////////
void SoftwareDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // device context for painting
SendMessage(WM_ICONERASEBKGND, (WPARAM)dc.GetSafeHdc(), 0);
// Center icon in client rectangle
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialog::OnPaint();
}
}
HCURSOR SoftwareDlg::OnQueryDragIcon()
{
return (HCURSOR)m_hIcon;
}
void SoftwareDlg::OnTimer(UINT nIDEvent)
{
CDialog::OnTimer(nIDEvent);
}
void SoftwareDlg::OnDestroy()
{
if (m_nTimerID)
KillTimer(m_nTimerID);
m_nTimerID = NULL;
MSG msg;
while (PeekMessage(&msg, m_hWnd, WM_TIMER, WM_TIMER, PM_REMOVE));
CDialog::OnDestroy();
}
BOOL SoftwareDlg::PreTranslateMessage(MSG* pMsg)
{
if (pMsg->message == WM_KEYDOWN)
{
switch (pMsg->wParam)
{
case ' ':
Sleep(1000);
}
}
return CDialog::PreTranslateMessage(pMsg);
}
BOOL SoftwareDlg::OnEraseBkgnd(CDC* pDC)
{
CRect rect;
GetClientRect(&rect);
CBrush myBrush(RGB(255, 0, 0)); // dialog background color
CBrush* pOld = pDC->SelectObject(&myBrush);
BOOL bRes = pDC->PatBlt(0, 0, rect.Width(), rect.Height(), PATCOPY);
pDC->SelectObject(pOld); // restore old brush
return bRes; // CDialog::OnEraseBkgnd(pDC);
}
HBRUSH SoftwareDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
// Are we painting the IDC_MYSTATIC control? We can use
m_brush.CreateSolidBrush(RGB(136, 217, 242));
//if (pWnd->GetDlgCtrlID() == IDD_SOFTWARE_DIALOG)
// Set the text color to red
pDC->SetTextColor(RGB(255, 0, 0));
// Set the background mode for text to transparent so background will show thru.
pDC->SetBkMode(TRANSPARENT);
// Return handle to our CBrush object
hbr = m_brush;
return hbr;
}
HBRUSH SoftwareDlg::CtlColor(CDC* pDC, UINT nCtlColor)
{
HBRUSH myBrush = CreateSolidBrush(RGB(136, 217, 242));
return myBrush;
}
Resource.h
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by Software.rc
//
#define IDM_ABOUTBOX 0x0010
#define IDD_ABOUTBOX 100
#define IDS_ABOUTBOX 101
#define IDD_SOFTWARE_DIALOG 102
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 141
#define _APS_NEXT_COMMAND_VALUE 32792
#define _APS_NEXT_CONTROL_VALUE 1026
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif
На другой вопрос , заданный тем же пользователем ~ 6 месяцев назад, был дан ответ с помощью похожего кода, но с использованием типа фреймворка, который содержит функцию "WinMain" (извините, я пока не могу различить типы 2+). Моя программа не содержит функции WinMain, поэтому я не мог использовать образец кода напрямую ... но еще одним отличием в этом ответе было то, что Дэвиду было сказано поймать WM_CTLCOLORDLG
тип WM_CTLCOLOR
сообщения, а не тип сообщения. Я попытался поймать этот новый тип сообщения, но IntelliSense сказал мне, что он не определен, и это конкретное свойство сообщения полностью отсутствовало в представлении ресурсов диалогового окна:

Я также попытался определить "WM_CTLCOLORDLG"
себя, как описано на странице Microsoft Docs , но продолжал получать сообщения об ошибках, когда пытался обработать это с помощью «ON_MESSAGE».
Мой код не был оригинальным проектом, а был взят из образца с открытым исходным кодом, поставляемого с RTSS . Таким образом, он использует не стандартный (?) «Pch.h», а «stdafx.h» (который, я полагаю, старше?). Я не уверен, актуально ли это, но мне кажется, что это может быть.
Я думаю, что эта проблема также может вызывать у меня множество других проблем с ростом, поэтому любая помощь ОЧЕНЬ приветствуется.
Ответы
Основная проблема с кодом OP заключается в том, что кисть воссоздается каждый раз OnCtlColor
и будет продолжать утечку дескрипторов GDI (отладочная сборка MFC поднимает ASSERT
об этом). Полное пошаговое исправление ниже.
Объявите цвет и кисть как элементы диалога.
class SoftwareDlg : public CDialog { //... protected: COLORREF m_color; CBrush m_brush; //...
Инициализируйте цвет и нанесите кистью
OnInitDialog
.BOOL SoftwareDlg::OnInitDialog() { m_color = RGB(136, 217, 242); m_brush.CreateSolidBrush(m_color); CDialog::OnInitDialog(); //...
Верните кисть из
OnCtlColor
.HBRUSH SoftwareDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) { //... return m_brush; }
Используйте этот цвет,
OnEraseBkgnd
чтобы перекрасить любую видимую область.afx_msg BOOL SoftwareDlg::OnEraseBkgnd(CDC* pDC) { CRect rc; pDC->GetClipBox(&rc); pDC->FillSolidRect(rc, m_color); return TRUE; }
OnInitDialog
Явно установите цвет фона для элемента управления Rich Edit, поскольку элементы управления Rich Edit не используютWM_CTLCOLOR***
сообщения.BOOL SoftwareDlg::OnInitDialog() { //... if (pPlaceholder) { //... if (!m_richEditCtrl.Create(WS_VISIBLE | ES_READONLY | ES_MULTILINE | ES_AUTOHSCROLL | WS_HSCROLL | ES_AUTOVSCROLL | WS_VSCROLL, rect, this, 0)) return FALSE; m_richEditCtrl.SetBackgroundColor(FALSE, m_color); //...
Примечание : при использовании CDialogEx::SetBackgroundColor
подхода , предложенного в другом ответе, OnCtlColor
и OnEraseBkgnd
части покрываются CDialogEx
реализацией. Последний шаг по-прежнему необходим, потому что WM_CTLCOLOR
механизм охватывает только основные элементы управления (статические, редактирование, кнопки и т. Д.) И сам диалог. С другими элементами управления (многофункциональный элемент управления, элемент управления списком и т. Д.) Необходимо обрабатывать каждый отдельно.
Просто используйте CDialogEx
класс, который он поддерживает, CDialogEx::SetBackgroundColor
и он выполняет все функции для статических и кнопочных элементов управления.