previous next Title Contents

4. Resurssien käyttö


Pääaiheet:

* valmiit ikkunaluokat: Edit, Button, Static, ScrollBar, List- ja ComboBox

* Windows-ohjelman debuggaus: viestien "vakoilu", SPY, WinSight

* resurssitiedosto .RC: työkalut tekemiseen

* valmiit dialogit: tiedoston avaus, värin valinta, fontin valinta, kirjoittimen valinta

* leikekirjan käyttö

* dynaamisen muistin käyttö

* bittikartat ja ikonit: staattiset ja liikkuvat

* dialogi pääikkunana

Windows tarjoaa paljon valmiita ratkaisuja yleisiin perustoimintoihin. Esimerkiksi API-funktioiden dokumentointia on 3 kirjaa jo peruspaketissa. Vaikka edellisessä luvussa piirsimmekin näyttöön, on tällainen piirtäminen ehkä oikeassa Windows-ohjelmassa vähemmällä merkityksellä, koska näyttö koostuu nappuloista, ikoneista, menuista jne., joiden piirtämisen Windows hoitaa itse.

4.1 Valmiit ikkunaluokat

Aikaisemmin mainittiin ikkunan luomisen yhteydessä, että ikkunaluokkana voidaan käyttää valmistakin ikkunaluokkaa. Esimerkiksi näppäimet, editointi ruudut, liukusäätimet, valintalistat jne. löytyivät valmiina luokkina.

4.1.1 Malli valmiiden luokkien käytöstä (WHELLO\THELLO.C)

Seuraavassa on malliohjelma, johon on kerätty joukko valmiita ikkunaluokkia:

	/****************************************************************************
	    PROGRAM: Thello.c
	    PURPOSE: Ohjelma tyypillisten kontrollien testaamiseen.
	    Editor:  Vesa Lappalainen  typistänyt malliohjelmista.
	    Project: thello.c, whello.def
	****************************************************************************/
	#include <windows.h>            /* Tarvitaan kaikissa Windows C-ohjelmissa */
	#include <windowsx.h>  
	
	#define MY_BUTTON 175           /* Nappulan tunniste                       */
	#define MY_EDIT   176           /* Tekstikentän (Edit) tunniste            */
	#define MY_BAR    177           /* Oman likusäätimen tunniste              */
	#define MY_STATIC 178           /* 1. vakiotekstin tunniste = liun arvo    */
	#define MY_BARS   179           /*  apuvakio seuraaville                   */
	#define MY_SVER   (MY_BARS+SB_VERT) /* 2. tekstikenän tunniste = pystyliun */
	#define MY_SHOR   (MY_BARS+SB_HORZ) /* 3. tekstikentän tunniste = vaakaliun*/
	
	#define MIN_BAR    0
	#define MAX_BAR   50
	
	static int value = 0;
	
	LONG CALLBACK MainWndProc(HWND hWnd, UINT message,
	                          WPARAM wParam, LPARAM lParam)
	{
	  PAINTSTRUCT ps;
	
	  switch (message)
	  {
	    case WM_PAINT:            /* Viesti: Piirrä ikkuna uudelleen       */
	      if ( BeginPaint(hWnd,&ps) )
	        TextOut(ps.hdc, 10, 10, "Hello World!",12);
	      EndPaint(hWnd,&ps);
	      return NULL;
	
	    case WM_COMMAND:
	      switch ( GET_WM_COMMAND_ID(wParam,lParam) ) {
	        case MY_BUTTON:     /* Painonappia painettu                  */
	          SetDlgItemInt(hWnd,MY_STATIC,++value,TRUE);
	          return NULL;
	        case MY_EDIT:       /* Jos viesti teksti-ikkunalta           */
	          switch ( GET_WM_COMMAND_CMD(wParam,lParam) ) {
	            case EN_CHANGE: /* Aina kun sisältö muuttuu, lisät.lask  */
	              SetDlgItemInt(hWnd,MY_STATIC,++value,TRUE);
	              return NULL;
	            case EN_KILLFOCUS: {/* Teksti-ikkunasta poistuttu        */
	              char svalue[20];
	              GetDlgItemText(hWnd,MY_EDIT,svalue,sizeof(svalue));
	              SetWindowText(hWnd,svalue); /* Sama tulos pääótsikkoon */
	              return NULL;
	            }
	          }
	          return NULL;
	        default: break;
	      }
	      break;
	
	    case WM_VSCROLL: {        /* Johonkin pysytliukuun koskettu        */
	      HWND shWnd = GET_WM_VSCROLL_HWND(wParam,lParam);
	      int fnBar = SB_CTL;
	      int id = GetDlgCtrlID(shWnd);
	      if ( shWnd == NULL ) { shWnd = hWnd; id = SB_VERT; fnBar = SB_VERT; }
	      switch ( GET_WM_VSCROLL_CODE(wParam,lParam) ) {
	        case SB_BOTTOM       : value = MIN_BAR; break;
	        case SB_TOP          : value = MAX_BAR; break;
	        case SB_LINEDOWN     : value++;         break;
	        case SB_PAGEDOWN     : value += 10;     break;
	        case SB_LINEUP       : value--;         break;
	        case SB_PAGEUP       : value -= 10;     break;
	        case SB_THUMBPOSITION:
	        case SB_THUMBTRACK   : value = GET_WM_VSCROLL_POS(wParam,lParam);
	                                                break;
	      }
	      SetScrollPos(shWnd,fnBar,value,TRUE);
	      SetDlgItemInt(hWnd,id == MY_BAR ? MY_STATIC : MY_SVER ,value, TRUE);
	      return NULL;
	    }
	
	    case WM_DESTROY:          /* Viesti: ikkuna hävitetään             */
	        PostQuitMessage(0);
	        return NULL;
	    default:                  /* Antaa Windowsin käsitellä muut        */
	        break;
	  }
	  return DefWindowProc(hWnd, message, wParam, lParam);
	}
	
	int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
	                   LPSTR lpCmdLine, int nCmdShow)
	{
	  HWND hWnd,hCtrl;/* Pääikkunan kahva ja apukahva kontrollien luontiin.  */
	  WNDCLASS  wc;   /* Ikkunaluokka                                        */
	  MSG msg;        /* Viesti                                              */
	  (void)lpCmdLine; /* Hämäystä, jottei valitusta param. käytt.           */
	
	  if (!hPrevInstance) {       /* Onko muita esiintymiä käynnisssä?       */
	    wc.style         = NULL;           wc.lpfnWndProc    = MainWndProc;
	    wc.cbClsExtra    = 0;              wc.cbWndExtra     = 0;
	    wc.hInstance     = hInstance;
	    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
	    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
	    wc.hbrBackground = GetStockObject(WHITE_BRUSH);
	    wc.lpszMenuName  = NULL;           wc.lpszClassName  = "WHelloWClass";
	    if ( !RegisterClass(&wc) ) return 1;
	  }
	
	  hWnd = CreateWindow("WHelloWClass","Windows Hello",
	           WS_CAPTION | WS_BORDER | WS_VSCROLL | WS_HSCROLL | WS_SYSMENU |
	           WS_THICKFRAME | WS_MINIMIZEBOX  |  WS_MAXIMIZEBOX,
	           50,50,300,300,NULL,NULL,hInstance,NULL);
	           if ( !hWnd ) return 1;
	
	  ShowWindow(hWnd,nCmdShow);        /* Näytetään ikkuna               */
	  hCtrl = CreateWindow("Button","Windows Hello",
	                       WS_BORDER |WS_CHILD | WS_VISIBLE ,
	                       10,30,100,30,hWnd,(HMENU)MY_BUTTON,hInstance,NULL);
	                       if ( !hCtrl ) return 1;
	  hCtrl = CreateWindow("Static","0",WS_CHILD | WS_VISIBLE | SS_RIGHT,
	                       40,70,40,30,hWnd,(HMENU)MY_STATIC,hInstance,NULL);
	                       if ( !hCtrl ) return 1;
	  hCtrl = CreateWindow("Static","0",WS_CHILD | WS_VISIBLE | SS_RIGHT ,
	                       80,70,40,30,hWnd,(HMENU)MY_SHOR,hInstance,NULL);
	                       if ( !hCtrl ) return 1;
	  hCtrl = CreateWindow("Static","0",WS_CHILD | WS_VISIBLE | SS_RIGHT,
	                       120,70,40,30,hWnd,(HMENU)MY_SVER,hInstance,NULL);
	                       if ( !hCtrl ) return 1;
	  hCtrl = CreateWindow("Edit","Windows Hello",
	                       WS_DLGFRAME | WS_CHILD | WS_VISIBLE,
	                       10,110,100,40,hWnd,(HMENU)MY_EDIT,hInstance,NULL);
	                       if ( !hCtrl ) return 1;
	  hCtrl = CreateWindow("Scrollbar","Windows Hello",
	                       WS_CHILD | WS_VISIBLE | SBS_VERT,
	                       200,10,30,200,hWnd,(HMENU)MY_BAR,hInstance,NULL);
	                       if ( !hCtrl ) return 1;
	
	  SetScrollRange(hCtrl,SB_CTL,MIN_BAR,MAX_BAR,TRUE);
	  SetScrollRange(hWnd,SB_VERT,MIN_BAR,2*MAX_BAR,TRUE);
	  SetScrollRange(hWnd,SB_HORZ,MIN_BAR,3*MAX_BAR,TRUE);
	
	  while ( GetMessage(&msg,NULL,NULL,NULL) ) {
	      TranslateMessage(&msg);     /* Tulkitaan virtuaaliset näp. koodit  */
	      DispatchMessage(&msg);      /* Lähetetään viesti ikkunalle         */
	  }
	  return msg.wParam;        /* Palautetaan PostQuitMessage-funktion arvo */
	}
	

Pääohjelmassa on aluksi luotu joukko erityyppisiä ikkunoita. Kullekin ikkunalle pitää määritellä oma tunnistin (esim. MY_BUTTON), jolla ikkunan toiminnan tapahtuminen tunnistetaan. Ikkunalta saadaan isä-ikkunalle WM_COMMAND -viesti, kun ikkunassa tapahtuu jotakin, joka vaatii viestin lähettämistä. Ikkunan tunniste saadaan sitten 16-bittisessä Windowsissa wParam -parametrissa ja mahdollinen lisätieto lParam -parametrin yläosassa (HIWORD). Parametrin alaosassa (LOWORD on ikkunan kahva).

32-bittisessä Windowsissa myös wParam on 32-bittinen ja ikkunoiden kahvat (HWND) ovat 32-bittisiä. Näin ollen koko lParam pitää varata ikkunan kahvalle, viestin lisätieto on siirretty wParam yläosaan ja ikkunan (kontrollin) tunniste on wParamin alaosassa. Seuraavassa wParam ja lParam molemmissa Windowseissa, kun esimerkiksi on muutettu editointikentän sisältöä (MY_EDIT), joka on luonnissa saanut tunnisteekseen vaikkapa 402C (MY_EDIT = 00B016 = 17610, EN_CHANGE = 030016):









Windows


wParam

lParam











Win 3.1


00B0

0300
402C











WIN32

0300
00B0

0000
402C










