Click here to Skip to main content
15,881,803 members
Articles / Mobile Apps / Windows Mobile

Changing the color of PPC controls

Rate me:
Please Sign up or sign in to vote.
4.63/5 (13 votes)
3 Mar 2006CPOL9 min read 60.1K   826   32   9
How to change the color of Windows CE components without modifying your program.

Sample Image - PPCColorConfig.jpg

Introduction

This article intends to show you how to change the color of a Windows CE device's components in a simple way. What would you do if, for example, you want to display a red background for your button? Most likely, you will have to catch the WM_PAINT message and do all the work by yourself. And what about changing the text color displayed in the title bar? You'll have to redraw the non-client area.

Furthermore, let's say that your customer wants you to create themes in your system. That is, for the theme X, I want a pink background for the windows, a red background for the buttons, white color for the static text, and a light blue background for the menus. And that is not all: the next month, the customer will want another theme.

In fact, the last paragraph described my situation not so long ago. What to do? Rewriting the application, catching the WM_PAINT, and doing the redraw for each control was not an option. Fortunately, after some research, I found a solution. And after finding it, I decided to create an application that allows me to change the system's color easily.

The purpose of this article is, thus, to show how to change the system's colors, and also to show how this application works and how to use it.

Background

Windows CE has many configurable stuff. The one that we are interested in, the system colors, is located in the registry, in the following path:

HKEY_LOCAL_MACHINE\System\GWE\SysColor

This registry key is a 116-byte binary buffer. Each system color uses four bytes: one byte for red color, one for green, and one for blue; the fourth is always 0, although it is believed that for further versions of Windows CE, this byte will be used as the alpha color.

Therefore, there are twenty nine system components whose color can be changed (116/4 = 29). The following is a list of those components.

Registry OrderSystem colorDescription
0COLOR_SCROLLBARColor of the gray area of a scroll bar.
1COLOR_BACKGROUNDBackground color of the desktop window.
2COLOR_ACTIVECAPTIONColor of the title bar of an active window.
3COLOR_INACTIVECAPTIONColor of the title bar of an inactive window.
4COLOR_MENUBackground color of a menu.
5COLOR_WINDOWBackground color of a window.
6COLOR_WINDOWFRAMEColor of a window frame.
7COLOR_MENUTEXTColor of the text in a menu.
8COLOR_WINDOWTEXTColor of the text in a window.
9COLOR_CAPTIONTEXTColor of the text in a title bar and of the size box and scroll bar arrow box.
10COLOR_ACTIVEBORDERColor of the border of an active window.
11COLOR_INACTIVEBORDERColor of the border of an inactive window.
12COLOR_APPWORKSPACEBackground color of multiple document interface (MDI) applications.
13COLOR_HIGHLIGHTColor of an item selected in a control.
14COLOR_HIGHLIGHTTEXTColor of the text of an item selected in a control.
15COLOR_BTNFACEColor of the face of a button.
16COLOR_BTNSHADOWShadow color of buttons for edges that face away from the light source.
17COLOR_GRAYTEXTColor of shaded text. This color is set to 0 if the current display driver does not support a solid gray color.
18COLOR_BTNTEXTColor of the text for push buttons.
19COLOR_INACTIVECAPTIONTEXTColor of the text in the title bar of an inactive window.
20COLOR_BTNHIGHLIGHTHighlight color of buttons for edges that face the light source.
21COLOR_3DDKSHADOWColor of the dark shadow for three-dimensional display elements.
22COLOR_3DLIGHTHighlight color of three-dimensional display elements for edges that face the light source.
23COLOR_INFOTEXTColor of the text for ToolTip controls.
24COLOR_INFOBKBackground color for ToolTip controls.
25COLOR_STATICBackground color for static controls and dialog boxes. Supported in Windows CE 2.0 and later.
26COLOR_STATICTEXTColor of the text for static controls. Supported in Windows CE 2.0 and later.
27COLOR_GRADIENTACTIVECAPTIONColor of the title bar of an active window that is filled with a color gradient.
28COLOR_GRADIENTINACTIVECAPTIONColor of the title bar of an inactive window that is filled with a color gradient.

