Um zu verstehen, warum man in C einen Standard eingeführt hat bedarf es ein wenig mehr der Geschichte zu dieser Sprache.C C++ C/C++ Programmieren C Kurs Programmierung Systemprogrammierung Win32 api Win-Api Windows Systemprogrammierung C Programmierung Win32 api Win-Api Windows Systemprogrammierung Kapitel 8 : Datei-Ein/Ausgabe

In diesem Kapitel werden Sie ein recht umfangreiches Programm schreiben, einen Texteditor. Dabei wird das Kapitel weit über das Thema Datei Ein/Ausgabe hinausgehen. Sie sollten also ein wenig mehr Zeit für dieses Kapitel investieren. Folgende Funktionalität soll der Editor hierbei aufweisen:

In diesem Kapitel werden Sie auch Gebrauch von einigen Dialogfeldern machen, die Ihnen Windows in der DLL-Datei COMMDLG.DLL zur Verfügung stellt. Dabei ist es nötig die Headerdatei commctrl.h mit einzubinden. Um Ihnen schon ein wenig Appetit zu machen, was in diesem Kapitel folgt, hierzu ein Screenshot der Anwendung, die Sie erstellen werden:

Der fertige Texteditor in diesem Kapitel

Der fertige Texteditor in diesem Kapitel

8.1. Quellcode            zurück  zum Inhaltsverzeichnis

Um gleich beim Thema zu bleiben, folgen zuerst der Quellcode, die Headerdatei und die Resourcen-Sktiptedatei der Anwendung. Ich empfehle Ihnen erst mal ein neues Projekt zu erstellen, dass Listing übersetzen und anschließend zur Ausführung zu bringen. Somit können Sie bei der Analyse des Quellcodes im Anschluss daran Herumexperimentieren.

Ressource-Skriptdatei

#include "resource.h"

/////////////////////////////////////////////////////////////////
//
// Menü
//

IDR_MENU MENU DISCARDABLE
BEGIN
    POPUP "Datei"
    BEGIN
        MENUITEM "Neu",                          ID_FILE_NEW
        MENUITEM "Öffnen...",                    ID_FILE_OPEN
        MENUITEM "Speichern unter...",           ID_FILE_SAVEAS
        MENUITEM SEPARATOR
        MENUITEM "Beenden",                      ID_FILE_EXIT
    END
    POPUP "Eigenschaften"
    BEGIN
        MENUITEM "Schriftart ändern",            ID_FORMAT_FONT
        MENUITEM "Schriftfarbe ändern",          ID_FONT_COLOR
        MENUITEM "Hintergrundfarbe ändern",      ID_BK_COLOR
    END
    POPUP "Über ..."
    BEGIN
        MENUITEM "Version",                      ID_VERSION
    END
END

/////////////////////////////////////////////////////////////////
//
//Icon
//
ID_ICON              ICON        "Edit.ico"

Die Headerdatei resource.h

#define IDR_MENU                        100

#define ID_FILE_EXIT                    40001
#define ID_FILE_OPEN                    40002
#define ID_FILE_SAVEAS                  40003
#define ID_FILE_NEW                     40004
#define ID_FORMAT_FONT                  40005
#define ID_FONT_COLOR                   40006
#define ID_BK_COLOR                     40007
#define ID_VERSION                      40008
#define ID_ICON                         40009

#define IDC_MAIN_EDIT                   101
#define IDC_MAIN_TOOL                   102
#define IDC_MAIN_STATUS                 103

Der komplette Quellcode

#include <windows.h>
#include <commctrl.h>
#include "resource.h"

#define WEISS   RGB(255,255,255)
#define SCHWARZ RGB(0,0,0)

const char g_szClassName[] = "Pronix - Texteditor";

static LOGFONT logfont ;
static HFONT hFont ;

HWND hEdit;
HWND hwnd;

COLORREF backcolor    = WEISS,
         textcolor    = SCHWARZ,
         currentcolor = SCHWARZ;
CHOOSECOLOR choosecolor;
HBRUSH hbrush;
static COLORREF customcolors[16];
static LOGBRUSH         logbrush;

/* Farben für das Edit-Fenster initialisieren */
void SetupChooseColor(CHOOSECOLOR *cc,COLORREF *customclrs,
                      HWND hwnd, COLORREF init)
{
   int ca;
   for(ca = 0; ca < 16; ca++)
      *(customclrs+ca) = SCHWARZ;

   cc->lStructSize = sizeof(CHOOSECOLOR);
   cc->hwndOwner = hwnd;
   cc->rgbResult = init;
   cc->lpCustColors = customclrs;
   cc->Flags = CC_ANYCOLOR|CC_FULLOPEN|CC_RGBINIT;
}

/* Schriftarten (Fonts) auswählen */
BOOL FontChooseFont (HWND hwnd)
{
     CHOOSEFONT cf ;

     ZeroMemory(&cf, sizeof(cf));

     cf.lStructSize    = sizeof (CHOOSEFONT) ;
     cf.hwndOwner      = hwnd ;
     cf.lpLogFont      = &logfont ;
     cf.Flags          = CF_INITTOLOGFONTSTRUCT |
                         CF_SCREENFONTS | CF_EFFECTS ;
     //Dialogbox:Schriftart auswählen
     return ChooseFont (&cf) ;
}

/* Fonts initialisieren */
void FontInitialize (HWND hwndEdit)
{
     GetObject (GetStockObject (SYSTEM_FONT), sizeof (LOGFONT),
                (PTSTR) &logfont) ;

     hFont = CreateFontIndirect (&logfont) ;
     SendMessage (hwndEdit, WM_SETFONT, (WPARAM) hFont, 0) ;
}

/* Neue ausgeählte Schriftart verwenden */
void FontSetFont (HWND hwndEdit)
{
     HFONT hFontNew ;
     RECT  rect ;

     hFontNew = CreateFontIndirect (&logfont) ;
     SendMessage (hwndEdit, WM_SETFONT, (WPARAM) hFontNew, 0) ;
     DeleteObject (hFont) ;
     hFont = hFontNew ;
     GetClientRect (hwndEdit, &rect) ;
     InvalidateRect (hwndEdit, &rect, TRUE) ;
}

/* DATEI/ÖFFNEN... Text in das Edit-Feld laden */
BOOL LoadText(HWND hEdit, LPCTSTR pszFileName)
{
   HANDLE hFile;
   BOOL bSuccess = FALSE;

   hFile = CreateFile(pszFileName, GENERIC_READ,
                       FILE_SHARE_READ, NULL,
                       OPEN_EXISTING, 0, NULL);
   if(hFile != INVALID_HANDLE_VALUE)
      {
         DWORD dwFileSize;

         dwFileSize = GetFileSize(hFile, NULL);
         if(dwFileSize != 0xFFFFFFFF)
         {
            LPSTR pszFileText;

            pszFileText =(char *) GlobalAlloc(GPTR, dwFileSize + 1);
            if(pszFileText != NULL)
            {
               DWORD dwRead;
               if(ReadFile( hFile, pszFileText, dwFileSize,
                             &dwRead, NULL )   )
                  {
                     pszFileText[dwFileSize] = '\0';
                     if(SetWindowText(hEdit, pszFileText))
                           bSuccess = TRUE;
                  }
               GlobalFree(pszFileText);
            }
         }
         CloseHandle(hFile);
      }
   return bSuccess;
}

