Não é possível alterar a cor das minhas caixas de diálogo em meu aplicativo

Dec 07 2020

Estou tentando criar um "modo escuro" para meu aplicativo Windows C ++ parcialmente para me divertir, parcialmente para tentar compreender totalmente a passagem da mensagem no MFC, mas estou encontrando alguns problemas realmente estranhos que não consigo encontrar explicados em lugar nenhum.

Passei a maior parte do dia tentando descobrir isso e tentarei o meu melhor para citar as muitas fontes que examinei e tentei implementar.

Acredito ter escrito manipuladores de mensagem com sucesso para ambos WM_CTLCOLORe com WM_ERASEBKGNDbase no código de exemplo desta resposta , mas eles não parecem ter nenhum efeito em meus diálogos. Reduzi o código aqui, mas espero ter fornecido o suficiente para expor meu problema. Se isso ainda não for suficiente, posso (com relutância) compartilhar todo o repo.

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

Outra pergunta , postada pelo mesmo usuário cerca de 6 meses antes, foi respondida com um código um pouco semelhante, mas usando o tipo de estrutura que contém uma função "WinMain" (desculpe, não posso diferenciar os 2+ tipos ainda). Meu programa não contém uma função WinMain, então não pude usar o código de exemplo diretamente ... mas outra diferença nesta resposta foi que David foi instruído a capturar um WM_CTLCOLORDLGtipo de mensagem em vez do WM_CTLCOLORtipo de mensagem. Tentei capturar esse novo tipo de mensagem, mas o IntelliSense me disse que era indefinido e que a propriedade da mensagem em particular estava completamente ausente da exibição de recursos da caixa de diálogo:

Também tentei "WM_CTLCOLORDLG"me definir conforme descrito na página do Microsoft Docs , mas continuei a receber mensagens de erro quando tentei lidar com isso por meio de "ON_MESSAGE".

Meu código não era um projeto original, mas foi tirado de uma amostra de código aberto fornecida com RTSS . Como tal, ele não usa o padrão (?) "Pch.h", mas "stdafx.h" (que é mais antigo, eu acho?). Não tenho certeza se isso é relevante, mas sinto que pode ser.

Eu acho que esse problema também pode estar me causando muitas outras dores de crescimento, então qualquer ajuda é MUITO apreciada.

Respostas

4 dxiv Dec 08 2020 at 00:12

O principal problema com o código do OP é que o pincel é recriado toda vez OnCtlColore continuará vazando identificadores GDI (a compilação de depuração do MFC levanta um problema ASSERTsobre isso). Conclua a correção passo a passo abaixo.

  • Declare a cor e o pincel como membros da caixa de diálogo.

    class SoftwareDlg : public CDialog
    {
    //...
    protected:
        COLORREF m_color;
        CBrush m_brush;
    //...
    
  • Inicialize a cor e aplique o pincel OnInitDialog.

    BOOL SoftwareDlg::OnInitDialog()
    {
        m_color = RGB(136, 217, 242);
        m_brush.CreateSolidBrush(m_color);
    
        CDialog::OnInitDialog();
    //...
    
  • Retorne o pincel de OnCtlColor.

    HBRUSH SoftwareDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
    {
    //...
        return m_brush;
    }
    
  • Use a cor OnEraseBkgndpara repintar qualquer área visível.

    afx_msg BOOL SoftwareDlg::OnEraseBkgnd(CDC* pDC)
    {
        CRect rc;
        pDC->GetClipBox(&rc);
        pDC->FillSolidRect(rc, m_color);
        return TRUE;
    }
    
  • Defina a cor de fundo para o controle de edição avançada OnInitDialogexplicitamente, uma vez que os controles de edição avançada não usam as WM_CTLCOLOR***mensagens.

    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 : se usar a CDialogEx::SetBackgroundColorabordagem proposta na outra resposta, as partes OnCtlColore OnEraseBkgndsão cobertas pela CDialogEximplementação. A última etapa ainda é necessária porque o WM_CTLCOLORmecanismo cobre apenas os controles básicos (estático, editar, botão, etc.) e a caixa de diálogo em si. Controles diferentes desses (controle de edição avançada, controle de exibição de lista etc.) precisam ser tratados separadamente.

4 xMRi Dec 07 2020 at 13:47

Simplesmente use a CDialogExclasse que ele suporta CDialogEx::SetBackgroundColore ele faz todo o material para controles estáticos e de botão.