Jos haluamme ohjelman toimivan kummassakin ympäristössä, on parasta käyttää WINDOWSX.H:n makroja. Esimerkiksi viestin aiheuttaneen kontrollin tunnus, viestin lisätieto ja kontrollin ikkunakahva selvitetään makroilla

	id    = GET_WM_COMMAND_ID(wParam,lParam);
	cmd   = GET_WM_COMMAND_CMD(wParam,lParam);
	chWnd = GET_WM_COMMAND_HWND(wParam,lParam);

Huom! Makroja ei löydy vanhoista (esim. Borland C++ 3.1) WINDOWSX.H:sta. Tällöin pitää etsiä tilalle uudempi versio.

Kontrolleista poikkeuksen tekee liukusäädin, joka lähettää oman WM_VSCROLL tai WM_HSCROLL -viestin. Windows 3.1:ssä lParam -parametrin yläosassa on jälleen säätimen kahva (NULL, jos ikkunan liuku), alaosassa säätimen asento ja wParam -parametrissa säätimen tapahtuman tyyppi. Arvojen ottaminen on kuitenkin jälleen siirrettävyyden säilyttämiseksi tehtävä makroilla. Viestin parametrien perusteella pitää sitten toteuttaa säätimen aiheuttamat muutokset ruudulla. Liukusäätimen rajat asetetaan SetScrollRange -funktiolla.

Esimerkiksi EDIT-ikkunasta saadaan viesti aina, kun ikkunassa tapahtuu jotakin muutoksia. Esimerkissä muutokset lasketaan lukumääränä STATIC-ikkunaan, jonka arvoa voidaan muuttaa myös liukusäätimellä tai nappulan painamisella. Kun EDIT-ikkunasta poistutaan, kopioidaan EDIT-ikkunan sisältö pääikkunan otsikoksi. Jos EDIT-ikkunan määrittelyssä olisi annettu bitti ES_PASSWORD, olisi EDIT-ikkunaan tulostunut pelkkiä tähtiä.

4.1.2 Monirivinen EDIT-ikkuna (WHELLO\TTHELLO.C)

Seuraava malliohjelma (TTHELLO.C) on edellisen taulukkokäsittelijällä toteutettu versio, jossa on pieniä muutoksi edelliseen. Esimerkiksi liukujen yhteenkoplaus ja monirivinen EDIT-ikkuna.

Jos tarvitaan monirivistä EDIT-ikkunaa, voidaan tämä tehdä EDIT-ikkunasta ES_MULTILINE -määrityksellä. Ikkunaan laitettavaan tekstiin pitää tällöin muistaa laittaa rivinvaihtojen kohdalle \r\n, pelkkä \n ei riitä. Moniriviseen EDIT-ikkunaan saadaan myös automaattinen rullaus.

	hCtrl = CreateWindow("Edit","Windows\r\n Hello",
	                     0* WS_CAPTION    | 0* WS_BORDER     | 1* WS_DLGFRAME    |
	                     1* WS_VSCROLL    | 0* WS_HSCROLL    | 0* WS_SYSMENU     |
	                     1* WS_THICKFRAME | 0* WS_MINIMIZEBOX| 0* WS_MAXIMIZEBOX |
	                     0* WS_POPUP      | 1* WS_CHILD      | 1* WS_VISIBLE     |
	                     0* ES_PASSWORD   | 1* ES_MULTILINE  | 1* ES_UPPERCASE   |
	                     0* ES_AUTOHSCROLL| 1* ES_AUTOVSCROLL,
	                       10,160,100,80,msg->hWnd,(HMENU)MY_MEDIT,hInstance,NULL);

Itse asiassa alkeellinen editori (kuten esim. NOTEPAD) saadaan aikaan yhdellä monirivisellä EDIT-ikkunalla.

Joskus EDIT-ikkunan muisti halutaan omaan muistilohkoon ja tällöin EDIT-ikkuna pitää muistaa luoda DS_LOCALEDIT -lippua käyttäen. Tällöin EDIT-ikkunalle pitää myös ilmoittaa muistilohkon kahva. Etuna olisi esimerkiksi helpompi tiedoston käsittely, kun kaikki tieto on valmiiksi jo omassa muistilohkossa - tiedostoon voidaan kirjoittaa pelkkä yhden muistilohkon sisältö. Tosin ominaisuus on valittavissa vain dialogeissa luotuihin EDIT-ikkunoihin.

Jos monirivisessä EDIT-ikkunassa halutaan rivinvaihto pelkästään painamalla Return ja EDIT-ikkuna on luotu dialogi-resurssista, pitää luonnissa käyttää tyyliä ES_WANTRETURN (toimii vain Win 3.1 eteenpäin). Muuten rivinvaihtamiseksi joudutaan painamaan Ctrl-Return.

4.2 Ohjelman oikeellisuuden ja toiminnan tutkiminen

4.2.1 Viestien tutkiminen (WINSIGHT, SPY)

Seuraavassa on kuva WinSight-ohjelman ruudusta ajettaessa resurssiesimerkkiä THELLO.C, kun on siirrytty EDIT-ikkunaan ja tehty jokin sen sisältöä muuttava toimenpide:

Ohjelmalla voidaan siis monitoroida Windowsin ikkunoita ja niille tulevia viestejä.

Esimerkissä monitoroinnin (=seurannan) kohteeksi on valittu ikkuna jonka kahva on 3D14 (THELLO-ohjelman isäikkuna). Voitaisiin valita myös useampia ikkunoita jos haluttaisiin. Message -kohdasta voitaisiin valita mitä viestejä tutkitaan. Esimerkissä on valittu kaikki viestit.

Aluksi kursori on siirretty EDIT-ikkunan päälle ja napautettu. Siirtäminen ja napauttaminen on aiheuttanut mm. pääikkunalle WM_SETCURSOR-viestin kursorin muodon vaihtamiseksi.. Tämän jälkeen on painettu BS -näppäintä, josta on tullut viesti aluksi vain EDIT-ikkunalle (402C)! Siis pääikkuna ei ole saanut tätä viestiä. Sitten EDIT-ikkuna on lähettänyt pääikkunalle viestin EN_UPDATE tiedoksi siitä, että sen sisältöä aiotaan muuttaa. Koska tätä viestiä ei ole käsitelty ikkunafunktiossamme, käytetään Windowsin oletuskäsittelijää, joka palauttaa EDIT-ikkunalle tiedon että "muuta vaan". Kun muutos on tehty, on pääikkunalle lähetty viesti EN_CHANGE, tiedoksi siitä, että nyt se sisältö sitten muuttui. Tämä viesti on käsitelty ikkunafunktiossamme ja siinähän pyydettiin STATIC-ikkunaa lisäämään arvoaan yhdellä.

4.2.2 Debuggerit

Näillä viestien vakoiluohjelmilla voidaan tutkia tietysti esimerkiksi esiin tulevia ongelmia. Debuggereiden ja keon (heap) tutkimisohjelmien (esim. HeapWalk) kanssa ne muodostavat Windows-ohjelmoijan perustyökalut virheiden paikallistamiseen (ja oikean toiminnan varmistamiseen). Tavallisen peräkkäisesti käyttäytyvän ohjelman kanssahan pärjätään aika pitkälle pelkällä debuggerilla, mutta Windows-ohjelmissa ainakin askel kerrallaan suorittaminen on usein mahdotonta. Siis debuggerin käyttökin pitää perustua pitkälle ehdollisten pysäytyskohtien käyttöön (conditional breakpoints).

4.2.3 Muistin käyttö

Eräs yksinkertainen tapa tarkistaa ohjelman muistin- ja resurssien käyttö on katsoa Program Managerin Help/Aboutista muistin ja resurssien määrä ennen ohjelman käynnistystä, ajaa ohjelma ja katsoa resurssien määrä uudelleen. Yhtään ei saisi hävitä. Vielä helpompi on esimerkiksi käyttää WINDOWS\VLWINAPP\RESMETER\SORSAT\RESMETER.C:n käännettyä versiota, joka voidaan mukavasti nollata ennen ohjelman ajamista.

4.3 Resurssit

Seuraavaksi teemme autolaskurin Windows-version:

4.3.1 Resurssitiedosto (WINLASKI\RESLASK\LASKURIR.RC)

Näppäinten, static-kenttien jne. paikan laskeminen ja sijoittelu näytölle on epäinhimillistä puuhaa. Lisäksi tällaisen tiedon ei tulisi olla kiinteästi ohjelmakoodissa (aika irralleenhan se oli saatu autolaskurin DOS-versiossakin).

Windowsissa onkin tapa esittää eri resursseja C-kielen ulkopuolisella resurssitiedostolla. Esimerkiksi autolaskuria vastaava dialog-laatikko saataisiin seuraavalla tiedostolla:

	#include "laskurir.h"
	LASKURI DIALOG 17, 25, 180, 88
	STYLE WS_OVERLAPPED | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX
	CAPTION "LASKURIR"
	{
	 CONTROL "&Henkilöautoja", HA, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP, 
	         33, 10, 50, 20
	 CONTROL "&Kuorma-autoja", KA, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP, 
	        100, 10, 50, 20
	 CONTROL "0 ", HAL, "STATIC", SS_RIGHT | WS_CHILD | WS_VISIBLE | WS_BORDER | 
	        WS_GROUP, 31, 40, 50, 10
	 CONTROL "0 ", KAL, "STATIC", SS_RIGHT | WS_CHILD | WS_VISIBLE | WS_BORDER | 
	        WS_GROUP, 100, 40, 50, 10
	 CONTROL "&Nollaa", NOLLAA, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP, 
	        50, 60, 80, 20
	 CONTROL "e&Xit", EXIT, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP, 
	        0, 0, 24, 14
	}
	
	PIKA ACCELERATORS 
	BEGIN
		"K", KA
		"k", KA
		"H", HA
		"h", HA
		"N", NOLLAA
		"n", NOLLAA
		"X", EXIT
		"x", EXIT
	END

Dialogin yksikkö riippuu dialogissa käytetyn fonttityypin kirjaimen leveydestä ja korkeudesta siten, että yksikkönä on vaakasuuntaan 1/4 kirjaimen leveydestä ja pystysuuntaan 1/8 kirjaimen korkeudesta.

Kun ohjelma on käännetty ja kertaalleen linkitetty, liitetään vielä käännetty resurssitiedosto (.RES) ohjelmatiedostoon (.EXE). Tämä kaikki hoituu automaattisesti, mikäli myös resurssitiedosto (.RC) on lisätty projektiin kuuluvaksi.

HUOM 1! Jos resurssitiedosto tehdään esimerkiksi Borlandin resurssieditorilla, kannattaa tarkistaa että nappula on todella "BUTTON" tyyppiä eikä esim."BORBTN" -tyyppiä. "BOR" -alkuiset tyylit vaativat että ajon aikana on ladattuna BWCC.DLL -moduli. Jollei ole, niin ne dialogit, joissa on "BOR" -alkuisia tyylejä, eivät näy ruudussa!

HUOM 2! Tässä tapauksessa pikanäppäinten (Accelerators) määrittely on tarpeetonta, koska samat näppäimet toimivat joka tapauksessa alleviivauksen ansiosta!

4.3.2 Vakiot (WINLASKI\RESLASK\LASKURIR.H)