/* DATEI/SPEICHERN UNTER ... Denn Text abspeichern */
BOOL SaveText(HWND hEdit, LPCTSTR pszFileName)
{
   HANDLE hFile;
   BOOL bSuccess = FALSE;

   hFile = CreateFile(pszFileName, GENERIC_WRITE, 0, NULL,
                    CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
   if(hFile != INVALID_HANDLE_VALUE)
      {
         DWORD dwTextLength;

         dwTextLength = GetWindowTextLength(hEdit);
         if(dwTextLength > 0)
            {
               LPSTR pszText;
               DWORD dwBufferSize = dwTextLength + 1;
               pszText = (char *) GlobalAlloc(GPTR, dwBufferSize);
               if(pszText != NULL)
                 {
                  if(GetWindowText(hEdit, pszText, dwBufferSize))
                     {
                         DWORD dwWritten;
                         if(WriteFile(hFile,pszText,dwTextLength,
                             &dwWritten, NULL )   )
                                    bSuccess = TRUE;
                     }
                  GlobalFree(pszText);
                 }
            }
         CloseHandle(hFile);
      }
   return bSuccess;
}

/* Dialogbox Datei öffnen */
void OpenFileBox(HWND hwnd)
{
   OPENFILENAME ofn;
   char szFileName[MAX_PATH] = "";

   ZeroMemory(&ofn, sizeof(ofn));

   ofn.lStructSize = sizeof(ofn);
   ofn.hwndOwner = hwnd;
   ofn.lpstrFilter = "Text Datei (*.txt)\0*.txt\0"
                     "C Datei (*.c)\0*.c\0"
                     "Alle Dateien (*.*)\0*.*\0";
   ofn.lpstrFile = szFileName;
   ofn.nMaxFile = MAX_PATH;
   ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST |
               OFN_HIDEREADONLY;
   ofn.lpstrDefExt = "txt";

   if(GetOpenFileName(&ofn))
      {
         HWND hEdit = GetDlgItem(hwnd, IDC_MAIN_EDIT);
         if(LoadText(hEdit, szFileName))
            {
               SendDlgItemMessage(hwnd, IDC_MAIN_STATUS,
                           SB_SETTEXT, 0, (LPARAM)"Geöffnet...");
               SendDlgItemMessage(hwnd, IDC_MAIN_STATUS,
                              SB_SETTEXT, 1, (LPARAM)szFileName);
            }
      }
}

/* Dialogbox: Datei speichern */
void SaveFileBox(HWND hwnd)
{
   OPENFILENAME ofn;
   char szFileName[MAX_PATH] = "";

   ZeroMemory(&ofn, sizeof(ofn));

   ofn.lStructSize = sizeof(ofn);
   ofn.hwndOwner = hwnd;
   ofn.lpstrFilter = "Text Datei (*.txt)\0*.txt\0"
                     "C Datei (*.c)\0*.c\0"
                     "Alle Dateien (*.*)\0*.*\0";
   ofn.lpstrFile = szFileName;
   ofn.nMaxFile = MAX_PATH;
   ofn.lpstrDefExt = "txt";
   ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST |
               OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT;

   if(GetSaveFileName(&ofn))
      {
         HWND hEdit = GetDlgItem(hwnd, IDC_MAIN_EDIT);
         if(SaveText(hEdit, szFileName))
            {
               SendDlgItemMessage(hwnd, IDC_MAIN_STATUS,
                       SB_SETTEXT, 0, (LPARAM)"Gespeichert...");
               SendDlgItemMessage(hwnd, IDC_MAIN_STATUS,
                        SB_SETTEXT, 1, (LPARAM)szFileName);
            }
      }
}

/* Die WndProc() Prozedur */
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam,
                                              LPARAM lParam)
{
   switch(msg)
      {
         case WM_CREATE:
            {
               HWND hTool;
               TBBUTTON tbb[3];
               TBADDBITMAP tbab;

               HWND hStatus;

               int statwidths[] = {100, -1};
               //Textfeld erstellen
               hEdit=CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", "",
                                    WS_CHILD | WS_VISIBLE |
                                    WS_VSCROLL | WS_HSCROLL |
                                    ES_MULTILINE|ES_AUTOVSCROLL|
                                    ES_AUTOHSCROLL,
                                    0, 0, 100, 100, hwnd,
                                    (HMENU)IDC_MAIN_EDIT,
                                    GetModuleHandle(NULL), NULL);
               if(hEdit == NULL)
                  MessageBox(hwnd,
                       "Fehler beim Erstellen des Textfelds",
                       "Fehler", MB_OK | MB_ICONERROR);

               FontInitialize (hEdit);

               logbrush.lbHatch = 0;
               logbrush.lbStyle = BS_SOLID;
               logbrush.lbColor = WEISS;

               SetupChooseColor( &choosecolor,customcolors,
                                 hEdit,currentcolor);

               //Werkzeugleiste erstellen
               hTool = CreateWindowEx(0,TOOLBARCLASSNAME, NULL,
                                      WS_CHILD|WS_VISIBLE,
                                      0, 0, 0, 0,hwnd,
                                      (HMENU)IDC_MAIN_TOOL,
                                      GetModuleHandle(NULL),
                                      NULL);
               if(hTool == NULL)
                  MessageBox(hwnd,
                         "Kann keine Werkzeugleiste erstellen",
                         "Fehler", MB_OK | MB_ICONERROR);

               SendMessage( hTool, TB_BUTTONSTRUCTSIZE,
                            (WPARAM)sizeof(TBBUTTON), 0 );

               tbab.hInst = HINST_COMMCTRL;
               tbab.nID = IDB_STD_LARGE_COLOR;
               SendMessage(hTool,TB_ADDBITMAP,0,(LPARAM)&tbab);

               ZeroMemory(tbb, sizeof(tbb));
               tbb[0].iBitmap = STD_FILENEW;
               tbb[0].fsState = TBSTATE_ENABLED;
               tbb[0].fsStyle = TBSTYLE_BUTTON;
               tbb[0].idCommand = ID_FILE_NEW;

               tbb[1].iBitmap = STD_FILEOPEN;
               tbb[1].fsState = TBSTATE_ENABLED;
               tbb[1].fsStyle = TBSTYLE_BUTTON;
               tbb[1].idCommand = ID_FILE_OPEN;

               tbb[2].iBitmap = STD_FILESAVE;
               tbb[2].fsState = TBSTATE_ENABLED;
               tbb[2].fsStyle = TBSTYLE_BUTTON;
               tbb[2].idCommand = ID_FILE_SAVEAS;

               SendMessage( hTool, TB_ADDBUTTONS,
                            sizeof(tbb)/sizeof(TBBUTTON),
                            (LPARAM)&tbb );

               //Statusleiste erstellen
               hStatus = CreateWindowEx(0,STATUSCLASSNAME,NULL,
                                        WS_CHILD | WS_VISIBLE |
                                        SBARS_SIZEGRIP,
                                        0, 0, 0, 0, hwnd,
                                        (HMENU)IDC_MAIN_STATUS,
                                        GetModuleHandle(NULL),
                                        NULL);

               SendMessage( hStatus, SB_SETPARTS,
                            sizeof(statwidths)/sizeof(int),
                            (LPARAM)statwidths );
               SendMessage( hStatus, SB_SETTEXT, 0,
                            (LPARAM)"Bereit..." );
            }//Ende WM_CREATE
         break;
         case WM_SIZE:
            {
               HWND hTool;
               RECT rcTool;
               int iToolHeight;

               HWND hStatus;
               RECT rcStatus;
               int iStatusHeight;

               int iEditHeight;
               RECT rcClient;

               hTool = GetDlgItem(hwnd, IDC_MAIN_TOOL);
               SendMessage(hTool, TB_AUTOSIZE, 0, 0);

               GetWindowRect(hTool, &rcTool);
               iToolHeight = rcTool.bottom - rcTool.top;

               hStatus = GetDlgItem(hwnd, IDC_MAIN_STATUS);
               SendMessage(hStatus, WM_SIZE, 0, 0);

               GetWindowRect(hStatus, &rcStatus);
               iStatusHeight = rcStatus.bottom - rcStatus.top;

               GetClientRect(hwnd, &rcClient);

               iEditHeight = rcClient.bottom - iToolHeight -
                             iStatusHeight;

               hEdit = GetDlgItem(hwnd, IDC_MAIN_EDIT);
               SetWindowPos( hEdit, NULL, 0, iToolHeight,
                             rcClient.right, iEditHeight,
                             SWP_NOZORDER );
            }//Ende WM_SIZE
                break;
        case WM_CTLCOLOREDIT:
            {
               SetTextColor((HDC)wParam,textcolor);
               SetBkMode((HDC)wParam,OPAQUE);
               SetBkColor((HDC)wParam,backcolor);
               logbrush.lbColor = backcolor;
               DeleteObject(hbrush);
               hbrush = CreateBrushIndirect(&logbrush);
               return (long)hbrush;
            }// Ende WM_CTLCOLOREDIT
         break;
         case WM_CLOSE:
            DestroyWindow(hwnd);
         break;
         case WM_DESTROY:
            PostQuitMessage(0);
         break;
         case WM_COMMAND:
            switch(LOWORD(wParam))
               {
                  case ID_FILE_EXIT:
                     PostMessage(hwnd, WM_CLOSE, 0, 0);
                  break;
                  case ID_FILE_NEW:
                     SetDlgItemText(hwnd, IDC_MAIN_EDIT, "");
                     SendDlgItemMessage(hwnd, IDC_MAIN_STATUS,
                        SB_SETTEXT, 0, (LPARAM)"Neue Datei...");
                     SendDlgItemMessage(hwnd, IDC_MAIN_STATUS,
                            SB_SETTEXT, 1, (LPARAM)"Unbenannt");
                  break;
                  case ID_FILE_OPEN:
                     OpenFileBox(hwnd);
                  break;
                  case ID_FILE_SAVEAS:
                     SaveFileBox(hwnd);
                  break;
                  case ID_FORMAT_FONT:
                     {
                        FontChooseFont (hEdit);
                        FontSetFont (hEdit);
                     }
                   break;
                   case ID_FONT_COLOR:
                   if(ChooseColor(&choosecolor))
                      {
                         currentcolor = choosecolor.rgbResult;
                         textcolor = currentcolor;
                         InvalidateRect(hEdit, 0, TRUE);
                      }
                   break;
                   case ID_BK_COLOR:
                   if(ChooseColor(&choosecolor))
                      {
                         currentcolor = choosecolor.rgbResult;
                         backcolor = currentcolor;
                         InvalidateRect(hEdit, 0, TRUE);
                      }
                   break;
                   case ID_VERSION:
                       MessageBox( NULL,
                       "Ein einfacher Texteditor\nVersion 0.1",
                       "Version",MB_OK );
                   break;
               }//switch
         break;//WM_COMMAND
         default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
      }
   return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine, int nCmdShow)
{
   WNDCLASSEX wc;
   MSG Msg;

   //InitCommonControls();

   wc.cbSize = sizeof(WNDCLASSEX);
   wc.style = 0;
   wc.lpfnWndProc = WndProc;
   wc.cbClsExtra = 0;
   wc.cbWndExtra = 0;
   wc.hInstance         = hInstance;
   wc.hIcon = LoadIcon(NULL, MAKEINTRESOURCE(ID_ICON));
   wc.hCursor = LoadCursor(NULL, IDC_ARROW);
   wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
   wc.lpszMenuName = MAKEINTRESOURCE(IDR_MENU);
   wc.lpszClassName = g_szClassName;
   wc.hIconSm = (HICON)LoadImage( GetModuleHandle(NULL),
                                  MAKEINTRESOURCE(ID_ICON),
                                  IMAGE_ICON, 16, 16, 0 );

   if(!RegisterClassEx(&wc))
      {
         MessageBox( NULL,
              "Fensterklasse konnte nicht Initialisiert werden!",
              "Error!",
              MB_ICONEXCLAMATION | MB_OK);
         return 0;
      }

   hwnd = CreateWindowEx(0, g_szClassName,"Pronix - Texteditor",
                         WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
                         CW_USEDEFAULT, CW_USEDEFAULT, 640, 480,
                         NULL, NULL, hInstance, NULL);

   if(hwnd == NULL)
      {
          MessageBox( NULL, "Konnte kein Fenster erstellen!",
                      "Error!",MB_ICONEXCLAMATION | MB_OK  );
          return 0;
      }

   ShowWindow(hwnd, nCmdShow);
   UpdateWindow(hwnd);

   while(GetMessage(&Msg, NULL, 0, 0) > 0)
      {
         TranslateMessage(&Msg);
         DispatchMessage(&Msg);
      }
   return Msg.wParam;
}