So, for locating the bytes you have to change, simple multiply the registry order times four. For example, for modifying the background color of a menu to red, you'll have to change the 16th byte to FF, the 17th to 00, and the 18th to 00 as well. Update the registry and perform a soft reset. Note: A soft reset (also called a warm boot) is needed, since these properties are read only when the system boots.

As you can see, the principle is somewhat simple. So, what a program must do is simply take the buffer, modify it, update the registry, and soft reset the device. Or you can do, as I did, modify the registry and create a CAB file that updates the registry. Either way, you need a way to modify the registry, so I made a little application that runs on the mobile device and updates the registry.

PPC Color Configurator

This is the application I made; you will find the source code at the beginning of this article. I tested it under Windows CE .NET (4.2), for Symbol MC50, an iPaq, and an Intermec 730 machines.

The application is dialog-based. Here is the declaration of the class for the main dialog:

class CPPCColorConfigDlg : public CDialog
{
    public:
        CPPCColorConfigDlg(CWnd* pParent = NULL);
        // standard constructor

        enum { IDD = IDD_PPCCOLORCONFIG_DIALOG };

    protected:
            HICON m_hIcon;

        virtual void DoDataExchange(CDataExchange* pDX);
        // DDX/DDV support

        virtual BOOL OnInitDialog();
        virtual void OnSelectOption();
        virtual void OnColorChange();
        virtual void OnPaint();
        virtual void OnUpdateSettings();

    private:
        void FillColorOptions();
        void InitVectors();
        void DisplayDescription();
        void UpdateRegistry();
        void ResetRegistry();
        void ParseItemColors(int* pRed, int* pGreen, int* pBlue);
        void SetColorsInBoxes(int iRed, int iGreen, int iBlue);

        int m_iSelected;
        int m_iRed;
        int m_iGreen;
        int m_iBlue;
        BYTE m_pNewBuffer<BUFFER_SIZE>;
        BYTE m_pOldBuffer<BUFFER_SIZE>;

        DECLARE_MESSAGE_MAP()
};

The member variable m_iSelected holds the index of the selected attribute (i.e., menu background color) and is changed within the OnSelectOption method. The properties m_iRed, m_iGreen, and m_iBlue hold the values for the red, green, and blue textboxes, and whose value will determine the color of the component. Finally, the properties m_pNewBuffer and m_pOldBuffer hold the buffer of memory that will be taken from the registry and whose value will be eventually updated to the registry as well. The macro BUFFER_SIZE is defined as 116.

The method InitVectors initializes the memory buffers, taking the values from the registry. Here is the code:

void CPPCColorConfigDlg::InitVectors()
{
    HKEY hKey;
    DWORD dwType;
    DWORD dwSize;    

    dwType = 0;
    dwSize = BUFFER_SIZE;
    memset(m_pNewBuffer, 0, BUFFER_SIZE);
    memset(m_pOldBuffer, 0, BUFFER_SIZE);

    ::RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("SYSTEM\\GWE"), NULL, NULL, &hKey);
    ::RegQueryValueEx(hKey, _T("SysColor"), NULL, &dwType, m_pOldBuffer, &dwSize);
    ::RegQueryValueEx(hKey, _T("SysColor"), NULL, &dwType, m_pNewBuffer, &dwSize);
    ::RegCloseKey(hKey);

    ASSERT(!memcmp(m_pOldBuffer, m_pNewBuffer, BUFFER_SIZE));
}

I created a global variable, g_vtrShowStrings, which is a 2D matrix that holds the strings displayed in the combo box, and its description. The position within the matrix will determine the position within the buffer whose bytes will be modified. The macro OPTION_VECTOR_SIZE is defined as 29.