Vakiot HA,HAL jne. on kirjoitettu tiedostoon LASKURIR.H (usein tiedoston tarkentimena käytetään myös .RH, Resource Header):

	#define LASKUREITA 2
	#define HA      1000
	#define KA      HA+1
	#define HAL     1010
	#define KAL     HAL+1
	#define EXIT    1027
	#define NOLLAA  1028

Vakioiden tarkoituksena on tietysti taas toimia tunnistimena nappuloille ja static-kentille.

4.3.3 Pääohjelma (WINLASKI\RESLASK\LASKURIR.C)

Itse ohjelma on periaatteessa samanlainen kuin ennenkin, mutta jos varsinaista pääikkunaa ei luoda ollenkaan, ei tietenkään vastaavaa ikkunaluokkaakaan tarvitse esitellä. Pääikkunana toimii nyt edellä suunniteltu dialogi (tästä seuraa myöhemmin hieman lisävaivaa ikoneiden kanssa).

	/**************/
	/* laskurir.c */
	/****************************************************************************
	**  PROGRAM: laskurir.c
	**  Windows: Win 3.1 & WIN32
	**  PURPOSE: Autolaskuri tehtynä mahd. pitkälle Windowsin resursseilla.
	**  Editor:  Vesa Lappalainen
	**    Projektiin tarvitaan
	**      laskurir.c    - tämä tiedosto
	**      laskurir.def  - moduulin määrittely
	**      laskurir.rc   - resurssit
	**
	** Tehtäviä: 1)  Lisää polkupyörien laskeminen (käytä .rc tiedoston 
	**               korjailemiseen Resource WorkShopia)
	**
	**           2)  Lisää +/- toiminto näppäinten avulla
	**
	**           3)  Muuta .rc tiedostoa siten, että myös DEL-näppäin
	**               tyhjentää näytön.
	**
	****************************************************************************/
	#include <windows.h>
	#include <windowsx.h>           /* Tarvitaan 32-bit yhteensopivuudeksi     */
	#include "laskurir.h"
	
	static int laskurit[LASKUREITA] = {0,0};
	
	BOOL CALLBACK _export MainWndProc(HWND hWnd, UINT message,
	                                  WPARAM wParam, LPARAM lParam)
	{
	#pragma argsused
	  switch (message) {
	    case WM_COMMAND: {
	      int id = GET_WM_COMMAND_ID(wParam,lParam); /* Jotta WIN32 toimisi */
	      switch ( id ) {
	        case HA:             /* Painonappia painettu                       */
	        case KA:
	          SetDlgItemInt(hWnd,id-HA+HAL,++laskurit[id-HA],TRUE);
	          return TRUE;
	        case NOLLAA: {
	          int i;
	          for (i=0; i<LASKUREITA; i++)
	            SetDlgItemInt(hWnd,i+HAL,laskurit[i] = 0,TRUE);
	          return TRUE;
	         }  /* NOLLAA */
	        case IDCANCEL:
	        case EXIT:
	          PostQuitMessage(0);
	          return TRUE;
	      } /* switch (id)        */
	    }   /* WM_COMMAND         */
	  }     /* switch ( message ) */
	  return FALSE;
	}
	
	int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
	                   LPSTR lpCmdLine, int nCmdShow)
	{
	#pragma argsused
	  MSG     msg;
	  FARPROC lProc  = MakeProcInstance((FARPROC)MainWndProc,hInstance);
	  HWND    hWnd   = CreateDialog(hInstance,"LASKURI",NULL,(DLGPROC)lProc);
	  HACCEL  hAccel = LoadAccelerators(hInstance,"PIKA");
	
	  if ( !hWnd ) goto lopetus;
	  while (GetMessage(&msg,NULL,NULL,NULL)) {
	    if ( TranslateAccelerator(hWnd,hAccel,&msg) ) continue;
	    if ( IsDialogMessage(hWnd,&msg)  )            continue;
	    TranslateMessage(&msg);     /* Tulkitaan virtuaaliset näp. koodit      */
	    DispatchMessage(&msg);      /* Lähetetään viesti ikkunalle             */
	  }
	lopetus:
	  (void)FreeProcInstance(lProc); /* void, jottei 32-bit kääntäjä valita */
	  return 0;
	}

Ikkunafunktion erona tavalliseen ikkunafunktioon on se, etteivät dialogin ikkunafunktiot saa kutsua oletuskäsittelijää, vaan niiden on palautettava TRUE, mikäli viesti käsiteltiin ja FALSE, mikäli viestiä ei käsitelty. Tämä on eräs inhottava piirre Windowsissa: eri ikkunat täytyy käsitellä eri tavoin. Ongelmaa on yritetty korjata ali\tabhand.h:n ja ali\mdialog.h:n makroilla (ks. winlaski\tablask\laskurir.c).

4.3.4 Resurssieditori (Resource Workshop)

Kaikki edellä mainitut tiedostot voidaan tietysti kirjoittaa millä tahansa tekstieditorilla, mutta .RC ja .H -tiedostojen tekemiseen voidaan käyttää myös resurssityökaluja. Yksi esimerkki on Borlandin Resource Workshop. Seuraavat työvaiheet ovat BC 4.0:n mukana tulevan RWS:n käyttämiseksi:

1. Käynnistetään resurssieditori.

2. Valitaan uusi projekti (ei ole sama asia kuin kääntäjän projekti) ja projektin tyypiksi .RC

3. h-tiedoston lisääminen: Lisätään projektiin kuuluva .h -tiedosto antamalla nimeksi LASKURIR.H sekä luomiseen vastataan Yes.

4. Parasta on heti antaa projektille nimi File, Save file as, ja tarkistaa myös että talletus tapahtuu oikeaan hakemistoon.

5. Dialogin tekeminen: Valitaan Resource, New ja tyypiksi Dialog. Dialogin tyypiksi valitaan vielä "Standard Window. No Buttons." Seuraavassa on myöhemmän vaiheen valmiiksi täytetty dialogi.

6. Näyttöön tulee tyhjä dialogi-laatikko. Dialogin nimi ja ominaisuudet voidaan vaihtaa näpäyttämällä dialogin otsikkoriviä 2 kertaa ja täyttämällä lomake seuraavasti:

HUOM! Huomaa erityisesti merkitä Visible -kohta, muuten esimerkiksi Modeless-dialogi ei näy ruudussa oletuksena.

7. Kun lomake on hyväksytty, voidaan dialogi venytellä sopivan kokoiseksi tai sitä voidaan venytellä myöhemmin.

8. Nappulan lisääminen: Näyttöön voidaan piirtää eXit-nappula valitsemalla oikeasta laidasta OK-nappulan näköinen työkalu ja siirtämällä se halutulle kohdalle näytössä. Nappulan koko voidaan muotoilla halutuksi. Nappulan tekstirivi voidaan muuttaa oikeassa laidassa olevassa Properties, Caption-kentässä tai näpäyttämällä nappulaa 2 kertaa. (Jos jokin kirjain halutaan alleviivatuksi, pitää sitä edeltää &-merkki, siis e&Xit).

9. Näpäytetään nappulaa 2 kertaa ja saadaan esille lomake, johon voidaan täyttää näppäimen ominaisuuksia.

10. Kirjoitetaan Control ID -kenttään näppäimen tunniste, joksi voidaan valita vaikkapa EXIT . (Jos täytetään Properties kohtaa, pitää muutoksia tehdä kohtiin IdSource ja IdValue).

11. Kun lomake hyväksytään, on tunniste EXIT tuntematon, jolloin sille annetaan jokin uusi arvo. Jos arvoa halutaan muuttaa, voidaan se tehdä menun Resource, Identifieres -kohdasta (tai Properties, IdValue). Tunnisteen arvoksi annetaan haluttu kokonaisluku, vaikkapa 1027 ja tarkistetaan onko tiedostona varmasti oikea .h tiedosto.

12. Vastaavasti lisätään kaikki muut nappulat ja keksitään kullekin hyvä kuvaava tunniste (Control ID) ja sopiva numero (ohjelma kyllä myös ehdottelee numeroita, mutta C-ohjelman kannalta on hyödyllistä saada Henkilöauto ja Kuorma-auto näppäinten tunniste peräkkäisiksi, samoin vastaavien näyttöjen). Kannattaakin ehkä tehdä tarvittavat tunnisteet menun Resource, Identifieres-kohdasta, jolloin voidaan esimerkiksi määrittää KA:lle arvo HA+1.

Huomaa, että kontrolleja (nappuloita jne.) voi laittaa samaan linjaan Alignment-työkalujen avulla. Saman kokoisiksi kontrolleja voidaan tehdä kohdan Align, Size -avulla. Myös valmiin kontrollin kopioiminen on hyvä idea! Hiiren oikealla näppäimellä saadaan myös Dublicate- toiminto, jolla on mukava tehdä esimerkiksi laskukoneen näppäimistö.

13. Tekstikentän lisääminen: Näytöt lisätään STATIC-kenttinä valitsemalla työkaluksi iso T-kirjain. Muotoillaan näyttö sopivan kokoiseksi ja käydään muuttamassa näytön tekstiksi "0 " (huom. ainakin yksi välilyönti loppuun jos haluat näytöstä siistimmän näköisen). Samalla laitetaan teksti oikeaan reunaan ja pyydetään tekstille raamit:

14. Kun dialogi on valmis, suljetaan dialogi-ikkuna koko dialogieditorin vasemmasta yläkulmasta (EI Resource Workshopin, parasta ehkä painaa Ctrl-F4, niin ei tule vahinkoa).

15. Resurssin nimen vaihto: Dialogin nimeksi tuli oletuksena DIALOG_1. Tämä voidaan vaihtaa siirtymällä ensin projekti-ikkunassa nimen päälle ja valitsemalla sitten: Resource, Rename (tai painetaan hiiren oikeaa näppäintä) ja kirjoitetaan uusi nimi LASKURI. Nyt kysymykseen "Create new identifier?" vastataan NO, mikäli halutaan ohjelmakoodissa käyttää nimeä merkkijonona kuten malliohjelmissa on tehty. Jos vastataan Yes, voi käydä helposti niin, että dialogi ei näy ruudussa. Jos vahingossa kuitenkin vastattiin Yes, voidaan myöhemmin poistaa tunniste laskuri LASKURIR.H-tiedostosta.

16. Pikanäppäimet: Nyt näppäimiltä saadaan viestit kun painetaan alleviivattuja kirjaimia Alt-näppäimen kanssa. Viestit pitäisi saada myös K, H, N ja X -näppäimistä. Näin tapahtuu dialogin ansiosta automaattisesti, koska ko. näppäimille ei missään kohdassa ole muuta käyttöä. Esimerkin vuoksi luomme kuitenkin vielä pikanäppäin-resurssin (Accelerator): (Resource, New, Accelerator).

17. Taulukkoon Command -kohtaan täytetään mikä viesti pitäisi saada näppäintä painamalla. Vaikka x näppäintä varten pitäisi saada viesti EXIT. Siis kirjoitetaan Command-kohtaan EXIT.

18. Siirrytään TAB-näppäimellä kenttään Key. Nyt painetaan sitä näppäinyhdistelmää, jolla viesti halutaan. Tässä tapauksessa painetaan pelkkä näppäintä x. Näppäimen valinta lopetetaan painamalla ESC. Lopuksi vielä kuitataan tehdyt toimenpiteet Return -näppäimellä.