Wenn Sie alle Dateien in einem Projekt vereint haben, befinden sich folgende Dateien in Ihrem Verzeichnis (bspw. bei der Verwendung der Bloodshed Dev-C++ Umgebung):

Projektdateien im Verzeichnis

Projektdateien im Verzeichnis

Wenn Sie aus Ihrem Projekt jetzt noch eine ausführbare Datei erstellen und das Programm starten, können Sie anschließend mit der Analyse des Quellcodes fortfahren.

8.2. Quellcode analysieren            zurück  zum Inhaltsverzeichnis

Da die WinMain()-Funktion dem üblichen Bekannten entspricht, soll gleich mit der Prozedur WndProc() angefangen werden, worin sich der komplette Programmablauf abspielt.

WM_CREATE
Es wird dabei gleich in die Auswertung der ersten Nachricht WM_CREATE gesprungen:

         case WM_CREATE:
            {
               HWND hTool;
               TBBUTTON tbb[3];
               TBADDBITMAP tbab;

               HWND hStatus;

               int statwidths[] = {100, -1};
               //Textfeld erstellen
               hEdit=CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", "",
                                    WS_CHILD | WS_VISIBLE |
                                    WS_VSCROLL | WS_HSCROLL |
                                    ES_MULTILINE|ES_AUTOVSCROLL|
                                    ES_AUTOHSCROLL,
                                    0, 0, 100, 100, hwnd,
                                    (HMENU)IDC_MAIN_EDIT,
                                    GetModuleHandle(NULL), NULL);
               if(hEdit == NULL)
                  MessageBox(hwnd,
                       "Fehler beim Erstellen des Textfelds",
                       "Fehler", MB_OK | MB_ICONERROR);

Hier erstellen Sie mit CreateWindowsEx() ein EDIT-Feld aus einer vordefinierten Fensterklasse der WIN-API. Aber die Funktion haben Sie ja bereits kennen gelernt und stellt somit nichts mehr Neues für Sie da. Damit das Editfeld auch funktioniert, wie es sich für einen Texteditor gehört, geben Sie mit dem vierten Parameter den Stil des Fensters an. Mit WS_CHILD erzeugen Sie ein abgeleitetes Fenster, welches sichtbar (WS_VISIBLE) ist. Mit WS_VSCROLL und WS_HSCROLL erzeugen Sie außerdem eine vertikale und horizontale Bildlaufleiste. Mit den Konstanten ES_AUTOVSCROLL geben Sie an, wenn Sie in der letzten Zeile des Texteditors ENTER drücken, wird der restliche Text nach oben gescrollt. Selbiges gilt für ES_AUTOHSCROLL nur eben für die horizontale Bildlaufleiste. Damit das EDIT-Feld mehr als eine Zeile besitzt (Default-Einstellung) wird die Konstante ES_MULTILINE verwendet. Wichtig ist auch ein Handle (hwnd) auf das Stammfenster und die ID-des Editfeld (IDC_MAIN_EDIT). Diese ID benötigen Sie bei den WM_COMMAND-Nachrichten, wenn für das Steuerelement eine Aktion ausgeführt werden soll.

Nachdem Erzeugen des Editfeldes wird die Schriftart des Editfeldes initialisiert:

FontInitialize (hEdit); 

Dabei handelt es sich um eine von Ihnen selbst erstellte Funktion im Listing, die wie folgt aussieht:

void FontInitialize (HWND hwndEdit)
{
     GetObject (GetStockObject (SYSTEM_FONT), sizeof (LOGFONT),
                (PTSTR) &logfont) ;

     hFont = CreateFontIndirect (&logfont) ;
     SendMessage (hwndEdit, WM_SETFONT, (WPARAM) hFont, 0) ;
}

Als Parameter übergeben Sie der Funktion FontInitialize() den Handle auf das Editfeld. Zuerst werden mit der Funktion GetObject() Informationen in den Puffer logfont (dritter Parameter) übergeben. logfont wurde am Anfang des Listings als globale Struktur vom Typ LOGFONT deklariert. Diese Struktur beinhaltet Attribute einer Schrift. Welche Daten in diese Adresse gelegt werden, hängt wiederum vom ersten Parameter der Funktion GetObject() ab. Dabei wurde der Funktionsaufruf GetStockObject(SYSTEM_FONT) angegeben. Diese Funktion gibt ein Handle auf ein vordefiniertes Objekt zurück. Dabei handelt es sich um Objekte, die immer im System vorhanden sind und somit keine unnötigen Resourcen benötigen. In dem Fall wird durch die Angaben von SYSTEM_FONT ein Handle der Systemschrift von Windows zurückgegeben. Mit dem zweiten Parameter der Funktion GetObject() geben Sie die Größe des Puffers von logfont an. Außer wie hier angegeben, kann die Funktion GetObject() auch Informationen über eine Bitmap, einen Pinsel, eine Palette oder einen logischen Stift zurückliefern.

Anschließend erzeugen Sie mit der Funktion CreateFontIndirect() eine logische Schrift, welche die Eigenschaften von logfont hat. War die Funktion erfolgreich, bekommen Sie einen Handle einer logischen Schrift vom Typ HFONT zurück. HFONT hFont wurde ebenfalls zu Beginn des Listings global deklariert.