CString g_vtrShowStrings[OPTION_VECTOR_SIZE][2] =
{
    { CString("Scrollbar"),    CString("Color of the gray area of a scroll bar") },
    { CString("Background"), CString("Background color of the desktop window") },
    { CString("Active Caption"), CString("Color of the title bar of an active window") },
    ...
};

Just for the records, I present the message map. As you noticed from the picture, there are some controls: a combo box that holds the components of the system that can be updated, four text boxes whose purpose is to allow the user to change the color of the component (the first one is for red, the second is for green, and the third is for the blue color; the fourth is unused, and it will be for the alpha, when WinCE admits that parameter). There is also a button, that -when clicked- will update the registry through the memory buffer. Here is the message map:

BEGIN_MESSAGE_MAP(CPPCColorConfigDlg, CDialog)
    ON_WM_PAINT()
    ON_CBN_SELCHANGE(IDC_CMB_COMPONENTS, OnSelectOption)
    ON_EN_CHANGE(IDC_TXT_RED, OnColorChange)
    ON_EN_CHANGE(IDC_TXT_GREEN, OnColorChange)
    ON_EN_CHANGE(IDC_TXT_BLUE, OnColorChange)
    ON_BN_CLICKED(IDC_CMD_UPDATE, OnUpdateSettings)
END_MESSAGE_MAP()