19. Samalla tavalla täytetään kaikki muutkin "pikanäppäimet". Muista laittaa myös vastaavat isot kirjaimet.

20. Nimetään ACCELERATORS_1 resurssi nimelle PIKA vastaavasti kuin dialogi-resurssin nimen vaihdossa. (Muista vastata NO!)

21. Talletus: Lopuksi valitaan File, Save Project. Tosin talletus voidaan tehdä työn aikana varalta useitakin kertoja.

22. Nyt meillä on tiedostot LASKURIR.RC ja LASKURIR.H ja voidaan kirjoittaa tarvittava C-ohjelma.

Myöhemmin valmiita tiedostoja voidaan editoida joko editorilla tai jälleen Resource Workshopilla.

Täytyy kuitenkin olla tarkkana, ettei sama tiedosto ole yhtäaikaa avoinna kahdessa eri ohjelmassa! Jos tiedosto on avoinna esimerkiksi Resource Workshopissa ja C-editorissa, käyttää kääntäjä C-editorin tiedostoa eikä tällöin RWS:llä tehdyt muutokset tunnu vaikuttavan!

4.3.5 Menut mukaan (WINLASKI\TABLASK\MENU2\LASKURIM.RC)

Oikeassa Windows ohjelmassa on tietysti aina myös menuja. Autolaskuriin menut ovat ehkä vähän keinotekoisia, mutta mikseipä laskureiden tilaa voisi tallettaa tiedostoon, niiden tilaa laittaa leikekirjaan tai päinvastoin. Lisäksi laskureiden suunta voitaisiin valita Options-kohdasta.

Menut voidaan kirjoittaa joko tekstieditorilla tai jollakin resurssieditorilla. Resurssieditorin hyvä puoli on siinä, että menun toimintaa voi heti kokeilla. Usein käytetään molempia sopivasti vuorotellen. Windows-ohjelmia käytettäessä on kuitenkin syytä jälleen huomata, ettei sama tiedosto ole vahingossa auki kahdessa ohjelmassa yhtäaikaa!

Menuja suunniteltaessa on syytä pitäytyä valmiissa termeissä ja menujen sijoituksissa. Lähes jokaisessa Windows-ohjelmassa on vasemmassa laidassa toiminnot File ja Edit. Vastaavasti oikeassa laidassa (Help break) on toiminto Help. Muut ohjelman menutoiminnot sijoitetaan näiden väliin taas sopivasti valmiista ohjelmista matkien.

Menujen suunnittelu onkin hyvä tapa aloittaa ohjelman suunnittelu.

4.3.6 Menujen käyttö (WINLASKI\TABLASK\MENU2\LASKURM2.C)

Menu voidaan liittää joko dialogiin, ilmoittaa ikkunaluokan alustuksessa tai ottaa myöhemmin käyttöön kutsuilla

	hMenuE = LoadMenu(hInstance,"EPAAMENU");
	SetMenu(hWnd,hMenuE);
	...
	DestroyMenu(hMenuE); /* Muista tuhota menut lopuksi!!! */

Menusysteemi on itsenäisesti toimiva kokonaisuus, joka antaa menun omistavalle ikkunalle WM_COMMAND -viestin kun menusta on lopulta valittu haluttu toiminto. Viestin ID-osassa (GET_WM_COMMAND_ID) tulee menuvalinnalle annettu kokonaislukuarvo. Vain menupuun lehtisolmuista saadaan viesti. Toiminnot kannattaa numeroida kuvaavasti siten, että myöhemmin ohjelmakoodissa muistaa mikä valinta tarkoitti mitäkin. Numeroarvot eivät ole suositeltavia. Hyvä nimi olisi esimerkiksi CM_FILE_NEW:

komento CM (CoMmand),

alasvetovalikko File (FILE)

ja sen alakohta New (NEW).

Menujen valinnat voidaan jo suunnitteluvaiheessa merkitä valituiksi (Checked, v-merkki) tai ei-aktiivisiksi (disabled, grayed, harmaa). Käytön mukaan näitä sitten ohjelmassa muutetaan esimerkiksi kutsulla

	CheckMenuItem(hMenu,IDM_OPTIONS_DEC,MF_BYCOMMAND | MF_UNCHECKED);
	EnableMenuItem(hMenu,IDM_OPTIONS_DEC,MF_BYCOMMAND | MF_GRAYED);

Kahva menuun saadaan esimerkiksi kutsulla:

	  HMENU hMenu = GetMenu(hWnd);

Edellä MF_BYCOMMAND tarkoittaa, että menun valintaa etsitään tunnuksen (tässä IDM_OPTIONS_DEC) mukaan. Toinen vaihtoehto olisi järjestyksen mukaan, mikä ei suinkaan ole hyvä vaihtoehto, jos joku muuttaa menun valintojen järjestystä.

Voitaisiin esimerkiksi tehdä ohjelma, jossa olisi käytettävissä kaksi kieltä: suomi ja englanti. .RC -tiedostoon kirjoitettaisiin kaksi erillistä menua, joissa toisessa olisi kielen vaihto suomeksi ja toisessa englanniksi ja tietysti kumpikin menu omalla kielellään. Jossakin kohti alustuksissa ladattaisiin kahvoihin hMenuE ja hMenuF vastaavat menut. Ikkunan funktiossa sitten menuja vaihdettaisiin seuraavasti:

	  case WM_COMMAND:    /* Dialogin toiminnot:                             */ 
	    switch ( GET_WM_COMMAND_ID(wParam,lParam) ) {
	...
	      case IDM_OPTIONS_ENG:
	        SetMenu(hWnd,hMenuE);
	        return TRUE;
	
	      case IDM_OPTIONS_FIN:
	        SetMenu(hWnd,hMenuF);
	        return TRUE;
	...

Menuihin voitaisiin myöhemmin vaihtaa bittikarttoja tekstien tilalle kutsulla:

	ModifyMenu(hMenu,IDM_OPTIONS_FIN,
	                 MF_BYCOMMAND | MF_BITMAP,IDM_OPTIONS_FIN,
	                 (LPSTR)hBitFin) ) 

Toisaalta bittikarttoja voitaisiin suoraan lisätä kutsulla:

	InsertMenu(hMenu,IDM_OPTIONS_FIN,
	                       MF_ENABLED | MF_BITMAP,IDM_OPTIONS_ENG,
	                       (LPSTR)hBitEng);

4.4 Valmiit dialogit ja tiedostojen käsittely

Windows 3.1:stä löytyy joukko valmiita dialogeja:

	ChooseColor       - värin valintadialogi, vertaa 
	ChooseFont        - fontin valinta
	FindText          - tekstin etsintädialogi 
	GetOpenFileName   - tiedoston nimi avaamista varten -dialogi
	GetSaveFileName   - tiedoston nimi tallettamista varten -dialogi
	PrintDlg          - dialogi tulostustietojen kysymistä varten 
	ReplaceText       - tekstin korvaamisdialogi

Näiden käyttö selviää parhaiten Helpin avulla.

4.4.1 Tiedoston nimi (ALI\FILENAME.C)

Esimerkiksi tiedoston nimen selvittämiseksi voitaisiin kirjoittaa seuraava aliohjelma:

	/***************************************************************************/
	DWORD AskFileName(HWND hwnd,int save,char *FileName)
	{
	  static OPENFILENAME ofn;
	  static char szDirName[256]=".";
	  static char szFile[256]="", szFileTitle[256];
	  static char szCustomFilter[256]="(*.*)\0*.*\0";
	  static int  FirstTime = 1;    /* Call this first time */
	  static char  szFilter[256]=
	   "Laskuri (*.las)\0*.las\0Teksti (*.txt)\0*.txt\0Kaikki (*.*)\0*.*\0";
	  DWORD error = 0;
	
	  if ( FirstTime ) { /* If first call.                     */   
	    FirstTime = 0;
	    /* Set all structure members to zero on first time. */
	    memset(&ofn, 0, sizeof(OPENFILENAME));
	
	    ofn.lStructSize       = sizeof(OPENFILENAME);
	    ofn.lpstrFilter       = szFilter;
	    ofn.nFilterIndex      = 1;
	    ofn.lpstrFile         = szFile;
	    ofn.nMaxFile          = sizeof(szFile);
	    ofn.lpstrCustomFilter = szCustomFilter;
	    ofn.nMaxCustFilter    = sizeof(szCustomFilter);
	    ofn.lpstrFileTitle    = szFileTitle;
	    ofn.nMaxFileTitle     = sizeof(szFileTitle);
	    ofn.lpstrInitialDir   = szDirName;
	  }
	  else
	    if ( save ) strcpy(szFile,FileName);
	    else szFile[0]=0;
	
	  ofn.hwndOwner = hwnd;
	  ofn.Flags = OFN_SHOWHELP | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
	
	  if ( ( save ? GetSaveFileName(&ofn) : GetOpenFileName(&ofn) ) == TRUE ) {
	    char s[80];
	    wsprintf(s,"%s - [%s]",(LPSTR)WTITLE,ofn.lpstrFileTitle);
	    SetWindowText(hwnd,s);
	    strcpy(FileName,szFile); /* strcpy is bad idea! */
	    return 0;
	  }
	
	  if ( CommDlgExtendedError() == 0 )
	      return -1; /* Only user pressed CANCEL or something like that        */
	  return error;
	}

4.4.2 Tiedostot (ALI\FILENAME.C)

Koska useimmiten kaikissa ohjelmissa on samanlaiset tiedostojen käsittelyt, kannattanee kirjoittaa valmis aliohjelmakirjasto tiedostojen yleiseen käsittelyyn. Jokaisella ohjelmalla on tietysti oma tapansa tallettaa ja lukea tiedostoja ja nämä tehtävät voidaankin sitten jättää ohjelman itsensä huoleksi.

Yleinen kirjasto hoitaa seuraavat asiat:

uuden tiedoston luonti (vanhan talletus)

tiedoston avaaminen (vanhan talletus)

tiedoston talletus jos sitä muutettu (jollei nimeä, kysytään nimi)

vanhojen tiedoston listan ylläpito File -menussa

Käyttäjä täyttää tiedostoa kuvaavan tietorakenteen:

	static FileType File={
	  "",                           /* Tiedoston nimi, aluksi ""               */
	  WTITLE,                       /* Ikkunan otsikko, jonka perään tied.nimi */
	  1,                            /* Luetun funktion tyyppi, ks. szFilter    */
	  "Laskuri (*.las)\0*.las\0Teksti (*.txt)\0*.txt\0Kaikki (*.*)\0*.*\0",
	  0,                            /* Onko tiedostoa muutettu.                */
	  MySave,                       /* Osoitin talletusfunktioon               */
	  MyRead,                       /* Osoitin lukufunktioon                   */
	  MyNew,                        /* Osoitin tiedon nollausfunktioon         */
	  laskurit                      /* Osoitin varsinaiseen dataan.            */
	};
	

missä MySave, MyRead ja MyNew ovat käyttäjän kirjoittamia omia funktioita talletukseen, lukemiseen ja uuden luomiseen.

4.4.3 MessageBox (WINLASKI\TABLASK\MENU2\LASKURM2.C)