Jetzt verschicken Sie die Nachricht WM_SETFONT an das Editfeld. Damit wird vorerst mal mit der Systemschrift von Windows der Text ausgegeben. Somit hätten Sie eine Schriftart initialisiert und die Grundlagen für weitere Schriftarten auswählen gelegt.

Wieder zurück zur WndProc() Prozedur:

               logbrush.lbHatch = 0;
               logbrush.lbStyle = BS_SOLID;
               logbrush.lbColor = WEISS;

Hier übergeben Sie der Struktur-Variablen logbrush vom Typ LOGBRUSH, was einen logischen Pinsel darstellt, die Werte 0 für lbHatch, BS_SOLID für lbStyle und die Farbe WEISS für lbColor. lbHatch ist der Schraffur-Stil des Pinsels, lbStyle ist der Pinselstil und lbColor die Farbe. Hierbei handelt es sich noch lediglich um eine Initialisierung der Struktur-Variablen logbrush, welche später benötigt wird, wenn die Hintergrundfarbe des Editfeld verändert wird. Wichtig dabei ist die Konstante BS_SOLID, womit das komplette Editfeld in eine entsprechende Hintergrundfarbe gezeichnet wird. Wenn der Pinsel etwas später im Programm verwendet wird, werden Sie sich dann sicherlich fragen, wofür dieser überhaupt verwendet wurde, sehen Sie sich am besten den Screenshot des Texteditors ohne der Verwendung von LOGBRUSH an:

Nur der geschriebene Teil besitzt eine Hintergrundfarbe

Nur der geschriebene Teil besitzt eine Hintergrundfarbe

Es wird später nochmals darauf eingegangen.

Weiter geht es mit dem Aufruf der selbst geschriebenen Funktion SetupChooseColor() mit:

                SetupChooseColor( &choosecolor,customcolors,
                                 hEdit,currentcolor);

In dieser Funktion initialisieren Sie einzelne Variablen der CHOOSECOLOR-Struktur. Diese Struktur wird für Informationen verwendet, womit Sie später mit der Funktion ChooseColor() eine systembasierte Dialog-Box zur Auswahl von Farben aufrufen können.

void SetupChooseColor(CHOOSECOLOR *cc,COLORREF *customclrs,
                      HWND hwnd, COLORREF init)
{
   int ca;
   for(ca = 0; ca < 16; ca++)
      *(customclrs+ca) = SCHWARZ;

   cc->lStructSize = sizeof(CHOOSECOLOR);
   cc->hwndOwner = hwnd;
   cc->rgbResult = init;
   cc->lpCustColors = customclrs;
   cc->Flags = CC_ANYCOLOR|CC_FULLOPEN|CC_RGBINIT;
}

In der Schleife initialisieren Sie die benutzerdefinierten Farbwerte erst mal alle mit Schwarz. Sie können dabei natürlich jede andere beliebige Farbe verwenden. Den Farbwert übergeben Sie dabei erst mal dem globalen Array static COLORREF customcolors[16] und anschließend mit

   cc->lpCustColors = customclrs; 

an die Strukturvariable CHOOSECOLOR. Damit Sie wissen, wovon ich schreibe, auf dem Bild ist ein roter Pfeil, der auf die benutzerdefinierten Farben zeigt.

Auswahl von benutzerdefinierter Farbe (roter Pfeil)

Auswahl von benutzerdefinierter Farbe (roter Pfeil)

Die weiteren Variablen für die Struktur währen lStructSize, welche die Größe in Bytes der Struktur selbst erhält, die Strukturvariable hwndOwner bekommt ein Handle auf das Fenster, welches farblich verändert werden soll. Mit rgbResults geben Sie die Farbe an, die beim ersten Starten der Dialog-Box ausgewählt (selektiert) ist. In unserem Fall währe dies Schwarz, wie Sie auch im Bild oben entnehmen können. Und mit Flags geben Sie noch einige Initialisierungs-Flags an, womit die Dialog-Box spezifiziert werden kann. Mit CC_ANYCOLOR werden alle möglichen Farben in der Dialogbox angezeigt. Ohne CC_FULLOPEN würde die Dialog-Box so aussehen:

Farbauswahl ohne dem Flag CC_FULLOPEN

Farbauswahl ohne dem Flag CC_FULLOPEN

Für weitere Flags und Informationen bezüglich der CHOOSECOLOR Struktur lesen Sie bitte in der MSDN-Dokumentation nach. Somit hätten Sie die Farbauswahl-Dialog-Box initialisiert und können diese im Programm mit ChooseColor() aufrufen. Was Sie etwas später auch noch tun werden.

Wieder zurück zur Windows-Prozedur WndProc() in der Nachricht WM_CREATE.

               //Werkzeugleiste erstellen
               hTool = CreateWindowEx(0,TOOLBARCLASSNAME, NULL,
                                      WS_CHILD|WS_VISIBLE,
                                      0, 0, 0, 0,hwnd,
                                      (HMENU)IDC_MAIN_TOOL,
                                      GetModuleHandle(NULL),
                                      NULL);
               if(hTool == NULL)
                  MessageBox(hwnd,
                         "Kann keine Werkzeugleiste erstellen",
                         "Fehler", MB_OK | MB_ICONERROR);

Hier verwenden Sie eine vordefinierte Fensterklasse (TOOLBARCLASSNAME) und zwar eine Werkzeugleiste für Schaltflächen wie in der folgenden Abbildung zu sehen ist:

Werkzeugleiste

Werkzeugleiste

In der folgenden Zeile

               SendMessage( hTool, TB_BUTTONSTRUCTSIZE,
                            (WPARAM)sizeof(TBBUTTON), 0 );

senden Sie dann die Nachricht TB_BUTTONSTRUCTSIZE. Mit dieser Nachricht geben Sie die Größe der TBBUTTON-Struktur an. Sollten Sie die Funktion CreateToolbarEx() zum Erzeugen einer Werkzeugleiste anstatt CreateWindowEx(), verwenden, müssen Sie diese Nachricht nicht mehr versenden. Dann übernimmt das die Funktion CreateToolbarEx() für Sie. Jetzt haben Sie zwar eine Werkzeugleiste initialisiert, doch bis jetzt befindet sich noch kein einziger Button darauf. Als Nächstes folgt hierbei erstmal

               tbab.hInst = HINST_COMMCTRL;
               tbab.nID = IDB_STD_LARGE_COLOR;
               SendMessage(hTool,TB_ADDBITMAP,0,(LPARAM)&tbab);

Damit übergeben Sie den einzelnen Strukturvariablen der Struktur TBADDBITMAP Werten. Diese Struktur wird benötigt, um Bitmaps für die einzelnen Buttons der Toolbar hinzu zufügen. Der ersten Strukturvariablen hInst übergeben Sie die Instanz des ausführbaren Programms. In diesem Beispiel geben Sie mit HINST_COMMCTRL an, dass hierbei vom System definierte Bitmaps verwendet werden. Mit der zweiten Sturkturvariablen geben Sie an, welche System-Buttons Sie dafür verwenden wollen. Mit IDB_STD_LARGE_COLOR definieren Sie, dass Sie große, farbige Standard-Bitmaps verwenden wollen.

Hätten Sie gerne kleine Buttons verwendet, können Sie die Konstante IDB_STD_SMALL_COLOR verwenden. Für weitere Konstanten empfehle ich die MSDN-Dokumentation. Nachdem Sie die Sturkturvariablen von TBADDBITMAP initialisiert haben, benachrichtigen Sie Windows darüber in dem Sie mit SendMessage() die Nachricht TB_ADDBITMAP senden.

Nachdem Windows jetzt weiß, dass Sie vordefinierte System-Bitmaps für die Buttons verwenden wollen, können Sie die einzelnen Buttons in der Toolbar initialisieren. An der Deklaration von

TBBUTTON tbb[3]; 

können Sie erkennen, dass Sie drei Buttons verwenden. Die Informationen zu den Buttons in der Toolbar müssen Sie der Sturkturvariablen TBBUTTON übergeben.

               ZeroMemory(tbb, sizeof(tbb));
               tbb[0].iBitmap = STD_FILENEW;
               tbb[0].fsState = TBSTATE_ENABLED;
               tbb[0].fsStyle = TBSTYLE_BUTTON;
               tbb[0].idCommand = ID_FILE_NEW;

               tbb[1].iBitmap = STD_FILEOPEN;
               tbb[1].fsState = TBSTATE_ENABLED;
               tbb[1].fsStyle = TBSTYLE_BUTTON;
               tbb[1].idCommand = ID_FILE_OPEN;

               tbb[2].iBitmap = STD_FILESAVE;
               tbb[2].fsState = TBSTATE_ENABLED;
               tbb[2].fsStyle = TBSTYLE_BUTTON;
               tbb[2].idCommand = ID_FILE_SAVEAS;

               SendMessage( hTool, TB_ADDBUTTONS,
                            sizeof(tbb)/sizeof(TBBUTTON),
                            (LPARAM)&tbb );

