Resources, menus and dialogs.

In this chapter we'll look at a simple configurable plasma generator, which has a dialog for control and a menu to get this dialog and resource file where menu and dialog resources are along with icon resource.

First, the C source:

#include <windows.h>
#include <nt\commctrl.h>
#include <math.h>

#define REDCHECK 201
#define GRNCHECK 202
#define BLUCHECK 203
#define APPLYCHECK 204
#define SINWAVE 301
#define SQRWAVE 302
#define SAWWAVE 303
#define DISTXTRACK 401
#define DISTYTRACK 402
#define POWERTRACK 403
#define IDAPPLY 500

char progname[]="Plasma generator";
char SINTAB[256];
HINSTANCE hInst;

struct configstruct 
{
  int red,green,blue;
  int autoapply;
  int wavetype;
  int distx;
  int disty;
  int power;
};

struct configstruct config;

void genwave(int wavetype)
{
  int i;
  switch (wavetype) {
  case 1:
    for (i=0;i<256;i++)
      SINTAB[i]=sin(((i+1)*3.14159265359)/128)*127+128;
    break;
  case 2:
    for (i=0;i<256;i++)
      SINTAB[i]=(i<127)?64:192;
    break;
  case 3:
    for (i=0;i<256;i++)
      SINTAB[i]=i;
    break;
  }
}


LRESULT CALLBACK winproc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInst, LPSTR lpszCmdLine, int nCmdShow)
{
  WNDCLASSEX winclass;
  HWND hWnd;
  MSG msg;
  hInst=hInstance;

  winclass.cbSize=sizeof(WNDCLASSEX);
  winclass.style=CS_DBLCLKS;
  winclass.lpfnWndProc=&winproc;
  winclass.cbClsExtra=0;
  winclass.cbWndExtra=0;
  winclass.hInstance=hInst;
  winclass.hIcon=LoadIcon(hInst,"OURICON");
  winclass.hCursor=LoadCursor(NULL,IDC_ARROW);
  winclass.hbrBackground=NULL;
  winclass.lpszMenuName="OURMENU";
  winclass.lpszClassName=progname;
  winclass.hIconSm=NULL;

  if (!RegisterClassEx(&winclass))
    return 0;

  genwave(1);

  config.red=1;
  config.green=1;
  config.blue=1;
  config.autoapply=0;
  config.wavetype=1;
  config.distx=0;
  config.disty=0;
  config.power=0;

  InitCommonControls();

  hWnd=CreateWindow(
    progname,
    progname,
    WS_SYSMENU|WS_CAPTION|WS_BORDER|WS_OVERLAPPED|WS_VISIBLE|WS_MINIMIZEBOX,
    CW_USEDEFAULT,
    0,
    320+2,
    200+16+16+2,
    NULL,
    NULL,
    hInst,
    NULL);

  ShowWindow(hWnd,nCmdShow);
  UpdateWindow(hWnd);
  
  while (GetMessage(&msg,NULL,0,0))
  {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }
  return (msg.wParam);
}

HDC pDC;
HBITMAP old;
HBITMAP ourbitmap;
int * framebuf;

int gen_red(int tick,int x, int y)
{
  if (config.red) {
    return SINTAB[(y/4-x+tick)&0xff];
  } else {
    return 0;
  }
}

int gen_green(int tick,int x, int y)
{
  if (config.green) {
    return SINTAB[(SINTAB[(x+y)&0xff]-SINTAB[tick&0xff])&0xff];
  } else {
    return 0;
  }
}

int gen_blue(int tick,int x, int y)
{
  if (config.blue) {
    return SINTAB[(x/4-y+tick)&0xff];
  } else {
    return 0;
  }
}

void render_effect(int tick,int * framebuf)
{
  int i,j,k;
  int xrip[200],yrip[320];
  tick/=16;
  for (i=0;i<200;i++)
    xrip[i]=(((SINTAB[(i*4+tick)&0xff]*(16+config.power))>>4)*config.distx)>>2;
  for (i=0;i<320;i++)
  yrip[i]=(((SINTAB[(i*4-tick)&0xff]*(16+config.power))>>4)*config.disty)>>2;
  for (k=0,i=0;i<200;i++)
    for (j=0;j<320;j++,k++)
      *(framebuf+k)=RGB(gen_blue(tick,yrip[j]+(i*(16+config.power))>>4,xrip[i]+(j*(16+config.power))>>4),
                        gen_green(tick,yrip[j]+(i*(16+config.power))>>4,xrip[i]+(j*(16+config.power))>>4),
                        gen_red(tick,yrip[j]+(i*(16+config.power))>>4,xrip[i]+(j*(16+config.power))>>4));
}