Ohjelman looginen toiminta on tietysti helpointa ensin testata "tyhjällä" ohjelmalla, jossa jokaiseen menutoimintaa reagoidaan jotenkin yksinkertaisesti, esimerkiksi sopivalla viestillä käyttäjälle. Tällaisten viestien lähettämiseksi Windosissa on valmis dialogi: MessageBox. Esimerkiksi tiedoston talletus voisi aluksi olla seuraavasti: Ikkunan funktiossa WM_COMMAND -viestiin vastattaisiin:

	/*-------------------------------------------------------------------------*/
	/* FILE */
	static EVENT WM_command_IDM_FILE_NEW(tMSGParam *msg)
	{
	  return NewFile(msg->hWnd,&File);
	}
	
	static EVENT WM_command_IDM_FILE_OPEN(tMSGParam *msg)
	{
	  return OpenAndReadFile(msg->hWnd,&File);;
	}
	
	static EVENT WM_command_IDM_FILE_SAVEAS(tMSGParam *msg)
	{
	  return SaveFileAs(msg->hWnd,&File);
	}
	
	static EVENT WM_command_IDM_FILE_SAVE(tMSGParam *msg)
	{
	  return SaveFile(msg->hWnd,&File);
	}
	
	/*-------------------------------------------------------------------------*/
	/* OPTIONS */
	static EVENT WM_command_IDM_OPTIONS_INC(tMSGParam *msg)
	{
	  HMENU hMenu = GetMenu(msg->hWnd);
	  CheckMenuItem(hMenu,IDM_OPTIONS_DEC,MF_BYCOMMAND | MF_UNCHECKED);
	  CheckMenuItem(hMenu,IDM_OPTIONS_INC,MF_BYCOMMAND | MF_CHECKED);
	  suunta = 1;
	  return 0;
	}
	
	static EVENT WM_command_IDM_OPTIONS_DEC(tMSGParam *msg)
	{
	  HMENU hMenu = GetMenu(msg->hWnd);
	  CheckMenuItem(hMenu,IDM_OPTIONS_DEC,MF_BYCOMMAND | MF_CHECKED);
	  CheckMenuItem(hMenu,IDM_OPTIONS_INC,MF_BYCOMMAND | MF_UNCHECKED);
	  suunta = -1;
	  return 0;
	}

Sitten testejä varten talletus olisi vain:

	/***************************************************************************/
	int MySave(FileType *File)
	{
	  MessageBox(File->hWnd,File->Name,"Talletus:",
	             MB_APPLMODAL |  MB_OK | MB_ICONEXCLAMATION);
	  return 0;
	}

MessageBox palauttaa arvonaan tiedon siitä, mitä nappulaa painettiin laatikosta poistumiseksi. Tämän avulla on helppo laatia mm. kyllä/ei -tyylisiä valintoja:

	...
	wsprintf(s,"%s\nTiedostoa muutettu.\n\nTalletetaanko?",
	             (LPSTR)File->Name);
	  answ = MessageBox(hWnd,s,File->Title,MB_APPLMODAL | MB_YESNOCANCEL |
	                           MB_ICONEXCLAMATION);
	  switch ( answ ) {
	    case IDYES: if ( (error = SaveFile(hWnd,File)) != 0 ) return error;
	    case IDNO:  return ClearFileName(hWnd,File,ClearName);
	    default: return -1;
	  }

MB_ICONEXCLAMATION tarkoittaa huutomerkkiä laatikon vasempaan reunaan. Myös STOP-merkki, kysymysmerkki ja i-kirjain ovat mahdollisia.

4.4.4 Oikeat tiedostot (WINLASKI\TABLASK\MENU3\LASKURM3.C)

Kun ohjelma loogisesti toimii, voidaankin lisätä oikea tiedoston käsittely:

	static int MySave(FileType *File)
	{
	  static OFSTRUCT OfStruct;
	  int count,n;
	  static char *s;
	  HFILE hFile = OpenFile(File->Name,&OfStruct,OF_CREATE);
	
	  if ( hFile == HFILE_ERROR )  goto error;
	
	  s     = laskurit_tekstiksi((int *)File->data);
	  count = strlen(s);
	  n     = _lwrite(hFile,s,count);
	
	  _lclose(hFile);
	  if ( n == count ) return 0;
	
	error:
	  MessageBox(File->hWnd,File->Name,"Talletus epäonnistui",
	             MB_APPLMODAL |  MB_OK | MB_ICONEXCLAMATION);
	  return -1;
	}

Windowsissa voidaan käyttää myös normaaleja fopen, fgets, fprintf ja fscanf -funktioita. Kuitenkin Windowsin luonteen takia suositellaan käytettäväksi funktioita OpenFile, _lwrite, _lread ja _lclose. Tosin rivi kerrallaan lukemista varten täytyy tällöin tehdä itse fgets:ää vastaava funktio. OpenFile:n parametreillä voidaan valita miten tiedosto avataan:

	OF_CANCEL           - CANCEL-nappula levynvaihto viestiin
	OF_CREATE           - luo uuden tiedoston, vanha häviää	
	OF_DELETE           - tuhoaa tiedoston
	OF_EXIST            - avaa ja sulkee, kätevä tiedoston olemassaolon testaamiseksi
	OF_PARSE            - täyttää OFSTRUC-tietueen, muttei tee muuta
	OF_PROMPT           - pyytää tarvittaessa levykettä asemaan 
	OF_READ             - avataan vain lukemista varten
	OF_READWRITE        - lukemista ja kirjoittamista varten
	OF_REOPEN           - jo avattu (ja suljettu) tiedosto avataan uudelleen
	OF_SEARCH           - tiedostoa etsitään koko polusta
	OF_SHARE_COMPAT     - muutkin saavat avata tiedoston, jopa useasti
	OF_SHARE_DENY_NONE  - muutkin saavat avata
	OF_SHARE_DENY_READ  - muut eivät saa lukea tiedostoa
	OF_SHARE_DENY_WRITE - muut eivät saa kirjoittaa
	OF_SHARE_EXCLUSIVE  - muut eivät saa lukea eivätkä kirjoittaa
	OF_VERIFY           - vertaa tiedoston luontiaikaa OFSTRUCT tietueen tietoihin
	OF_WRITE            - avaa kirjoittamista varten

Itse lukeminen ja kirjoittaminen tapahtuvat sitten C:n alemman tason ei-formatoiduilla funktioilla.

Huomattakoon, ettei OpenFilellä avattu kahva ole yhteensopiva fprintf vaatiman FILE -tyyppisen osoittimen kanssa.

Kun ohjelma menettää kontrollin, olisi hyvä sulkea tiedostot. Jokin toinen ohjelmahan saattaa tänä aikana pyytää vaikkapa levykkeen vaihtoa. Kun kontrolli jälleen palautuu ohjelmalle, voidaan uudelleen avaaminen tehdä OF_REOPEN -parametrilla. Tämän vuoksi OpenFile -funktion käyttö on suositeltavaa.

4.4.5 .INI tiedostot (WINLASKI\TABLASK\MENU3\LASKURM3.C)

Mikäli tiedostoon talletettava asia on hyvin lyhyttä tai jopa ohjelman asetuksiin liittyvää, on Windowsissa helpointa käyttää .INI-tiedostoja.

WIN.INI on yksi tyypillinen tällainen tiedosto. Tähän tiedostoon olisi kuitenkin suositeltavaa kirjoittaa vain informaatiota, joka on tarkoitettu muidenkin ohjelmien käytettäväksi. Lukeminen ja kirjoittaminen tapahtuu GetProfileString ja WriteProfileString -funktioilla. Kokonaisluvun lukemiseksi on lisäksi GetProfileInt -funktio.

Ohjelman omat tiedot talletetaan vastaavassa formaatissa olevaan ohjelman omaan .INI -tiedostoon. Tätä varten on vastaavat ???PrivateProfile??? -funktiot Esimerkiksi laskurin tila voitaisiin ohjelman lopuksi tallettaa tiedostoon LASKURI.INI muodossa:

	[Laskuri]                             - 1. param. määrää tämän nimen
	Laskuri=21 5                          - 2. param. määrää tämän nimen

Ohjelmakoodissa talletus olisi vaikkapa kun saadaan ohjelman lopettamista vaativa viesti:

	#define WTITLE "Laskuri"
	#define ININAME ".\\"WTITLE".INI"
	...
	static EVENT lopeta(tMSGParam *msg)
	{
	  WritePrivateProfileString(WTITLE,WTITLE,
	    laskurit_tekstiksi(laskurit), ININAME);
	  if ( NewFile(msg->hWnd,&File) == 0 ) PostQuitMessage(0);
	  return 0;
	}

Ensimmäinen WTITLE -parametri tarkoittaa hakasulkeisiin tulevan osion nimeä, toinen osion alle tulevaa valintaa Laskuri=. Näiden ei tietenkään tarvitse olla samoja.

Tiedoston lukeminen tapahtuu vastaavasti ikkunan syntyessä:

	static EVENT WM_initdialog(tMSGParam *msg)
	{
	  char s[50];
	  GetPrivateProfileString(WTITLE,WTITLE,"",s,sizeof(s), ININAME);
	  teksti_laskureiksi(msg->hWnd,s,laskurit);
	  return 0;
	}..

4.5 Leikekirja ja dynaamisen muistin käyttö

Tärkeä tapa sovellusten väliseen tiedonsiirtoon on leikekirja (Clipboard). Leikekirjaan ohjelma voi laittaa mm. tekstiä ja grafiikkaa. Käytössä on erilaisia graafisia formaatteja. Samoin ohjelma voi tietysti lukea leikekirjaa. Ohjelman on tietysti osattava käytetyt formaatit. Tutustumme seuraavassa tekstimuotoisen tiedon siirtämiseen leikekirjan avulla.

4.5.1 Globaalin muistin varaus: Win 3.1

Windowsissa käyttäjä voi varata joko globaalia tai lokaalia muistia. Lokaali muisti on korkeintaan 64 kiloa (?) Lisäksi muistista voidaan varata korkeintaan 8000 "palasta"; jos tarvitaan enemmän, pitää varata iso "möykky", josta sitten itse jaetaan pienempiä palasia. C:n normaalit muistinhallintafunktiot malloc, free jne. käsittelevät Windowsissa lokaalia muistia pienessä muistimallissa (small) ja globaalia suuressa muistimallissa (large). Lokaali muisti häviää ohjelman poistuessa muistista. Esimerkiksi leikekirjaan tietojen on kuitenkin jäätävä vaikka ohjelman suoritus loppuisikin. Siksi leikekirjan käyttöön muistia on varattava globaalista keosta.

	

Välttääkseen muistin pirstoutumista Windows haluaa mielellään muistinvaraukset tehtäväksi siten, että systeemillä on oikeus järjestellä muistia uudelleen. Tämä johtaa siihen, että muistinhallintaan tarvitaan useita funktiota: muistin varaus (Alloc), muistin lukitseminen käytön ajaksi paikalleen (Lock), muistin lukituksen vapauttaminen (UnLock) ja muistin vapautus (Free).

Kustakin funktiosta on sekä Global että Local -versio.

Käytännössä Alloc-funktiot palauttavat kahvan muistialueeseen. Sen avulla ei siis voida kirjoittaa mihinkään. Lock -toiminto antaa sitten fyysisen muistiosoitteen (32 bittiä Global-versiossa), jonka avulla varsinainen muistin käsittely suoritetaan.