Da nicht alle Strukturvariablen von TBBUTTON verwendet werden, füllen Sie mit der Funktion ZeroMemory() den kompletten Speicherblock mit Nullen. Würden Sie das nicht machen, würde das Resultat dann in etwas so aussehen:

Undefinierte Strukturvariablen können Probleme machen

Undefinierte Strukturvariablen können Probleme machen

Außerdem sind die weiteren Vorgänge des Programms ebenfalls undefiniert. Sehen Sie sich die Initialisierung eines Buttons am ersten Beispiel an. Mit iBitmap geben Sie die Bitmap an, welche Sie verwenden wollen. Da hierbei vordefinierte System-Bitmaps verwendet werden, wurde hier die Konstante STD_FILENEW angegeben. Deren Bedeutung lässt sich bereits am Namen erahnen. Damit wird die Bitmap für eine neue Datei angezeigt.

System-Bitmap für Neue Datei

System-Bitmap für Neue Datei

Für weitere Informationen zu den verschieden vordefinierten Systembuttons sollten Sie sich die MSDN-Dokumentation unter der Struktur TBADDBITMAP nachsehen. Mit fsState geben Sie den Status für den Button an. Hier TBSTATE_ENABLED, womit der Button bereit für die Eingabe des Anwenders ist. Geben Sie diesen Status nicht an, ist der Button ausgegraut. Mit TBSTATE_HIDDEN können Sie den Button ganz verstecken. Mehr zu den Status von Buttons können Sie in der MSDN-Dokumentation unter "Toolbar Button States" nachlesen. Mit TBSTYLE_BUTTON geben Sie in der Sturkturvariable fsStyle an, dass Sie einen einfachen normalen Standardbutton erstellen. Die Variable idCommand ist die Wichtigste, ohne der Ihr Button wohl nicht viel Sinn hätte. Damit geben Sie den Kommando-Idenfizierer für die Nachricht WM_COMMAND an. Nicht verwendet wurde die Strukturvariable iString, womit Sie einen Index für einen nullterminierten String angeben können. Bspw. tbb[0].iString = (int)"Neu" würde folgendes Ergebnis ausgeben:

Button beschriftet

Button beschriftet

Ebenso wurde die Strukturvariable dwData nicht verwendet, welche ein Applikations-definierter Wert ist. Die anderen beiden Buttons können Sie jetzt von selbst nachvollziehen. Jetzt können Sie die Buttons zu Ihrer Werkzeugleiste hinzufügen:

               SendMessage( hTool, TB_ADDBUTTONS,
                            sizeof(tbb)/sizeof(TBBUTTON),
                            (LPARAM)&tbb );

Außer der Nachricht TB_ADDBUTTONS ist der dritte Parameter bei der Funktion SendMessage von Bedeutung. Damit geben Sie an, wieviele Button hinzugefügt werden sollen.

Nachdem die Werkzeugleiste erstellt wurde, soll nun die Statusleiste erstellt werden.

Statusleiste beschriften

Statusleiste beschriften

Dies geschieht mit den folgenden Zeilen:

               //Statusleiste erstellen
               hStatus = CreateWindowEx(0,STATUSCLASSNAME,NULL,
                                        WS_CHILD | WS_VISIBLE |
                                        SBARS_SIZEGRIP,
                                        0, 0, 0, 0, hwnd,
                                        (HMENU)IDC_MAIN_STATUS,
                                        GetModuleHandle(NULL),
                                        NULL);

               SendMessage( hStatus, SB_SETPARTS,
                            sizeof(statwidths)/sizeof(int),
                            (LPARAM)statwidths );
               SendMessage( hStatus, SB_SETTEXT, 0,
                            (LPARAM)"Bereit..." );

Hierzu verwenden wir wieder eine Fensterklasse (STATUSCLASSNAME) um die Statusleiste zu erzeugen. Anschließend senden Sie die Nachricht SB_SETPARTS zum teilen der Statusleiste und geben mit dem dritten Parameter an, wie viele Pixel der linke Teil der Statusleise besitzen soll. Gleich darauf verschicken Sie noch eine Nachricht (SB_SETTEXT), womit der Text für die linke Seite der Statusleiste angegeben wird. Dass der Text "Bereit…" im linken Teil der Statusleiste erscheint ist dem dritten Parameter (0) zu verdanken. Geben Sie hierbei 1 an, befindet sich der Text im rechten Teil der Statusleiste.

WM_SIZE
Mit der Auswertung der Nachricht WM_SIZE sorgen Sie für die automatische Anpassung des Fensters bei Veränderung der Größe. Speziell beim Editfeld, der Werkzeugleiste und der Statusleiste ist dies unbedingt nötig. Wenn Sie dies ignorieren, bekommen Sie solch ein Ergebnis zurück:

Mögliche Probleme beim Missachten der Nachricht WM_SIZE

Mögliche Probleme beim Missachten der Nachricht WM_SIZE

Auf die Verarbeitung von WM_SIZE wird hierbei nur kurz eingegangen.

               hTool = GetDlgItem(hwnd, IDC_MAIN_TOOL);
               SendMessage(hTool, TB_AUTOSIZE, 0, 0);

Zuerst ermitteln Sie mit der Funktion GetDlgItem() den Handle für das Steuerelement der Werkzeugleiste. Dazu ist nur als erster Parameter, der Dialogfeld-Handle nötig, der das Steuerelement beinhaltet und als zweiter Parameter die ID des zu ermittelnden Steuerelementes. Dieser ermittelte Handle wird jetzt mit der Funktion SendMessage() und der Nachricht TB_AUTOSIZE versendet. Diese Nachricht können Sie versenden, wenn sich die Größe der Werkzeugleiste verändert hat.

Anschließend holen Sie sich die Höhe der Werkzeugleiste mit:

               GetWindowRect(hTool, &rcTool);
               iToolHeight = rcTool.bottom - rcTool.top;

Ähnliches, was mit der Werkezeugleiste gemacht wurde, wird kurz darauf auch mit der Statusleiste gemacht:

               hStatus = GetDlgItem(hwnd, IDC_MAIN_STATUS);
               SendMessage(hStatus, WM_SIZE, 0, 0);

               GetWindowRect(hStatus, &rcStatus);
               iStatusHeight = rcStatus.bottom - rcStatus.top;

Jetzt ermitteln Sie die komplette Größe des Fensters:

GetClientRect(hwnd, &rcClient);

wovon Sie gleich in der nächsten Zeile mit

               iEditHeight = rcClient.bottom - iToolHeight -
                             iStatusHeight;

die gesamte Höhe von der Höhe der Werkezeugleiste und die Höhe der Statusleiste abziehen. Als Ergebnis dieser Subtraktion erhalten Sie die Höhe, des Editfeldes. Von diesem ermitteln Sie auch das Handle mit

               hEdit = GetDlgItem( hwnd, IDC_MAIN_EDIT );

Anschießend ändern Sie die Größe und Position des Editfeldes entsprechend mit der Funktion:

               SetWindowPos( hEdit, NULL, 0, iToolHeight,
                             rcClient.right, iEditHeight,
                             SWP_NOZORDER );

Benötigen Sie mehr Informationen zu den einzelnen Funktionen sei hier, wie immer, auf die MSDN-Dokumentation hingewiesen.

WM_CTLCOLOREDIT
Diese Nachricht wird bspw. gesendet, wenn die Textfarbe oder der Hintergrund eines Edit-Feldes verändert wird. Zuerst setzen Sie die Textfarbe textcolor vom Type COLORREF mit

               SetTextColor((HDC)wParam,textcolor); 

Die Textfarbe zu ändern ist einfach. Der Gerätekontex zum Ändern der Farbe befindet sich im Parameter wParam der WndProc()-Prozedur. In der nächsten Zeile setzen Sie den Modus, wie die Hintergrundfarbe verändert werden soll.

               SetBkMode((HDC)wParam,OPAQUE);

