Customizing windows

From SEGGER Wiki
Jump to navigation Jump to search

The following tutorial will demonstrate and explain in detail how a window can be customized in emWin. Customizing means that the user defines certain aspects of a window, like how it should be drawn, how it should react on mouse or keyboard input and so on.

Window changes color upon mouse click.

Objective of the tutorial

The purpose of this tutorial is an in-depth explanation on how custom behavior and custom drawing can be set up for a window in emWin.

The final application that is written in this tutorial will be a small window, that changes its color from red to green as long as it is pressed.

Tutorial

Prerequirements

In order to be able to use emWin's features and the Window Manager, the file WM.h has to be included. In case we would not need the Window Manager, we would include GUI.h and if we needed the Window Manager and widgets, we would include DIALOG.h.

#include "WM.h"

Then we should add a MainTask() to the application, that initializes emWin and frequently calls GUI_Delay() in a superloop to keep emWin alive.

void MainTask(void) {  
  //
  // Init GUI.
  //
  GUI_Init();
  //
  // When calling the delay routine, GUI_Exec() is called which executes emWin operations.
  //
  while (1) {
    GUI_Delay(100);
  }
}

Create a window

The next step to do is creating a window, that we will customize in the further steps of this tutorial. After emWin has been initialized in MainTask(), we can create a window by adding the following function call.

WM_CreateWindowAsChild(0, 0, 100, 100, WM_HBKWIN, WM_CF_SHOW, _cbWin, 0);

This will create a new window placed at the pixel position x = 0, y = 0 with the size of 100 pixels in height and width. The child window's parent is the "background window" which can be accessed via the macro WM_HBKWIN. For the next parameter, window create flags have to be specified. With WM_CF_SHOW, the window will be made visible after it has been created. Next, we pass a function pointer to _cbWin() as the window's callback. Lastly, we don't have to allocate additional bytes for this window, so we set 0 as the number of extra bytes. The callback has not been created yet, this will be done in the next step.

Create the window's callback routine

A window (or widget) callback has the following prototype.

typedef void WM_CALLBACK(WM_MESSAGE * pMsg);

With this prototype, we can create our own callback that we will use for the window we just created in the previous step. When a message is sent to a window, it will be processed by the window's callback routine. Therefore, we have to process the messages by checking the message ID, which is stored in the MsgId member of the WM_MESSAGE structure.

static void _cbWin(WM_MESSAGE * pMsg) {
  switch(pMsg->MsgId) {
  default:
    WM_DefaultProc(pMsg);
  }
}

First, we add a default case to the callback and call WM_DefaultProc(). This will ensure, that all other messages not handled by the user's callback will be handled by the default callback.

WM_PAINT: Define drawing of the window

By adding a WM_PAINT case to the callback, we are defining how the window will be drawn.

case WM_PAINT:
  GUI_SetBkColor(GUI_RED);
  GUI_Clear();
  break;

By calling GUI_SetBkColor(GUI_RED) and GUI_Clear(), the entire window will be filled with red. All drawing operations done in a WM_PAINT case

When we now execute our application, we will see the red window in the top left corner of the screen.

WM_PID_STATE_CHANGED: Reacting on mouse input

For the next step, we want to define the behavior of the window when it receives a mouse click. If the window is clicked, it should change its color to green. When the click is released, the window should be red again.

In order that the window can react on mouse clicks, we have to add a WM_PID_STATE_CHANGED case to the window callback.

case WM_PID_STATE_CHANGED:
  break;

When the window receives a WM_PID_STATE_CHANGED message, a pointer to a WM_PID_STATE_CHANGED_INFO structure is sent with the message. The pointer can be accessed via pMsg->Data.p. We are going to save this pointer in a variable.

WM_PID_STATE_CHANGED_INFO * pInfo;
...
pInfo = (WM_PID_STATE_CHANGED_INFO *)pMsg->Data.p;

Now, we can access the data about the pointer input device (PID) state. The element pInfo->State is 0 if the mouse is unpressed and 1 if the mouse is pressed. Since we need to process this value in the WM_PAINT case, we are saving it in a static (!) integer variable. The variable has to be static so its value will be still be persistent when the callback is called the next time.

static int Pressed;
...
Pressed = pInfo->State;
WM_InvalidateWindow(pMsg->hWin);

After the PID state has been saved, we are also invalidating the window, which will ensure that it will be redrawn again. If we wouldn't do that, the state would be saved but no changes would appear on the screen. We specify the current window with pMsg->hWin. The hWin element of the WM_MESSAGE structure contains the window handle of the window the message is sent to.

Now that we saved the pressed state, we can define the drawing so that the window will appear green when it is clicked.

case WM_PAINT:
  if(Pressed) {
    GUI_SetBkColor(GUI_GREEN);
  } else {
    GUI_SetBkColor(GUI_RED);
  }
  GUI_Clear();
  break;

After this step is done, the callback should look like shown below. When executing the application, the window will appear green while it is clicked and red when it isn't.

static void _cbWin(WM_MESSAGE * pMsg) {
  WM_PID_STATE_CHANGED_INFO * pInfo;
  static int                  Pressed;

  switch(pMsg->MsgId) {
  case WM_PAINT:
    if(Pressed) {
      GUI_SetBkColor(GUI_GREEN);
    } else {
      GUI_SetBkColor(GUI_RED);
    }
    GUI_Clear();
    break;
  case WM_PID_STATE_CHANGED:
    //
    // Save pressed state.
    //
    pInfo   = (WM_PID_STATE_CHANGED_INFO *)pMsg->Data.p;
    Pressed = pInfo->State;
    //
    // Redraw this window.
    //
    WM_InvalidateWindow(pMsg->hWin);
    break;
  default:
    WM_DefaultProc(pMsg);
  }
}

Processing other messages

The mechanism above explains how to react on emWin's system messages. This can be applied to most other system messages. When a window should for example react on key input, it has to react on the corresponding message which is WM_KEY in this case. The emWin manual has a detailed list of all messages, when they are sent to windows and what additional data is stored in the message.

Downloads

The source code of this example can be downloaded here.