Kun käsittely on suoritettu loppuun, pitää muistilohko päästää jälleen kellumaan Unlock -funktiolla. Tällöin Lock -funktiolla saatu muistiosoite ei tietenkään häviä mihinkään, mutta Windows saattaa siirtää muistilohkon eri paikkaan ja em. muistiosoitteeseen saattaa siirtyä vallan muuta tietoa. Siis ohjelmoija on itse vastuussa siitä, ettei hän käytä muistiosoitetta Unlock -funktion kutsumisen jälkeen.

4.5.2 Leikekirjaan talletus (WINLASKI\TABLASK\MENU3\LASKURM3.C, ALI\CLIPBOARD.C)

Leikekirjan käsittelystä on kirjoitettu käyttöä helpottava aliohjelmakirjasto CLIPBOARD.C. Seuraavassa esimerkkejä sen käytöstä.

Laskuriohjelman näyttöjen arvot voitaisiin tallettaa leikekirjaan seuraavasti:

	static int laskurit_leikekirjaan(HWND hWnd,int *laskurit)
	{
	  const char *pText = laskurit_tekstiksi(laskurit);
	  return TextToClipboard(hWnd,pText);
	}
	#define TextToClipboard(hWnd,s) \
	  AddToClipboard(hWnd,CF_TEXT,s,strlen(s)+1,TRUE)
	
	/***************************************************************************/
	int AddToClipboard(HWND hWnd,UINT fmt,const char *Text,int len,BOOL empty)
	/* Lisätään leikekirjaan data Text, josta otetaan len tavua.
	** Jos hWnd == NULL, oletetaan leikekirjan olevan avoinna.
	** Jos empty==TRUE, niin leikekirja tyhjennetään ennen lisäystä.
	** Palautetaan != 0 jos lisäys ei onnistu.
	*/
	{
	  HANDLE hData;
	  char FAR *lpData;
	
	  if ( !(hData = GlobalAlloc(GMEM_MOVEABLE, len)) ) return -1;
	  if ( (lpData = GlobalLock(hData)) == NULL || (hWnd && !OpenClipboard(hWnd)) ) {
	    GlobalFree(hData);
	    return -2;
	  }
	
	  _fmemcpy(lpData, Text,len);
	  GlobalUnlock(hData);
	
	  if ( empty ) EmptyClipboard();
	  SetClipboardData(fmt, hData);
	  if ( hWnd ) CloseClipboard();
	
	  return 0;
	}

Leikekirjalle tieto viedään globaalin muistilohkon kahvana. Siis ensiksi pitää moinen lohko luoda ja siirtää tieto sinne. Kun tieto on saatu lohkoon, pitää lohko päästää kellumaan. Leikekirja avataan, tyhjennetään (jollei haluta tallettaa useita eri formaatteja samasta asiasta) ja sille välitetään edellä täytetyn muistilohkon kahva. Samalla kerrotaan mitä tyyppiä muistilohkossa oleva tieto on.

Muistilohkon tuhoamisesta vastaa tämän jälkeen leikekirja (esim. EmptyClipboard -kutsun yhteydessä). Leikekirjassa voi olla sama tieto useita kertoja eri formaatissa. Esimerkiksi taulukkolaskenta voi tallettaa leikekirjaan solujen sisällön tekstinä ja toisaalta myös bittikuvana. Usein myös talletetaan leikekirjaan tieto siitä, miten voidaan muodostaa DDE-linkki tietoon.

4.5.3 Leikekirjasta lukeminen

Tiedon ottaminen leikekirjasta tapahtuu tietysti käänteisesti:

	int leikekirja_laskureihin(HWND hWnd,int *laskurit)
	{
	  const char *pText = TextClipboard(hWnd);
	  if ( !pText ) return -1;
	
	  teksti_laskureiksi(hWnd,pText,laskurit);
	  return 0;
	}
	#define CopyFromClipboard(hWnd,fmt) CopyFromClipboardN(hWnd,fmt,NULL)
	#define TextClipboard(hWnd)         CopyFromClipboard(hWnd,CF_TEXT)
	
	/***************************************************************************/
	char *DataFromClipboardN(HWND hWnd,UINT fmt,UINT *n,char *buf, UINT bufm)
	/* Otetaan tieto leikekirjasta.  Jos hWnd=NULL, oletetaan leikekirjan
	** jo olevan auki, eikä sitä avata eikä suljeta.
	** Jos muoto on tekstiformaattia, on koko jonon pituus.
	*/
	{
	  HANDLE hClipData;
	  char FAR *lpClipData;
	  DWORD gsize,size = bufm-1;
	  if ( n ) *n = 0;
	  buf[0]=0;
	
	  if ( !fmt ) return NULL;
	  if ( hWnd && !OpenClipboard(hWnd) ) return NULL;
	  if ( !IsClipboardFormatAvailable(fmt) ) {
	    if ( hWnd ) CloseClipboard();
	    return NULL;
	  }
	
	  /* get data from the clipboard */
	  if (!(hClipData = GetClipboardData(fmt)))  {
	    if ( hWnd ) CloseClipboard();
	    return NULL;
	  }
	
	  gsize = GlobalSize(hClipData);
	  if ( (lpClipData = GlobalLock(hClipData)) == NULL )  {
	     if ( hWnd ) CloseClipboard();
	     return NULL;
	  }
	
	  if ( gsize < size ) size=gsize;
	  _fmemcpy(buf, lpClipData,(UINT)size);
	  buf[(UINT)size] = 0;
	
	  GlobalUnlock(hClipData);
	  if ( hWnd ) CloseClipboard();
	
	  if ( TextFmt(fmt) ) size = strlen(buf);
	
	  if ( n ) *n = (UINT)size;
	
	  return buf;
	}
	
	
	/***************************************************************************/
	const char *CopyFromClipboardN(HWND hWnd,UINT fmt,UINT *n)
	{
	  static char pText[MAX_COPY];
	  return DataFromClipboardN(hWnd,fmt,n,pText,sizeof(pText));
	}

Leikekirja palauttaa globaalin muistilohkon kahvan, mikäli siellä on halutun muotoista dataa. Lohko lukitaan, tieto siirretään omaan käyttöön ja leikekirja suljetaan. Muistilohkon vapauttaminen jätetään edelleen leikekirjalle, mutta lohko päästetään tietysti kellumaan.

4.6 Bittikartat ja ikonit

Bittikartat ja ikonit ovat tapa elävöittää ohjelmaa. Ikonia käytetään yleensä ohjelman merkkinä kun ohjelma kutistetaan. Bittikarttoja voidaan laittaa selventämään joitakin toimintoja, niitä voidaan liikutella pitkin näyttöä tai niistä voidaan tehdä ohjelmalle taustakuva. Ikoneita ja bittikarttoja voidaan tehdä resurssityökalujen avulla. Bittikarttoja voidaan luoda myös muilla ohjelmilla, esimerkiksi PaintBrushilla, joka tulee Windowsin mukana.

Edellähän jo näytettiin, miten bittikartta laitetaan mukaan menuihin.

4.6.1 Ikonit

Tavalliselle ohjelmalle, jolle luodaan oma ikkunaluokka, ikonin käyttö on helppoa - ikkunaluokan tietojen alustuksessa ilmoitetaan .RC -tiedostossa oleva ikonin nimi:

	      wc.hIcon         = LoadIcon(hInstance,"Ikoni");

Mikäli .RC -tiedostossa on tehty dialogeja, voidaan dialogeihin laittaa näitä ikoneita. Huomattakoon, että tällöin ikonin nimi annetaan Caption -kohtaan, ei ID-kohtaan.

Laskuriohjelmassa ei kuitenkaan ollut pääikkunaa, vaan dialogi oli ohjelman pääikkunana. Tällöin ohjelmalle ei voida ilmoittaa ikonia. SetClassWord -funktiolla voidaan kyllä muuttaa dialogi-luokan ikonia, mutta tällöin muuttuu kaikkien dialogien ikoni (myös muiden ohjelmien), joten sitä ei sopine tehdä.

Jos dialogille ei tehdä muita muutoksia, pitää ikoni piirtää itse kun laskuriohjelma kutistetaan ikoniksi, Tämä voidaan tehdä vaikkapa seuraavasti:

Alustuksessa:

	...
	  ikoni          = LoadIcon(hInstance,"IKONI");
	...

Ikkunafunktiossa:

	  case WM_PAINT:
	    if ( !BeginPaint(hWnd,&ps) ) break;
	    /* Ikoni joudutaan piirtämään itse, koska dialogi luokalla ei ikonia */
	    if ( IsIconic(hWnd) ) PiirraIkoni(ps.hdc);
	    EndPaint(hWnd,&ps);
	    return TRUE;

Itse piirtävä aliohjelma on sitten vaikkapa seuraava:

	/***************************************************************************/
	void PiirraIkoni(HDC hDC)
	/* Piirretään ikoni ikonialueelle maalaten ensin ikonin tausta harmaaksi   */
	{
	  SelectObject(hDC,GetStockObject(GRAY_BRUSH));
	  Rectangle(hDC,0,0,40,40);
	  DrawIcon(hDC,2,2,ikoni);
	}

Vastaavasti voitaisiin piirtää ikoni myös itse pääikkunan tai dialogin alueelle.

4.6.2 Bittikartta taustakuvaksi(ALI\TAUSTA.C)

Autolaskurin taustakuvaksi voitaisiin piirtää jokin sopiva kuva jollakin piirto-ohjelmalla, tai leikata kuva jostakin muusta kuvasta. Kun bittikarttaa käytetään, pitää bittikartalle varata muistitila ja ladata bittikartta tähän tilaan:

	static HBITMAP hBitBack;
	...
	  hBitBack = LoadBitmap(hInstance,"TAUSTA");

Latauksen tuloksena saadaan siis kahva bittikarttaan. Ohjelman lopuksi pitää bittikartat poistaa kutsulla:

	  DeleteObject(hBitBack);

Bittikarttaa tuhottaessa pitää olla varma, ettei se ole minkään laiteyhteyden objektina sillä hetkellä!

Taustakuva piirretään kun saadaan WM_ERASEBKGND -viesti:

	  case WM_ERASEBKGND:
	    /* Ison bittikartan venyttäminen kestää kauan ja ant. myös huon. tul.*/
	    /* TaytaBittikarttalla(hWnd,(HDC)wParam,hBitBack); */ 
	    PiirraBittikartta((HDC)wParam,hBitBack,0,0);
	    return TRUE;

Bittikartta normaalimuodossa saadaan tulostettua aliohjelmalla:

	/***************************************************************************/
	int PiirraBittikartta(HDC hDC,HBITMAP hBitmap,int x,int y)
	/* Piirretään bittikartta alkaen paikasta x,y                              */
	{
	  HDC  hDCBitmap = CreateCompatibleDC(hDC);
	  HGDIOBJ hOld   = SelectObject(hDCBitmap,hBitmap);
	  BITMAP bm;
	
	  GetObject(hBitmap,sizeof(bm),&bm);
	  BitBlt(hDC,x,y,bm.bmWidth,bm.bmHeight,hDCBitmap,0,0,SRCCOPY);
	  SelectObject(hDCBitmap,hOld);
	  DeleteDC(hDCBitmap);
	  return 1;
	}