The next thing is the OnInitDialog. In such a dialog, we do two tasks: initialize the combo box according to the g_vtrShowStrings matrix (the combo box's property "sorted" was unmarked in the dialog resource editor), and select the first item of the combo box. The method looks as follows:

BOOL CPPCColorConfigDlg::OnInitDialog()
{
    CDialog::OnInitDialog();

    SetIcon(m_hIcon, TRUE);            
    SetIcon(m_hIcon, FALSE);        
    
    CenterWindow(GetDesktopWindow());

    FillColorOptions();

    SetDlgItemText(IDC_TXT_OTHER, _T("0"));
    SetDlgItemText(IDC_TXT_RED, _T("0"));
    SetDlgItemText(IDC_TXT_GREEN, _T("0"));
    SetDlgItemText(IDC_TXT_BLUE, _T("0"));
    OnColorChange();

    CComboBox* pColorOptions;
    pColorOptions = reinterpret_cast<CComboBox*&gt(GetDlgItem(IDC_CMB_COMPONENTS));
    pColorOptions->SetCurSel(0);

    return TRUE;  
}

The method FillColorOptions will fill the combo box. It will iterate over the first dimension of the global matrix. Here's the code:

void CPPCColorConfigDlg::FillColorOptions()
{
    CComboBox* pColorOptions;

    pColorOptions = reinterpret_cast<CComboBox*>(GetDlgItem(IDC_CMB_COMPONENTS));
    pColorOptions->Clear();

    for (int i = 0; i < OPTION_VECTOR_SIZE; i++)
    {
        pColorOptions->AddString(g_vtrShowStrings[i][0]);
    }
}

As explained before, the m_iSelected holds the position of the matrix that is going to be changed. This member is updated when the index of the combo box is changed. Here is the code that responds to such an event:

void CPPCColorConfigDlg::OnSelectOption()
{
    CComboBox* pColorOptions;
    int iRed, iGreen, iBlue;
    
    pColorOptions = reinterpret_cast<CComboBox*>(GetDlgItem(IDC_CMB_COMPONENTS));
    m_iSelected = pColorOptions->GetCurSel();

    // fill the current text boxes with the current color

    iRed = iGreen = iBlue = 0;

    ParseItemColors(&iRed, &iGreen, &iBlue);
    SetColorsInBoxes(iRed, iGreen, iBlue);

    DisplayDescription();    
}

This method does two actions. First, updates the m_iSelected member. Second, it will also update the text boxes' values, according to the values within the buffer. The method ParseItemColors gets the three colors from the memory buffer. The method SetColorsInBoxes updates the textboxes' values. Here is the code:

void CPPCColorConfigDlg::ParseItemColors(int* pRed, int* pGreen, int* pBlue)
{
    int iPosition;
    
    iPosition = m_iSelected * 4;

    memcpy(pRed, m_pNewBuffer + iPosition++, 1);
    memcpy(pGreen, m_pNewBuffer + iPosition++, 1);
    memcpy(pBlue, m_pNewBuffer + iPosition++, 1);
}

void CPPCColorConfigDlg::SetColorsInBoxes(int iRed, int iGreen, int iBlue)
{
    CString strRed, strGreen, strBlue;

    strRed.Format(_T("%d"), iRed);
    strGreen.Format(_T("%d"), iGreen);
    strBlue.Format(_T("%d"), iBlue);

    SetDlgItemText(IDC_TXT_RED, strRed);
    SetDlgItemText(IDC_TXT_GREEN, strGreen);
    SetDlgItemText(IDC_TXT_BLUE, strBlue);
}

Notice that in ParseItemColors, we use m_iSelected to determine the position within the memory buffer.

When an item is selected from the combo box, it will also call the DisplayDescription method, which will show a brief description about the system's component. It takes such a value from the global matrix.

void CPPCColorConfigDlg::DisplayDescription()
{
    CStatic* pDescription;

    pDescription = reinterpret_cast<CStatic*>(GetDlgItem(IDC_LBL_DESCRPT));
    pDescription->SetWindowText(g_vtrShowStrings[m_iSelected][1]);
}

When we change the value in one of the three text boxes available, the method OnColorChange will be called. This method gets such values and update the local ones. Also, it performs some validations, so that the value is always between 0 and 255.

void CPPCColorConfigDlg::OnColorChange()
{
    CEdit* pRed, * pGreen, * pBlue;
    int iRed, iGreen, iBlue;
    CString strRed, strGreen, strBlue;

    pRed = reinterpret_cast<CEdit*>(GetDlgItem(IDC_TXT_RED));
    pGreen = reinterpret_cast<CEdit*>(GetDlgItem(IDC_TXT_GREEN));
    pBlue = reinterpret_cast<CEdit*>(GetDlgItem(IDC_TXT_BLUE));

    pRed->GetWindowText(strRed);
    pGreen->GetWindowText(strGreen);
    pBlue->GetWindowText(strBlue);

    iRed = _ttoi(strRed.GetBuffer(0));
    iGreen = _ttoi(strGreen.GetBuffer(0));
    iBlue = _ttoi(strBlue.GetBuffer(0));

    if (iRed < 0 || iRed > 255) 
    {
        MessageBox(_T("Red tone must be between 0 and 255"), 
                   NULL, MB_ICONEXCLAMATION);
        pRed->SetWindowText(_T("0"));
    }
    else if (iGreen < 0 || iGreen > 255) 
    {
        MessageBox(_T("Green tone must be between 0 and 255"), 
                   NULL, MB_ICONEXCLAMATION);
        pGreen->SetWindowText(_T("0"));
    }
    else if (iBlue < 0 || iBlue > 255) 
    {
        MessageBox(_T("Blue tone must be between 0 and 255"), 
                   NULL, MB_ICONEXCLAMATION);
        pBlue->SetWindowText(_T("0"));
    }
    else 
    {
        m_iRed = iRed;
        m_iGreen = iGreen;
        m_iBlue = iBlue;
        InvalidateRect(NULL);
    }
}

Notice that in the end, if everything is alright, we call InvalidateRect. This function will invoke the WM_PAINT message, so that the window will be redrawn. We then catch such a message within the OnPaint method, and then we draw a simple rectangle in the window, whose color is the one selected by the user. This will help the user to preview the color that is being selected. The method is simple.

void CPPCColorConfigDlg::OnPaint()
{
    CDialog::OnPaint();

    CClientDC dc(this);
    CBrush brush;

    brush.CreateSolidBrush(RGB(m_iRed, m_iGreen, m_iBlue));
    dc.SelectObject(&brush);
        
    dc.Rectangle(12, 90, 215, 150);
}

Finally, when we select the system's component and decide its color, we have to push the "Update settings" button. This will call the OnUpdateSettings method that will update the memory buffer and -eventually- the registry.

void CPPCColorConfigDlg::OnUpdateSettings()
{
    int iPosition;

    iPosition = m_iSelected * 4;
    memcpy(m_pNewBuffer + iPosition++, &m_iRed, 1);
    memcpy(m_pNewBuffer + iPosition++, &m_iGreen, 1);
    memcpy(m_pNewBuffer + iPosition++, &m_iBlue, 1);
    UpdateRegistry();

    SetDlgItemText(IDC_LBL_DESCRPT, 
      _T("A soft reset must be made before changes take effect."));
}

The member UpdateRegistry... guess what? Updates the registry...

void CPPCColorConfigDlg::UpdateRegistry()
{
    HKEY hKey;
    DWORD dwType;
    DWORD dwSize;

    dwType = 0;
    dwSize = BUFFER_SIZE;

    ::RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("SYSTEM\\GWE"), NULL, NULL, &hKey);
    ::RegQueryValueEx(hKey, _T("SysColor"), NULL, 
                      &dwType, m_pOldBuffer, &dwSize);
    ::RegSetValueEx(hKey, _T("SysColor"), NULL, 
                    dwType, m_pNewBuffer, BUFFER_SIZE);
    ::RegCloseKey(hKey);
}