Auch hier verwenden Sie wieder den Gerätekontex in wParam und den Modus OPAQUE, womit zuerst der Hintergrund mit einer bestimmten Farbe gefüllt wird, bevor der Text gezeichnet wird. Mit den nächsten Zeilen setzen Sie dann die Hintergrundfarbe.

               SetBkColor((HDC)wParam,backcolor);
               logbrush.lbColor = backcolor;
               DeleteObject(hbrush);

               hbrush = CreateBrushIndirect(&logbrush);
               return (long)hbrush;

Warum nach der Funktion SetBkColor() nochmals einen logischen Pinsel erzeugt (CreateBrushIndirect) wird, sollen die folgenen beiden Bilder zeigen:

Ohne CreateBrushIndirect()

Ohne CreateBrushIndirect()

Ohne SetBkColor()

Ohne SetBkColor()

Ich denke die beiden Bilder demonstrieren den Sachverhalt eindeutig.

WM_COMMAND
Jetzt erst können Sie zum eigentlichen Thema des Kapitels kommen. Denn Auswertungen der Menüeingaben und vor allem der Datei Ein/Ausgabe Funktionen.

Beginnen wollen wir mit der Menüauswahl DATEI/NEU:

                  case ID_FILE_NEW:
                     SetDlgItemText(hwnd, IDC_MAIN_EDIT, "");
                     SendDlgItemMessage(hwnd, IDC_MAIN_STATUS,
                         SB_SETTEXT, 0, (LPARAM)"Neue Datei...");
                     SendDlgItemMessage(hwnd, IDC_MAIN_STATUS,
                             SB_SETTEXT, 1, (LPARAM)"Unbenannt");

Zuerst setzen Sie mit SetDlgItemText() den Titel oder den Text im Steuerelement des Editfeldes (IDC_MAIN_EDIT). Der erste Parameter ist der Dialog-Handle, der zweite die ID des zu ändernden Steuerelementes und der dritte Parameter ist ein Zeiger auf einen nullterminierten String, welcher hier, wie es sich für eine neue leere Datei gehört leer ist. Würden Sie bspw. Folgendes schreiben

SetDlgItemText( hwnd, IDC_MAIN_EDIT,
                "Shareware - Bitte registrieren Sie sich" );

würde jedesmal, wenn Sie eine neue Textdatei erstellen wollen, Folgendes ausgegeben:

Vorbelegter Text mit der Funktion SetDlgItemText()

Vorbelegter Text mit der Funktion SetDlgItemText()

Mit den nächsten beiden Zeilen geben Sie an, welcher Text in der Statusleiste (SetDlgItemMeessage) stehen soll, wenn der Anwender eine neue Datei erstellt:

Neue Datei wurde erzeugt - Info in der Statusleiste

Neue Datei wurde erzeugt - Info in der Statusleiste

Der nächste Fall ist das Öffnen einer neuen Textdatei und somit Ihre erste Datei Ein/Ausgabe-Operation:

                  case ID_FILE_OPEN:
                     OpenFileBox(hwnd);

Zuerst übergeben Sie der selbst geschriebenen Funktion OpenFileBox() den Fensterhandle hwnd.

void OpenFileBox(HWND hwnd)
{
   OPENFILENAME ofn;
   char szFileName[MAX_PATH] = "";

   ZeroMemory(&ofn, sizeof(ofn));

   ofn.lStructSize = sizeof(ofn);
   ofn.hwndOwner = hwnd;
   ofn.lpstrFilter = "Text Datei (*.txt)\0*.txt\0"
                     "C Datei (*.c)\0*.c\0"
                     "Alle Dateien (*.*)\0*.*\0";
   ofn.lpstrFile = szFileName;
   ofn.nMaxFile = MAX_PATH;
   ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST |
               OFN_HIDEREADONLY;
   ofn.lpstrDefExt = "txt";

   if(GetOpenFileName(&ofn))
      {
         HWND hEdit = GetDlgItem(hwnd, IDC_MAIN_EDIT);
         if(LoadText(hEdit, szFileName))
            {
               SendDlgItemMessage(hwnd, IDC_MAIN_STATUS,
                           SB_SETTEXT, 0, (LPARAM)"Geöffnet...");
               SendDlgItemMessage(hwnd, IDC_MAIN_STATUS,
                              SB_SETTEXT, 1, (LPARAM)szFileName);
            }
      }
}

Um eine Datei in den Texteditor zur laden, benötigen Sie eine Dialog-Box, welche Windows gewöhnlich zum Öffnen einer Datei verwendet.

Dialog-Box zum Öffnen einer Datei

Dialog-Box zum Öffnen einer Datei

Diese Dialog-Box können Sie mit der Funktion GetOpenFileName() erzeugen. Als Parameter benötigt diese Funktion einen Zeiger auf die Struktur OPENFILENAME, welche zu beginn der Funktion OpenFileBox() deklariert wurde. Diese Struktur beinhaltet alle Informationen zur Dialogbox, die benötigt werden, um diese zu initialisieren.

Da diese Struktur recht umfangreich ist und Sie nicht alle Strukturvariablen initialisieren wollen, behelfen wir unser wieder mit dem Trick:

   //alle Strukturvariabeln erstmal mit 0 belegen
   ZeroMemory(&ofn, sizeof(ofn));

Anschließend initialisieren Sie einige Strukturvariablen:

   ofn.lStructSize = sizeof(ofn);
   ofn.hwndOwner = hwnd;
   ofn.lpstrFilter = "Text Datei (*.txt)\0*.txt\0"
                     "C Datei (*.c)\0*.c\0"
                     "Alle Dateien (*.*)\0*.*\0";
   ofn.lpstrFile = szFileName;
   ofn.nMaxFile = MAX_PATH;
   ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST |
               OFN_HIDEREADONLY;
   ofn.lpstrDefExt = "txt";

Mit lStructSize geben Sie die Größe der Struktur in Bytes an. hwndOwner spezifiziert das Fenster, für welches die Dialogbox gedacht ist. Mit lpstrFilter können Sie einen Filter für spezielle Dateien verwenden. Bspw. mit

"C Datei (*.c)\0*.c\0"  

steht im Feld Dateityp "C Datei (*.c)" und dahinter befindet sich "*.c" der eigentliche Filter, welche Dateien angezeigt werden sollen. Wenn die Funktion GetOpenFileName() erfolgreich eine Datei öffnen konnte, befindet sich im String szFileName anschließend der Pfad und der Dateinamen. Daher wird der Strukturvariable lpstrFile die Anfangsadresse des Strings übergeben. nMaxFile bekommt die maximale Länge des Dateinamens. Damit es kein Problem mit 16bit und 32bit-Anwendung gibt, wird hier die Konstante MAX_PATH verwendet. Bei den Flags können Sie noch eine Menge mehr Angaben zur Dialogbox verknüpfen. Mit der Strukturvariable lptstrDefExt geben Sie die Default-Extension an, wenn der Anwender kein Extension für eine Datei angibt.

Jetzt wird versucht, eine Datei mit der Dialog-Box und der Funktion GetOpenFileName() zu öffnen. Gelingt dies, ermitteln Sie zuerst den Handel zum Editfeld und rufen anschließend mit dem Handle und dem Dateinamen in szFileName die selbst geschriebene Funktion LoadText() auf.

         HWND hEdit = GetDlgItem(hwnd, IDC_MAIN_EDIT);
         if(LoadText(hEdit, szFileName))

Die Funktion LoadText():

BOOL LoadText(HWND hEdit, LPCTSTR pszFileName)
{
   HANDLE hFile;
   BOOL bSuccess = FALSE;

   hFile = CreateFile(pszFileName, GENERIC_READ,
                       FILE_SHARE_READ, NULL,
                       OPEN_EXISTING, 0, NULL);
   if(hFile != INVALID_HANDLE_VALUE)
      {
         DWORD dwFileSize;

         dwFileSize = GetFileSize(hFile, NULL);
         if(dwFileSize != 0xFFFFFFFF)
         {
            LPSTR pszFileText;

            pszFileText = GlobalAlloc(GPTR, dwFileSize + 1);
            if(pszFileText != NULL)
            {
               DWORD dwRead;
               if(ReadFile( hFile, pszFileText, dwFileSize,
                             &dwRead, NULL )   )
                  {
                     pszFileText[dwFileSize] = '\0';
                     if(SetWindowText(hEdit, pszFileText))
                           bSuccess = TRUE;
                  }
               GlobalFree(pszFileText);
            }
         }
         CloseHandle(hFile);
      }
   return bSuccess;
}