Bittikartan tulostaminen halutulle laiteyhteydellä aloitetaan tekemällä bittikartalle vastaava laiteyhteys muistiin. Tähän laiteyhteyteen valitaan sitten käytettävä bittikartta. Mikäli nyt piirrettäisiin muistilaiteyhteyteen, menisi tulos bittikarttaan. Siis tätä voitaisiin käyttää bittikarttojen luomiseen.

Laiteyhteyksien välillä bittikartta voidaan siirtää BitBlt -funktiolla. Ensin on kuitenkin otettu selville alkuperäisen bittikartan tiedot, erityisesti leveys ja korkeus.

Aliohjelma käy tietysti bittikartan tulostamiseksi mihin tahansa kohtaan, ei vain taustakuvaksi.

Jälleen on oltava tarkkana, että tuhottaessa muistiin tehtyä laiteyhteyttä, ei siellä ole vahingossa mitään bittikarttaa aktiivisena. Siis objektin vaihdossa on muistettava tallettaa vanha objekti (hOld) myöhempää palauttamista varten!

Taustan piirtoa helpottamaan on tehty aliohjelmakirjasto ALI\TAUSTA.C.

4.6.3 Bittikartan venytys (WINLASKI\TABLASK\MENU4\KIELI.C)

Autolaskurin dialogiin on oikeaan alalaitaan varattu ikkuna, johon on tarkoitus tulostaa käytössä olevan menun kieltä vastaava lippu. Aluksi kahvat lippuja kuvaaviin bittikarttoihin on ladattu kuten edellä ladattiin taustakuvan kahva.

Kun kieli halutaan vaihtaa, voidaan se valita menusta tai napauttamalla lipun kuvaa, joka on Button-tyyppinen, eikä staattinen kuten vasemmassa laidassa oleva ikoni. Siis lipusta saadaan WM_COMMAND -viesti. Viestiin vastataan vaihtamalla seuraavaan kieleen:

	/***************************************************************************/
	int AsetaKieli(HWND hWnd,int k)
	{
	  kieli = k;
	  if ( kieli >= Kielia ) kieli = 0;
	  SetMenu(hWnd,Languages[kieli].hMenu);
	  InvalidateRect(hWnd,NULL,FALSE);
	  return 0;
	}

Kun ikkuna pyydetään piirtämään uudelleen, tulee dialogi piirretyksi uudelleen. Koska lipun tyypiksi on ilmoitettu User Draw, tyhjentää systeemi lipun alta alueen ja sitten antaa ikkunafunktiolla viestin WM_DRAWITEM:

	static EVENT WM_drawitem_ID_LANGUAGE(tMSGParam *msg)
	{
	  DRAWITEMSTRUCT *dis = (DRAWITEMSTRUCT *)msg->lParam;
	  HWND hwnd = GetDlgItem(msg->hWnd,ID_LANGUAGE);
	  TaytaBittikartalla(hwnd,dis->hDC,Languages[kieli].hBit);
	  return 0;
	}

Koska koko lipulle varattu alue halutaan lipun kokoiseksi, pitää lippua näytöstä riippuen joko pienentää tai suurentaa (dialogin koko saattaa riippua näytön resoluutiosta, joten sen pikselimäärä ei aina ole sama). Siis piirtofunktiolle pitää viedä myös lippuikkunan kahva, jotta ikkunan dimensiot saadaan selville.

	/***************************************************************************/
	int TaytaBittikartalla(HWND hWnd,HDC hDC,HBITMAP hBitmap)
	/* Täytetään koko alue bittikartalla                                       */
	{
	  HDC  hDCBitmap = CreateCompatibleDC(hDC);
	  HGDIOBJ hOld  = SelectObject(hDCBitmap,hBitmap);
	  RECT rc;
	  BITMAP bm;
	
	  GetObject(hBitmap,sizeof(bm),&bm);
	  GetClientRect(hWnd,&rc);
	  StretchBlt(hDC,0,0,rc.right,rc.bottom,hDCBitmap,
	                 0,0,bm.bmWidth,bm.bmHeight,SRCCOPY);
	  SelectObject(hDCBitmap,hOld);
	  DeleteDC(hDCBitmap);
	  return 0;
	}

StretchBlt -funktio muotoilee bittikartan mahtumaan haluttuun alueeseen. Joko pikseleitä joudutaan jättämään välistä pois tai sitten joudutaan lisäämään uusia. Tulos saattaa siis hieman kärsiä ja toimitus on hitaampi kuin pelkkä BitBlt. StretchBlt -funktiolla voidaan myös peilata bittikarttoja antamalla joko leveyteen tai korkeuteen negatiivisia arvoja. Tällöin tulee myös muistaa muuttaa alkupisteen koordinaatteja (ks. AUTOT.C).

4.6.4 Liikkuvat bittikuvat (ALI\AUTOTW.C)

Liikkuva bittikartta tehdään periaatteessa seuraavasti:

1. luetaan bittikartan kokoinen alue valitusta paikasta taustakuvaa toiseen bittikarttaan

2. piirretään bittikartta

3. jonkin ajan kuluttua talletetaan tausta

4. lasketaan bittikartan uudet koordinaatit

5. jatketaan kohdasta 1.

Windowsissa tämä tapa tuottaa kuitenkin huomattavasti ongelmia. Tausta saattaa muuttua ja luettu tausta on väärä. Näin tapahtuu esimerkiksi jonkin alla olevan ikkunan muuttaessa arvoaan, lipun vaihtuessa lippunappulassa, ikkunan koon muuttuessa jne.

Siis koko toimitus onkin parempi tehdä muodostamalla kustakin liikkuvasta bittikuvasta oma Windowsin ikkuna. Nyt liikuttelemalle näitä ikkunoita Windows hoitaa kaikki taustaan liittyvät ongelmat sekä lisäksi leikkauksen muiden ikkunoiden kanssa.

Loppu onkin varsin helppoa, mikäli liikuteltava kuva on suorakaiteen muotoinen ja täyttää koko ikkunansa. Jos kuitenkin liikuteltavan kuvan halutaan olevan epämääräisen muotoinen, pitää käyttää monimutkaisempaa menetelmää kuin pelkkä bittikuvan piirtäminen.

Samoin tehtävä vaikeutuu jos kuvan halutaan olevan erilainen eri suuntiin liikuttaessa. Tällöin tarvitaan tietysti kaikkiin liikesuuntiin erilaiset bittikartat. Yksinkertaisessa tapauksessa nämä voidaan muodostaa peilaamalla alkuperäistä bittikarttaa.

Läpinäkyvä kuva muodostetaan seuraavasti:

Piirrettävälle alueelle tehdään piirrettävän kuvan muotoinen musta aukko. Tähän aukkoon laitetaan OR-operaatiolla haluttu värillinen bittikuva, josta ylimääräiset reunat on muutettu mustaksi.

Tarvitaan siis kaksi bittikuvaa. AND-maski, joka tekee mustan aukon ja OR-maski joka piirtää varsinaisen kuvan (joskus käytetään myös XOR-maskia, jolla voidaan lisäksi vaihtaa taustan väriä sopivista kohdista). Nämä maskit voidaan tietysti muodostaa alkuperäisestä kuvasta, jos oletetaan jonkin tietyn värin (esim. valkoisen) tarkoittavan läpinäkymistä.

	

Seuraavassa auton alustamisen hieman yksinkertaistettu versio:

	typedef struct {  /* Yhden auton bittikartojen kahvat                      */
	  HBITMAP hAnd;      /* AND -maski (mustavalkoinen)                        */
	  HBITMAP hOr;       /* OR  -maski (mahd. värillinen)                      */
	} MaskiTyyppi;
	
	/***************************************************************************/
	/* .INI -merkityt kohdat voi alustaa .INI-tiedostolla                      */
	typedef struct {  /* Auton ylläpitämiseksi tarvittavat tiedot:             */
	  char nimi[20];     /* Bittikuvan nimi.                              .INI */
	  int korkeus;       /* Auton yläreunan y-koordinaatti, 0 on pohj.    .INI */
	  int vauhti;        /* x-siirtymä kerrallaan                         .INI */
	  int aikavali;      /* Siirtymien aikaväli ms                        .INI */
	  int paikka;        /* Auton x-koordinaatti                          .INI */
	  int suunta;        /* +/- vauhti kulkusuunnan mukaan                     */
	  int xkoko;         /* Bittikartan x-koko (Selvitetään bittikartasta)     */
	  int ykoko;         /* Bittikartan y-koko                                 */
	  int xmin;          /* Auton liikkeen pienen x-koordinaatti               */
	  int xmax;          /* Vastaava suurin x-koordinaatti (ikkunan koko määr) */
	  int kopio;         /* Onko bittikartat kopioita jostakin muusta          */
	  MaskiTyyppi AutoO; /* Oikealle kulkevan auton bittimaskit                */
	  MaskiTyyppi AutoV; /* Vasemmalle kulkevan auton bittimaskit              */
	  MaskiTyyppi Nyt;   /* Kulkusuunnan mukaan joko AutoO tai AutoV           */
	  int alustettu;     /* Onko tietorakenne alustettu (0 = ei)               */
	  int nakyvissa;     /* Onko auto näkyvissä (nykyinen versio ei käytä)     */
	  int pysakoity;     /* Onko auto pysäköity vai ei (oletus ei).            */
	  HWND hwnd;         /* Autoa kuvaavan ikkunan kahva.                      */
	  HWND ohitus;       /* Kulkeeko auto päällä vai pohjalla             .INI */
	  HWND ParenthWnd;   /* Isäikkunan kahva.                                  */
	  HINSTANCE hInstance;/* Esiintymä, jossa auton ikkuna luotu               */
	  int kokokuva;      /* Auto täyttää koko ikkunansa,helppo siirtää    .INI */
	} AutoTyyppi;
	static int alusta_ja_lisaa_auto(HWND hWnd,AutoTyyppi *a)
	{
	  RECT rc;
	  AutoTyyppi *pa;
	  if ( !a ) return 1;
	  if ( a->alustettu ) return 0;
	
	  a->ParenthWnd = hWnd;
	  if ( !hWnd ) hWnd = GetDesktopWindow();
	  GetClientRect(hWnd,&rc);
	
	  if ( luo_bittikartat(hWnd,a) ) return 1;
	
	  a->Nyt = a->AutoO;
	
	  if ( !a->vauhti )   a->vauhti = 10;
	  if ( !a->aikavali ) a->aikavali = 100;
	  if ( !a->xmax )     a->xmax = rc.right - a->xkoko;
	  if ( !a->korkeus )  a->korkeus = rc.bottom - a->ykoko;
	  a->suunta = a->vauhti;
	  a->paikka = a->xmax/2;
	
	  a->hwnd = CreateWindow(WAutoWClass,a->nimi,
	                        WS_CHILD | WS_VISIBLE,
	                        a->paikka,a->korkeus,a->xkoko,a->ykoko,
	                        hWnd,NULL,a->hInstance,(void *)a);
	
	  if ( a->hwnd == NULL ) { putsaa_auto(a); return 1; }
	
	  a->nakyvissa = 0;
	  a->alustettu = 1;
	  if ( ( pa = lisaa_listaan(a) ) == NULL ) return 1;
	  SetWindowLong(a->hwnd,0,(LONG)pa);
	  SetTimer(a->hwnd,1,a->aikavali,NULL);
	
	  return 0;
	}
	static int luo_bittikartat(HWND hWnd,AutoTyyppi *a)
	{
	  HDC hDC,hDCBit1,hDCBit2;
	  HGDIOBJ hOld1,hOld2; /* Ja niiden vanhat "työkalut"                      */
	  BITMAP bm;
	  /* Oikealle ja vasemmalle liikkuvat värilliset autot                     */
	  a->kopio = 0;
	  a->AutoO.hOr  = LoadBitmap(a->hInstance,a->nimi);
	  if ( !a->AutoO.hOr ) return 1;
	
	  hDC     = GetDC(hWnd);
	  hDCBit1 = CreateCompatibleDC(hDC); /* Apu muisti-DC:t piirtelyyn     */
	  hDCBit2 = CreateCompatibleDC(hDC);
	
	  GetObject(a->AutoO.hOr,sizeof(bm),&bm);
	  a->xkoko    = bm.bmWidth; a->ykoko = bm.bmHeight;
	
	  a->AutoV.hOr  = CreateCompatibleBitmap(hDC,a->xkoko,a->ykoko);
	
	  /* Mustavalkoiset maskiautot (bitmap on mustavalk. jos CCB muisti DC:stä */
	  /* jossa ei ole ennestään bitmappia!)                                    */
	  a->AutoO.hAnd = CreateCompatibleBitmap(hDCBit1,a->xkoko,a->ykoko);
	  a->AutoV.hAnd = CreateCompatibleBitmap(hDCBit2,a->xkoko,a->ykoko);
	
	  hOld1         = SelectObject(hDCBit1,a->AutoO.hOr);
	  hOld2         = SelectObject(hDCBit2,a->AutoO.hAnd);
	
	  /* Maskiauto, jossa vain mustaa autossa ja valkoista reunoilla           */
	  BitBlt(hDCBit2,0,0,a->xkoko,a->ykoko,hDCBit1,0,0,SRCCOPY);
	  /* Alkuperäisestä autosta reunat mustaksi maskiauton avulla              */
	  BitBlt(hDCBit1,0,0,a->xkoko,a->ykoko,hDCBit2,0,0,SRCINVERT);
	
	  /* Vasemmalle liikkuva auto tehdään oikealle liikkuvasta autosta         */
	  SelectObject(hDCBit2,a->AutoV.hOr);
	  /* Tehdään auton peilikuva bittikarttaan AutoV.hOr                       */
	  StretchBlt(hDCBit2,0,0,a->xkoko,a->ykoko,
	             hDCBit1,a->xkoko-1,0,-a->xkoko,a->ykoko,SRCCOPY);
	
	  /* Mustavalkoinen vasemmalle menevä maskiauto                            */
	  SelectObject(hDCBit1,a->AutoV.hAnd);
	  SelectObject(hDCBit2,a->AutoO.hAnd);
	  /* Tehdään mustavalkoisen auton peilikuva bittikarttaan AutoV.hAnd       */
	  StretchBlt(hDCBit1,0,0,a->xkoko,a->ykoko,
	             hDCBit2,a->xkoko-1,0,-a->xkoko,a->ykoko,SRCCOPY);
	
	  /* Poistetaan turhat DC:t                                                */
	  SelectObject(hDCBit2,hOld2);
	  SelectObject(hDCBit1,hOld1);
	  DeleteDC(hDCBit2);
	  DeleteDC(hDCBit1);
	  ReleaseDC(hWnd,hDC);
	  return 0;
	}