Conclusions

Well, that's it. You can create all sorts of themes for your Pocket PC. Furthermore, you can include parts of this code in your apps, so you can customize its display, and when the application ends, you could restore the original settings (which are stored in HKEY_LOCAL_MACHINE\System\GWE\DefSysColor). You can even extend this functionality, since you can also change many other keys under HKEY_LOCAL_MACHINE\System\GWE.

Remember that you need to soft reset your device before the changes take effect.

Working with Intermec Devices

If you change the registry to adjust any color as described in the article, the Intermec machine will "reset" your registry to its original value as soon as you soft-reset it. Hence, you will not be able to see the changes. For that, you have two options.

  • Remove the file "registry" under the "\Flash File Store" directory.
  • Once the registry is updated, and before you soft reset, run the RegFlush2.exe program. This program will take a "picture" of your registry and save it under the "\Flash File Store" directory so the next time it boots, it will take that configuration.

For more information, see the 700c WM 2003 how to save the registry article from Intermec's Knowledge Base.

History

  • [Mar 03, 2006] Main release of the article.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Chief Technology Officer Blendwerk TI & Media
Mexico Mexico
42

Comments and Discussions

 
GeneralThere's a better way to do it. Pin
alnorman6-Mar-06 4:03
alnorman6-Mar-06 4:03 
GeneralRe: There's a better way to do it. Pin
Fernando A. Gomez F.6-Mar-06 4:33
Fernando A. Gomez F.6-Mar-06 4:33 
GeneralRe: There's a better way to do it. Pin
burakb26-Apr-06 5:13
burakb26-Apr-06 5:13 
AnswerRe: There's a better way to do it. Pin
JP081528-Apr-08 4:38
JP081528-Apr-08 4:38 
GeneralRe: There's a better way to do it. Pin
Fernando A. Gomez F.29-Apr-08 6:58
Fernando A. Gomez F.29-Apr-08 6:58 
GeneralRe: There's a better way to do it. Pin
twisterat5729-Jan-15 22:35
professionaltwisterat5729-Jan-15 22:35 
GeneralNice Pin
Est Solarus3-Mar-06 21:04
Est Solarus3-Mar-06 21:04 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.