void render(HDC hDC)
{
  render_effect(GetTickCount(),framebuf);
  BitBlt(hDC,0,0,320,200,pDC,0,0,SRCCOPY);
}

void deinit_framebuf(void)
{
  SelectObject(pDC,old);
  DeleteDC(pDC);
  DeleteObject(ourbitmap);
}

void init_framebuf(void)
{
  HDC hDC;
  BITMAPINFO bitmapinfo;
  hDC=CreateCompatibleDC(NULL);
  bitmapinfo.bmiHeader.biSize=sizeof(BITMAPINFOHEADER);
  bitmapinfo.bmiHeader.biWidth=320;
  bitmapinfo.bmiHeader.biHeight=-200;
  bitmapinfo.bmiHeader.biPlanes=1;
  bitmapinfo.bmiHeader.biBitCount=32;
  bitmapinfo.bmiHeader.biCompression=BI_RGB;
  bitmapinfo.bmiHeader.biSizeImage=0;
  bitmapinfo.bmiHeader.biClrUsed=256;
  bitmapinfo.bmiHeader.biClrImportant=256;
  ourbitmap=CreateDIBSection(hDC,&bitmapinfo,DIB_RGB_COLORS,&framebuf,0,0);
  pDC=CreateCompatibleDC(NULL);
  old=SelectObject(pDC,ourbitmap);
  DeleteDC(hDC);
}

struct configstruct oldconfig, tempconfig;

BOOL APIENTRY ControlProc(HWND hDlg,UINT uiMessage,UINT wParam,LONG lParam)
{
  switch (uiMessage) {
  case WM_HSCROLL:
  case WM_VSCROLL:
    wParam=0;
  case WM_COMMAND:
    tempconfig.distx=SendDlgItemMessage(hDlg,DISTXTRACK,TBM_GETPOS,0,0);
    tempconfig.disty=SendDlgItemMessage(hDlg,DISTYTRACK,TBM_GETPOS,0,0);
    tempconfig.power=SendDlgItemMessage(hDlg,POWERTRACK,TBM_GETPOS,0,0);
    tempconfig.autoapply=IsDlgButtonChecked(hDlg,APPLYCHECK);
    tempconfig.red=IsDlgButtonChecked(hDlg,REDCHECK);
    tempconfig.green=IsDlgButtonChecked(hDlg,GRNCHECK);
    tempconfig.blue=IsDlgButtonChecked(hDlg,BLUCHECK);
    if (IsDlgButtonChecked(hDlg,SINWAVE))
      tempconfig.wavetype=1;
    if (IsDlgButtonChecked(hDlg,SQRWAVE))
      tempconfig.wavetype=2;
    if (IsDlgButtonChecked(hDlg,SAWWAVE))
      tempconfig.wavetype=3;
    if (LOWORD(wParam)==IDOK) {
      memcpy((char*)&config,(char*)&tempconfig,sizeof(struct configstruct));
      EndDialog(hDlg,TRUE);
      genwave(config.wavetype);
      return FALSE;
    }
    if ((LOWORD(wParam)==IDAPPLY) || tempconfig.autoapply) {
      memcpy((char*)&config,(char*)&tempconfig,sizeof(struct configstruct));
      genwave(config.wavetype);
    }
    if (LOWORD(wParam)==IDCANCEL) {
      memcpy((char*)&config,(char*)&oldconfig,sizeof(struct configstruct));
      EndDialog(hDlg,TRUE);
      genwave(config.wavetype);
      return FALSE;
    }
    break;
  case WM_INITDIALOG:
    memcpy((char*)&oldconfig,(char*)&config,sizeof(struct configstruct));
    memcpy((char*)&tempconfig,(char*)&config,sizeof(struct configstruct));
    SendDlgItemMessage(hDlg,DISTXTRACK,TBM_SETRANGE,TRUE,MAKELONG(0,30));
    SendDlgItemMessage(hDlg,DISTYTRACK,TBM_SETRANGE,TRUE,MAKELONG(0,30));
    SendDlgItemMessage(hDlg,POWERTRACK,TBM_SETRANGE,TRUE,MAKELONG(0,62));
    SendDlgItemMessage(hDlg,DISTXTRACK,TBM_SETPOS,1,config.distx);
    SendDlgItemMessage(hDlg,DISTYTRACK,TBM_SETPOS,1,config.disty);
    SendDlgItemMessage(hDlg,POWERTRACK,TBM_SETPOS,1,config.power);
    CheckDlgButton(hDlg,APPLYCHECK,config.autoapply);
    CheckDlgButton(hDlg,REDCHECK,config.red);
    CheckDlgButton(hDlg,GRNCHECK,config.green);
    CheckDlgButton(hDlg,BLUCHECK,config.blue);
    switch (config.wavetype) {
    case 1:CheckDlgButton(hDlg,SINWAVE,1); break;
    case 2:CheckDlgButton(hDlg,SQRWAVE,1); break;
    case 3:CheckDlgButton(hDlg,SAWWAVE,1); break;
    }    
    break;
  }
  return FALSE;
}