Dateien erstellen und öffnen - CreateFile()
Mit der Win32-API werden Dateien mit nur einer Funktion erstellt und geöffnet:

HANDLE CreateFile (
   LPCTSTR  lpFileName,
   DWORD  dwDesiredAccess,
   DWORD  dwShareMode,
   LPSECURITY_ATTRIBUTES  lpSecurityAttributes,
   DWORD  dwCreationDistribution,
   DWORD  dwFlagsAndAttributes,
   HANDLE  hTemplateFile
                  );

Dabei ist es egal, ob es sich bei dieser Datei um eine benannte Pipe, Kommunikationsressource, Festplatte, Disketten, Konsole oder ein Verzeichnis handelt. Die Funktion CreateFile() gibt als Rückgabewert einen HANDLE, der zur weiteren Verarbeitung (Lesen, Schreiben) auf das Objekt (die Datei) verwendet werden kann. Sofern Sie sich ein wenig mit der Programmierung der niedrigeren Ebene in C auskennen, können Sie diesen Handle mit dem Vergleichen, der auch bei den Funktionen open(), read() oder write() verwendet wird. Jetzt zu den einzelnen Parameter dieser umfangreichen Funktion im Vergleich mit unserem Listing und dem Funktionsaufruf

   hFile = CreateFile(pszFileName, GENERIC_READ,
                       FILE_SHARE_READ, NULL,
                       OPEN_EXISTING, 0, NULL);

Der erste Parameter von CreateFile() ist ein Zeiger auf einen nullterminierten String, der den Namen der Datei, Pipe usw. beinhaltet. In diesem Beispiel haben Sie diesen Namen mit der Dialogbox "Öffnen" in der selbst geschriebenen Funktion OpenFileBox() ermittelt und als Parameter an die Funktion LoadText() übergeben. Die Länge des Strings darf MAX_PATH Zeichen nicht überschreiten. Diese max. Länge kann aber auch theoretisch umgangen werden, was aber hier nicht behandelt wird. Mit dem zweiten Parameter von CreateFile() geben Sie den Zugriffstyp der Datei an. Mit GENERIC_READ spezifizieren Sie eine Datei, auf die nur im Lesemodus zugegriffen werden kann. Wollen Sie hingegen den Zugriffstyp einer Datei zum Schreiben spezifizieren müssen Sie GENERIC_WRITE angeben. Soll beides möglich sein müssen Sie beide Angaben, unter verwenden eines Bitweisen ODER machen. Geben Sie hingegen 0 an, erlauben Sie der Anwendung, die Geräteattribute abzufragen, ohne wirklich auf das Gerät zuzugreifen. Der dritte Parameter gibt an, wie die Datei gemeinsam benutzt werden kann. Mit FILE_SHARE_READ geben Sie bspw. an, dass andere Aktionen zum Öffnen im Lesezugriff auf diese Datei durchgeführt werden. Den gegenteiligen Effekt erreichen Sie mit FILE_SHARE_WRITE. Der vierte Parameter ist nur unter NTFS-Datei-Systemen verwendbar und enthält die SECURITY_ATTRIBUTES-Struktur, womit das Sicherheitsattribut für das Verzeichnis angegeben wird. Meistens wird dieser Wert nicht verwendet und auf NULL gesetzt. Der nächste Parameter gibt an, was die Funktion machen soll, wenn eine Datei bereits existiert oder nicht.

Ein Überblick zu den möglichen Werten finden Sie in der folgenden Tabelle:

Wert Bedeutung
CREATE_ALWAYS Datei wird erzeugt. Existiert diese bereits, wird sie überschrieben.
CREATE_NEW Datei wird erzeugt. Existiert diese Datei bereits schlägt die Funktion CreateFile fehl.
OPEN_ALWAYS Öffnet eine Datei. Existiert diese nicht, wird sie erzeugt.
OPEN_EXISTING Öffnet eine Datei nur, wenn diese existiert. Ansonsten schlägt CreateFile fehl.
TRUNCATE_EXISTING Öffnet ein Datei und verringert die Größe auf 0 Bytes. Vorraussetzung dieses Wertes ist ein GENERIC_WRITE-Zugriff.

Mit dem sechsten Parameter können Sie die Attribute einer Datei vergeben. Dabei sind mehrere Kombinationen möglich. Da Sie ja erst mal die Datei geöffnet haben und bearbeiten, setzen Sie dieses Flag später beim Speichern der Datei. Daher hier erst mal 0. Hierzu einige Attribute, welche Sie der Datei vergeben können:

Wert Bedeutung
FILE_ATTRIBUTE_HIDDEN Datei wird versteckt
FILE_ATTRIBUTE_NORMAL Dieser Wert kann nur alleine verwendet werden. Datei hat keine Attribute.
FILE_ATTRIBUTE_OFFLINE Datei ist nicht unmittelbar verfügbar da die Dateidaten phsyisch in den Offline-Speicher geschoben wurden.
FILE_ATTRIBUTE_READONLY Aus Datei darf nur gelesen werden
FILE_ATTRIBUTE_SYSTEM Datei ist eine Systemdatei
FILE_ATTRIBUTE_TEMPORARY Datei wird temporär gespeichert

Mit dem letzten Parameter der Funktion CreateFile() geben Sie ein Handle auf eine Schablonen (Template) Datei mit GENERIC_READ Zugriff an. Dieser Parameter wird selten genutzt und wir setzen ihn auch auf NULL. Zugegeben zur CreateFile() Funktion lässt sich noch eine Menge mehr schreiben, aber dazu sei die MSDN-Dokumentation empfohlen. Zurück zum Programm. In der nächsten Zeile überprüfen Sie, ob die Funktion CreateFile() auch erfolgreich eine Datei geöffnet hat (ungleich INVALID_HANDLE_VALUE).

   if(hFile != INVALID_HANDLE_VALUE) 

Es wird hier davon ausgegangen, dass alles glatt verlief. Weiter in dem Anweisungsblock der if-Bedingung:

         DWORD dwFileSize;

         dwFileSize = GetFileSize(hFile, NULL);
         if(dwFileSize != 0xFFFFFFFF)

Mit der Funktion GetFileSize() ermitteln Sie die Länge des Textes in Bytes, die Sie eben mit CreateFile() geöffnet bzw. erzeugt haben. Wenn die Funktion den Wert 0xFFFFFFFF zurückgibt ist ein Fehler aufgetreten. Falls nicht, können Sie mit dem nächsten Anweisungsblock weitermachen:

           LPSTR pszFileText;

            pszFileText = GlobalAlloc(GPTR, dwFileSize + 1);
            if(pszFileText != NULL)

Damit Sie den Text auch anschließend in dem Texteditor einlesen können, benötigen Sie Speicherplatz dafür. Diesen Allozieren Sie hier vom Heap mit der Funktion GlobalAlloc(). Mit dem ersten Parameter geben Sie, wie der Speicher allloziert werden soll. Hier eine kurze Übersicht zu den verschiedenen Möglichkeiten:

Wert Bedeutung
GMEM_FIXED Alloziert einen festen Speicher.
GMEM_MOVABLE Alloziert einen verschiebbaren Speicher.
GMEM_ZEROINT Initialisiert den Speicherinhalt mit 0
GHND Kombination aus GMEM_MOVABLE und GMEM_ZEROINT
GPTR Kombination aus GMEM_FIXED und GMEM_ZEROINT

Mit dem zweiten Parameter von GlobalAlloc() geben Sie die Anzahl der zu allozierenden Bytes an. Ist der Rückgabewert von GlobalAlloc() ungleich NULL, können Sie einen Anweisungsblock tiefer gehen:

               DWORD dwRead;
               if(ReadFile( hFile, pszFileText, dwFileSize,
                             &dwRead, NULL )   )
                  {
                     pszFileText[dwFileSize] = '\0';
                     if(SetWindowText(hEdit, pszFileText))
                           bSuccess = TRUE;
                  }
               GlobalFree(pszFileText);

Jetzt lesen Sie mit der Funktion ReadFile() aus dem Handle hFile, dwFileSize Bytes in den Puffer pszFileText. In dwRead befinden sich die Anzahl erfolgreich gelesener Bytes. Den letzten Parameter können Sie wieder auf NULL setzen. Jetzt wird der String noch terminiert und anschließend geben Sie den kompletten Text mit der Funktion SetWindowText() in den Fenster-Handle hEdit, also dem Texteditor aus. Da der Text ausgegeben wurde, können Sie bSuccess auf TRUE setzen. Mit GlobalFree() geben Sie den alllozierten Speicher wieder an das System zurück.

