No puedo cambiar el color de mis cuadros de diálogo en mi aplicación
Estoy intentando crear un "modo oscuro" para mi aplicación de Windows C ++ parcialmente por diversión, parcialmente para intentar comprender completamente el mensaje que pasa en MFC, pero me encuentro con algunos problemas realmente extraños que no puedo encontrar explicados en ninguna parte.
He pasado la mayor parte del día intentando resolver esto y haré todo lo posible para citar las muchas fuentes que he examinado e intentado implementar.
Creo que escribí con éxito controladores de mensajes para ambos WM_CTLCOLOR
y me WM_ERASEBKGND
basé en el código de ejemplo de esta respuesta , pero no parecen tener ningún efecto en mis diálogos. Corté el código aquí, pero espero haber proporcionado lo suficiente para exponer mi problema. Si eso aún no es suficiente, puedo (a regañadientes) compartir todo el repositorio.
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
Otra pregunta , publicada por el mismo usuario ~ 6 meses antes, fue respondida con un código similar, pero usando el tipo de marco que contiene una función "WinMain" (lo siento, no puedo diferenciar los 2+ tipos todavía). Mi programa no contiene una función WinMain, por lo que no pude usar el código de muestra directamente ... pero otra diferencia en esta respuesta fue que se le dijo a David que capturara un WM_CTLCOLORDLG
tipo de mensaje en lugar del WM_CTLCOLOR
tipo de mensaje. Intenté captar este nuevo tipo de mensaje, pero IntelliSense me dijo que no estaba definido y que esa propiedad de mensaje en particular estaba completamente ausente de la vista de recursos del cuadro de diálogo:

También intenté "WM_CTLCOLORDLG"
definirme como se describe en la página de Microsoft Docs , pero seguí recibiendo mensajes de error cuando intenté manejarlo a través de "ON_MESSAGE".
Mi código no era un proyecto original, sino que se tomó de una muestra de código abierto proporcionada con RTSS . Como tal, no usa el estándar (?) "Pch.h", sino "stdafx.h" (¿que es más antiguo, supongo?). No estoy seguro de si eso es relevante, pero siento que puede serlo.
Creo que este problema también me puede estar causando muchos otros dolores de crecimiento, por lo que cualquier ayuda es GRANDE.
Respuestas
El principal problema con el código de OP es que el pincel se vuelve a crear cada vez OnCtlColor
y seguirá filtrando identificadores de GDI (la versión de depuración de MFC genera un problema al ASSERT
respecto). Complete la solución paso a paso a continuación.
Declare el color y el pincel como miembros del diálogo.
class SoftwareDlg : public CDialog { //... protected: COLORREF m_color; CBrush m_brush; //...
Inicializa el color y aplica el pincel
OnInitDialog
.BOOL SoftwareDlg::OnInitDialog() { m_color = RGB(136, 217, 242); m_brush.CreateSolidBrush(m_color); CDialog::OnInitDialog(); //...
Devuelve el cepillo de
OnCtlColor
.HBRUSH SoftwareDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) { //... return m_brush; }
Usa el color
OnEraseBkgnd
para repintar cualquier área visible.afx_msg BOOL SoftwareDlg::OnEraseBkgnd(CDC* pDC) { CRect rc; pDC->GetClipBox(&rc); pDC->FillSolidRect(rc, m_color); return TRUE; }
Establezca el color de fondo para el control de edición enriquecida de forma
OnInitDialog
explícita, ya que los controles de edición enriquecidos no utilizan losWM_CTLCOLOR***
mensajes.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); //...
Nota : si utiliza el CDialogEx::SetBackgroundColor
enfoque propuesto en la otra respuesta, las partes OnCtlColor
y OnEraseBkgnd
están cubiertas por la CDialogEx
implementación. El último paso sigue siendo necesario porque el WM_CTLCOLOR
mecanismo solo cubre los controles básicos (estático, editar, botón, etc.) y el diálogo en sí. Los controles distintos de estos (control de edición enriquecido, control de vista de lista, etc.) deben manejarse por separado.
Simplemente use la CDialogEx
clase que admite CDialogEx::SetBackgroundColor
y hace todo lo necesario para los controles estáticos y de botones.