LRESULT CALLBACK winproc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  HDC hDC;
  PAINTSTRUCT PtStr;
  switch (uMsg) {
  case WM_COMMAND:
    switch (LOWORD(wParam)) {
    case 1001:
      MessageBox(hWnd,"Plasma generator 0.01a","Sorry, no about dialog =)",MB_OK|MB_ICONEXCLAMATION);
      break;
    case 1002:
      SendMessage(hWnd,WM_CLOSE,0,0);
      break;
    case 1101:
      DialogBox(hInst,"OURDIALOG",hWnd,(DLGPROC)&ControlProc);
      break;
    }
    break;
  case WM_DESTROY:
    deinit_framebuf();
    PostQuitMessage(0);
    KillTimer (hWnd, 1);
    break;
  case WM_CREATE:
    SetTimer (hWnd, 1, 10, NULL);
    init_framebuf();
    break;
  case WM_TIMER:
  InvalidateRgn(hWnd,0,0);
    UpdateWindow (hWnd);
    break;
  case WM_PAINT:
    hDC=BeginPaint(hWnd,&PtStr);
    render(hDC);
    EndPaint(hWnd,&PtStr);
    break;
  default:
    return DefWindowProc (hWnd, uMsg, wParam, lParam);
    break;
  }
  return 0;
}

First, check what has happened to WinMain. you should easily locate the changes in the window class; icon and menu names appear there. Another important change is the InitCommonControls which makes sure the comctl32.dll is loaded. This dll is windows 4.0 specific (win95/winnt4.0), and includes trackbars, tooltips and other new stuff. To be able to use these common controls, you must link your program with comctl32.lib and #include <nt\commctrl.h>.

Some interesting changes have appeared at window procedure:

case WM_COMMAND:
  switch (LOWORD(wParam)) {
  case 1001:
    MessageBox(hWnd,"Plasma generator 0.01a","Sorry, no about dialog =)",MB_OK|MB_ICONEXCLAMATION);
    break;
  case 1002:
    SendMessage(hWnd,WM_CLOSE,0,0);
    break;
  case 1101:
    DialogBox(hInst,"OURDIALOG",hWnd,(DLGPROC)&ControlProc);
    break;
  }
  break;

WM_COMMAND message gives us commands. All keyboard keys arrive here as well. The low word of wParam has the command key. 1001, 1002 and 1101 are defined in our menu resource. Messagebox is old stuff. 1002 reads "quit" in our menu, and thus we send ourselves the WM_CLOSE message.

1101 then starts our new dialog box, transferring control to the following function:

BOOL APIENTRY ControlProc(HWND hDlg,UINT uiMessage,UINT wParam,LONG lParam)

When you look at this function, you will note that it is pretty close to the window procedure. Also note that there is no default function call. There is a DefDlgProc-function, but it must NOT be used here. (Or go ahead and try - it's a spectacular way to instabilize windows. The most spectacular win95 crash I've managed to hassle up to now can be achieved by changing the DialogBox call into DialogBoxIndirect without any other changes, causing windows to crash every time it tries to display the "crash occurred"-window).

The ControlProc sends current configuration to different dialog box items at creation, and then reads them back at every event. If apply is pressed or auto-apply is activated, this configuration is written to the active one. On cancel-button press, old configuration is restored.

Finally, let's look at the resource script. Note that some rows are pretty long.

#include <windows.h>
#include <nt\commctrl.h>