Jetzt können Sie wieder zur WM_COMMAND Nachricht zurückkehren und mit der nächsten Menüauswahl fortfahren:

                  case ID_FILE_SAVEAS:
                     SaveFileBox(hwnd);

Eine genauere Erläuterung zur Funktion SaveFileBox() kann ich mir hier ersparen, da diese ähnlich aufgebaut ist, wie die Funktion OpenFileBox(). Nur wird hierbei die Dialogbox anstatt mit der Funktion GetOpenFileName() mit der Funktion (logisch oder) GetSaveFileName() geöffnet. Auch die Funktion SaveText() verläuft dann recht ähnlich ab wie die Funktion LoadText().

BOOL SaveText(HWND hEdit, LPCTSTR pszFileName)
{
   HANDLE hFile;
   BOOL bSuccess = FALSE;

   hFile = CreateFile(pszFileName, GENERIC_WRITE, 0, NULL,
                    CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
   if(hFile != INVALID_HANDLE_VALUE)
      {
         DWORD dwTextLength;

         dwTextLength = GetWindowTextLength(hEdit);
         if(dwTextLength > 0)
            {
               LPSTR pszText;
               DWORD dwBufferSize = dwTextLength + 1;
               pszText = GlobalAlloc(GPTR, dwBufferSize);
               if(pszText != NULL)
                 {
                  if(GetWindowText(hEdit, pszText, dwBufferSize))
                     {
                         DWORD dwWritten;
                         if(WriteFile(hFile,pszText,dwTextLength,
                             &dwWritten, NULL )   )
                                    bSuccess = TRUE;
                     }
                  GlobalFree(pszText);
                 }
            }
         CloseHandle(hFile);
      }
   return bSuccess;
}

Zuerst öffnen Sie die Datei zum Schreiben mit CreateFile(). Falls diese Datei existiert, wird dies Überschreiben (CREATE_ALWAYS). Da in der Funktion SaveFileBox() das Flag OFN_OVERWRITEPROMPT verwendet wurde, wird vorher nochmals nachgefragt, ob die Datei wirklich überschrieben werden soll.

   if(hFile != INVALID_HANDLE_VALUE)
      {
         DWORD dwTextLength;

         dwTextLength = GetWindowTextLength(hEdit);
         if(dwTextLength > 0)

Zuerst wird überprüft, ob die Funktion CreateFile erfolgreich war. Anschließend ermitteln Sie mit der Funktion GetWindowTextLength() die Länge des Textes in Bytes.

              LPSTR pszText;
               DWORD dwBufferSize = dwTextLength + 1;
               pszText = GlobalAlloc(GPTR, dwBufferSize);
               if(pszText != NULL)
                 {
                  if(GetWindowText(hEdit, pszText, dwBufferSize))
                     {
                         DWORD dwWritten;
                         if(WriteFile(hFile,pszText,dwTextLength,
                             &dwWritten, NULL )   )
                                    bSuccess = TRUE;
                     }
                  GlobalFree(pszText);
                 }

Danach Allozieren wir wieder Speicher von Heap mit GlobalAlloc() und lesen den Text mit der Funktion GetWindowText() in den Puffer pszText ein. Der Handle hEdit ist der Fenster-Handle, auf den sich die Funktion beziehen soll und dwBufferSize ist die Anzahl Bytes, die eingelesen werden sollen. Anschließend können Sie mit der Funktion WriteFile() den Text pszText mit dwTextLength Bytes in den Handle hFile schreiben. In dwWritten befindet sich die Anzahl der erfolgreich geschriebenen Bytes. Den weiteren Verlauf dürften Sie wohl selbst nachvollziehen können.

Jetzt zurück zu WM_COMMAND und der nächsten Menüauswahl:

                  case ID_FORMAT_FONT:
                     {
                        FontChooseFont (hEdit);
                        FontSetFont (hEdit);
                     }
                   break;

Mit diesem Menüpunkt kann die Schriftart verändert werden. Initialisiert wurden die Fonts ja bereits mit der Funktion FontInitialize(hEdit) bei der Nachricht WM_CREATE. Darauf wurde bereits eingegangen. Jetzt rufen Sie die Funktion FontChooseFont() auf.

/* Schriftarten (Fonts) auswählen */
BOOL FontChooseFont (HWND hwnd)
{
     CHOOSEFONT cf ;

     ZeroMemory(&cf, sizeof(cf));

     cf.lStructSize    = sizeof (CHOOSEFONT) ;
     cf.hwndOwner      = hwnd ;
     cf.lpLogFont      = &logfont ;
     cf.Flags          = CF_INITTOLOGFONTSTRUCT |
                         CF_SCREENFONTS | CF_EFFECTS ;
     //Dialogbox:Schriftart auswählen
     return ChooseFont (&cf) ;
}

Dabei müssen Sie zuerst die Struktur CHOOSEFONT mit Informationen füttern, bevor Sie mit der Funktion ChooseFont() als Parameter die CHOOSEFONT-Struktur aufrufen und eine Dialog-Box zum Auswählen der Schriftart erzeugen. Da die CHOOSEFONT-Struktur wieder recht umfangreich ist und wir nicht alle Werte einzeln initialisieren wollen behelfen wir uns wieder mit dem ZeroMemory()-Trick und setzen erst mal alle Strukturvariablen auf 0. Anschließend setzen Sie die Strukturvariablen, welche zur Initialisierung nötig sind. Für mehr Details lesen Sie bitte die MSDN-Dokumentation. Am Ende der Funktion rufen Sie mit ChooseFont() die Dialogbox zum ändern der Schrift auf.

Dialogbox - Schriftart

Dialogbox - Schriftart

Je nachdem welche Schrift Sie ausgewählt haben, befindet sich diese jetzt in logfont. Zunächst wird nach der Auswahl der Schrift die Funktion FontSetFont() aufgerufen:

void FontSetFont (HWND hwndEdit)
{
     HFONT hFontNew ;
     RECT  rect ;

     hFontNew = CreateFontIndirect (&logfont) ;
     SendMessage (hwndEdit, WM_SETFONT, (WPARAM) hFontNew, 0) ;
     DeleteObject (hFont) ;
     hFont = hFontNew ;
     GetClientRect (hwndEdit, &rect) ;
     InvalidateRect (hwndEdit, &rect, TRUE) ;
}

Mit der Funktion CreateFontIndirect() welche die Struktur LOGFONT benötigt, erzeugen Sie eine logische Schrift. Den Zurückgegebenen Handle für die Schrift schicken Sie in der nächsten Zeile (SendMessage) mitsamt der Nachricht WM_SETFONT an Windows. Die alte Schrift wird mit DeleteObject() wieder freigegeben und in der nächsten Zeile übergeben Sie gleich die neue Schrift an hFont. Dann ermitteln Sie noch den rechteckigen Bereich (GetClientRect) des Editfeldes und zeichnen diesen mit der neuen Schriftart (InvalidateRect) neu.

Jetzt noch zu den letzten beiden Menüpunkten, welche beide recht ähnlich und einfach zu bewerkstelligen sind (Dank guter Vorarbeit in WM_CREATE mit der Funktion SetupChooseColor()).

                   case ID_FONT_COLOR:
                   if(ChooseColor(&choosecolor))
                      {
                         currentcolor = choosecolor.rgbResult;
                         textcolor = currentcolor;
                         InvalidateRect(hEdit, 0, TRUE);
                      }
                   break;
                   case ID_BK_COLOR:
                   if(ChooseColor(&choosecolor))
                      {
                         currentcolor = choosecolor.rgbResult;
                         backcolor = currentcolor;
                         InvalidateRect(hEdit, 0, TRUE);
                      }
                   break;

Hier rufen Sie jeweils die Funktion ChooseColor() für die Auswahl der Farbe auf. Anschließend übergeben Sie die Strukturvariable rgbResult von CHOOSECOLOR an currentcolor. Diese Variable übergeben Sie wiederum an, je nach Anwendungsfall, textcolor oder backcolor. Anschließend rufen Sie die Funktion InvalidRect() auf und die Nachricht WM_CTLCOLOREDIT tut Ihr Übriges.

Weiter mit Kapitel 9: Drucken            zum Inhaltsverzeichnis