Aluksi ladataan alkuperäinen auton kuva resursseista. Tästä tehdään sitten tarvittavat 4 maskia: vasemmalle ja oikealle AND ja OR-maskit.

Mustavalkoinen bittikartta saadaan tekemällä "yhteensopiva" bittikartta muistiDC:stä. Värillisen uuden bittikartan tekemiseksi pitää se tehdä oikeasta fyysisestä laiteyhteydestä. Kun bittikartta on luotu ja yhdistetty jonkin muistilaiteyhteyteen, voidaan sinne jälleen piirtää kaikilla Windowsin funktiolla.

Kun bittikartat on saatu valmiiksi, luodaan autoa varten uusi ikkuna, jonka ikkunafunktio on sama kaikille autoille ja taustan harjana on läpinäkyvä harja. Tähän ikkunaan pistetään timeri käyntiin siirtojaksojen mukaisesti.

	... auton ikkunafunktiossa
	
	    case WM_TIMER:
	      if ( !(a = EtsiAuto(hWnd)) ) break;
	      if ( a->pysakoity ) break;
	      if ( IsIconic(a->ParenthWnd) ) return NULL;
	      siirra_auto(a);
	      return NULL;
	...

Koska kaikille autoille on sama ikkunafunktio, täytyy ensin etsiä autoista se, jolle viesti tuli. Tämä tunnistetaan ikkunan kahvan perusteella.

Siirtämisessä lasketaan ikkunan uusi paikka ja mikäli auton tausta ei näy läpi, voidaan pyytää pelkkä ikkunan siirto. Mikäli autosta pitää näkyä lävitse, pitää auton kuva "pilata", jotta Windows varmasti piirtää uuden autonkuvan tyhjän taustakuvan päälle. Siirto voitaisiin tehdä myös MoveWindow -funktiolla, mutta tällöin auton "syvyyttä" ei voitaisi muuttaa.

	static int siirra_auto(AutoTyyppi *a)
	{
	  int suunta_vaihtui = 0;
	  if ( !a->alustettu || a->pysakoity ) return 1;
	
	  a->paikka += a->suunta;
	  if ( a->paikka > a->xmax ) {                 /* Valitaan auto vasemmalle */
	    a->paikka = a->xmax; a->suunta = -a->vauhti;  a->Nyt = a->AutoV;
	    suunta_vaihtui = 1;
	  }
	  if ( a->paikka < a->xmin ) {                 /* Valitaan auto oikealle   */
	    a->paikka = a->xmin; a->suunta = a->vauhti;  a->Nyt = a->AutoO;
	    suunta_vaihtui = 1;
	  }
	
	   if ( !a->kokokuva || suunta_vaihtui ) InvalidateRect(a->hwnd,NULL,FALSE);
	   SetWindowPos(a->hwnd,a->ohitus,a->paikka,a->korkeus,a->xkoko,a->ykoko,
	     SWP_NOSIZE  );
	  return 0;
	}

Malliohjelmassa alustettavien autojen tiedot luetaan .INI -tiedostosta:

	[Laskuri]
	Laskuri=0 40
	[Autot]
	Auto0=HAUTO,0,0,10,200
	Auto1=KAUTO,0,100,20,300,10,1
	Auto2=HAUTO,1,70,30,100
	Auto3=KAUTO,0,0,10,300,100,1
	

Tiedosto luetaan alusta_autot -aliohjelmassa:

	...
	for (lkm=0;;lkm++) {
	    sprintf(pname,"Auto%d",lkm);         /* Tehdään Auto?                  */
	    if ( GetPrivateProfileString("AUTOT",pname,"",s,sizeof(s),ininame) == 0 )
	      break;                             /* Jollei autoa saatu, lopetetaan */
	    if ( jono_autoksi(hInst,s,&a) ) continue;
	    alusta_ja_lisaa_auto(hWnd,&a);
	  }
	...
	static int jono_autoksi(HINSTANCE hInst,char *s,AutoTyyppi *a)
	{
	  char *p;  int ohitus = 0;
	  a->alustettu = 0;  a->nimi[0] = 0;
	  a->kokokuva = a->xmin = a->xmax = a->korkeus = a->vauhti = a->aikavali = 0;
	  a->paikka = 0;     a->ohitus = HWND_TOP;  a->hInstance = hInst;
	
	  if ( (p=strtok(s,",")) == NULL ) return 1;
	  strncpy(a->nimi,p,sizeof(a->nimi)); a->nimi[sizeof(a->nimi)-1] = 0;
	  if ( (p=strtok(NULL,"\0")) == NULL ) return 0;
	
	  sscanf(p,"%d,%d,%d,%d,%d,%d",&a->kokokuva,&a->korkeus,
	           &a->vauhti,&a->aikavali,&a->paikka,&ohitus);
	  if ( ohitus ) a->ohitus = HWND_BOTTOM;
	
	  return 0;
	}

Näin kaiken kaikkiaan liikkuvista kuvista on saatu aliohjelmakirjasto, jonka käyttämiseksi käyttäjän on vain kutsuttava pääikkunan luomisen jälkeen aliohjelmaa alusta_autot ja lopuksi tuhoa_autot (tosin autot voidaan tuhota automaattisesti, koska ne ovat ikkunoita, joista kukin saa WM_DESTROY -viestin ennen pääikkunan tuhoutumista), kirjoitettava .INI -tiedosto ja tehtävä tietysti resursseihin liikkuvien otusten bittikartat.

4.7 Dialogi pääikkunaksi (WINLASKI\TABLASK\MENU4\LASKURM4.C)

Edellä dialogia käytettiin pääikkunana. Tästä oli mm. se haitta, että koska luokan rekisteröintiä ei päästy itse tekemään, jouduttaisiin ikoni piirtämään itse.

Dialogin suunnittelun yhteydessä voidaan dialogille kuitenkin antaa luokka. Tällöin tämä luokka pitää rekisteröidä. Luokan rekisteröinti tapahtuu kuten tavallisenkin ikkunan. Koska kyseessä on kuitenkin dialogi, pitää ikkunan ylimääräisten tavujen määrä muistaa laittaa oikein:

	wc.cbWndExtra     = DLGWINDOWEXTRA + OMAT_TAVUT;

Luokan ikkunafunktioksi voidaan laittaa jokin oma funktio tai DefDlgProc. Itse dialogille voidaan rakentaa sitten normaali dialogin funktio. Tiedoston ALI\TABHAND.H makroilla koko "ilkeys" on käyttäjän kannalta piilotettu siihen, että kumpi seuraavista makroista valitaan "pääohjelmaksi":

	TblClassDlgMAIN("LASKURM4","ikoni","PIKA","LASKURI",MsgTbl);       
	... vaiko ..
	TblDlgMAIN("PIKA","LASKURI",MsgTbl); 

Jos käytetään Class -versiota, pitää .RC -tiedostossa dialogissa "LASKURI" olla mainittuna luokka "LASKURM4".

	LASKURI DIALOG 16, 25, 179, 88
	STYLE WS_OVERLAPPED | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX
	CLASS "LASKURM4"
	CAPTION "LASKURM4"
	MENU KIELI_0
	{
	 PUSHBUTTON "&Henkilöautoja", HA, 33, 10, 50, 20

Jollei dialogissa ole luokkaa, ei ikonia kuitenkaan piirretä. Jos vastaavasti dialogissa on luokka, mutta sitä ei ole rekisteröity, dialogi ei näy ruudussa!

Automaattisen ikonin lisäksi dialogiluokan rekisteröimisellä saavutetaan mm. se etu, että luokan ominaisuudet ovat tarkemmin kontrolloitavissa. Useissa sovelluksissa ikkunan pitäisi pystyä tallettamaan ikkunan kahvaan kuuluvaa tietoa. Rekisteröimällä ikkunan luokka itse, voidaan ikkunalle varata tavuja, joissa tieto talletetaan.


previous next Title Contents