#define REDCHECK 201
#define GRNCHECK 202
#define BLUCHECK 203
#define APPLYCHECK 204
#define SINWAVE 301
#define SQRWAVE 302
#define SAWWAVE 303
#define DISTXTRACK 401
#define DISTYTRACK 402
#define POWERTRACK 403
#define IDAPPLY 500

OURICON ICON pgenico.ico

OURDIALOG DIALOG DISCARDABLE  16, 16, 241, 125
STYLE DS_MODALFRAME | WS_OVERLAPPED | WS_CAPTION | WS_VISIBLE | WS_SYSMENU | DS_3DLOOK
CAPTION "Plasma Generator Controls"
FONT 8, "Helv"
BEGIN
  CONTROL "",       DISTXTRACK, "msctls_trackbar32", TBS_AUTOTICKS | TBS_VERT | TBS_FIXEDLENGTH | TBS_LEFT | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 94, 1, 24, 88
  CONTROL "",       DISTYTRACK, "msctls_trackbar32", TBS_AUTOTICKS | TBS_FIXEDLENGTH | TBS_TOP | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 0, 83, 100, 23
  CONTROL "Use the bottom and right trackbars to control horizontal and vertical disting, also known as ripple or wave effect.", 105, "STATIC", SS_LEFT | WS_CHILD | WS_VISIBLE, 11, 15, 76, 63
  CONTROL "Disting",104, "BUTTON", BS_GROUPBOX | BS_LEFT | WS_CHILD | WS_VISIBLE, 7, 4, 87, 78
  CONTROL "OK",     IDOK, "BUTTON", BS_DEFPUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 194, 105, 45, 17
  CONTROL "Cancel", IDCANCEL, "BUTTON", BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 147, 105, 45, 17
  CONTROL "Apply",  IDAPPLY, "BUTTON", BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 100, 105, 45, 17
  CONTROL "Red channel",   REDCHECK, "BUTTON", BS_AUTOCHECKBOX | BS_LEFT | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 123, 7, 63, 9
  CONTROL "Green channel", GRNCHECK, "BUTTON", BS_AUTOCHECKBOX | BS_LEFT | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 123, 18, 64, 9
  CONTROL "Blue channel",  BLUCHECK, "BUTTON", BS_AUTOCHECKBOX | BS_LEFT | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 123, 29, 63, 10
  CONTROL "Sin wave",      SINWAVE, "BUTTON", BS_AUTORADIOBUTTON | BS_LEFT | WS_CHILD | WS_VISIBLE, 190, 7, 46, 9
  CONTROL "Sqr wave",      SQRWAVE, "BUTTON", BS_AUTORADIOBUTTON | BS_LEFT | WS_CHILD | WS_VISIBLE, 190, 18, 46, 9
  CONTROL "Saw wave",      SAWWAVE, "BUTTON", BS_AUTORADIOBUTTON | BS_LEFT | WS_CHILD | WS_VISIBLE, 190, 29, 46, 10
  CONTROL "", POWERTRACK, "msctls_trackbar32", TBS_AUTOTICKS | TBS_FIXEDLENGTH | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 123, 57, 111, 20
  CONTROL "Powa",  119, "STATIC", SS_CENTER | WS_CHILD | WS_VISIBLE, 128, 48, 103, 8
  CONTROL "Less",  120, "STATIC", SS_LEFT | WS_CHILD | WS_VISIBLE, 126, 78, 25, 10
  CONTROL "More",  121, "STATIC", SS_RIGHT | WS_CHILD | WS_VISIBLE, 194, 78, 35, 9
  CONTROL "Auto apply", APPLYCHECK, "BUTTON", BS_AUTOCHECKBOX | BS_LEFT | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 7, 109, 75, 10
END

ourmenu MENU
BEGIN
  POPUP "&File"
    BEGIN
    MENUITEM "&About..",1001
    MENUITEM SEPARATOR
    MENUITEM "&Quit",1002
    END
  POPUP "&Control"
    BEGIN
    MENUITEM "C&ontrols..",1101
    END
END

The menu resource structure should be quite clear by just looking at it. The characters after the &-sign are the hotkeys.

The dialog box resource is actually lots of parameters to CreateWindow calls. It is usually best to build dialog boxes with visual tools, like this was made.

Icon resource format is just [resourcename] ICON [filename]. Same goes for WAVE resources and other simple resources. Windows will use the first icon in the resource as the executable icon.

That's all for now.

Journey onwards