摘录于《Windows程序(第5版,珍藏版).CHarles.Petzold 著》P425
Windows 最初发行时的主要目标之一就是提供一种标准化的用户界面。对于公用菜单项来说,这一目标实现得很快。几乎所有的软件制造商都采用了 Alt-File-Open 组合来打开文件。但是,真正用来打开文件的对话框却经常很不一样。
从 Windows 3.1 开始,这一问题的解决方案开始出现。这一方案被称作“公用对话框库”(common dialogue box library)。这个库包括了一些函数,可以用来激活打开和存储文件、查找和替换、选择颜色、选择字体(所有这些我都讲在本章中演示)以及打印(我将在第 13 章中演示)的对话框。
在使用这些函数时,基本上要初始化一个结构的一些字段并将该结构的指针传递给公用对话框库的某个函数。该函数会创建并显示相应对话框。当用户关闭对话框时,函数将控制权返还给程序,然后程序可以从此前传递给函数的结构中获取信息。
需要在所有用到公用对话框库的 C 源文件中包括 COMMDLG.H 头文件。关于公用对话框的帮助信息,可以在/Plat-form SDK/User Interface Services/User Input/Common Dialog Box Library 中找到。
11.3.1 完善 POPPAD
当我们在第 10 章为 POPPAD 程序加入菜单时,并没有实现集中标准菜单项。现在我们有了充分的准备可以实现 POPPAD 中打开文件、读取文件、存储文件的程序逻辑。在此过程中,我们还将添加选择字体和查找并替换文本的程序逻辑。
/*--------------------------------------------------------
POPPAD.C -- Popup Editor
(c) Charles Petzold, 1998
--------------------------------------------------------*/
#include <windows.h>
#include <commdlg.h>
#include "resource.h"
#define ID_EDIT 1
#define UNTITLED TEXT ("(untitled)")
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM);
BOOL CALLBACK AboutDlgProc (HWND, UINT, WPARAM, LPARAM);
// Functions in POPFILE.C
void PopFileInitialize (HWND);
BOOL PopFileOpenDlg (HWND, PTSTR, PTSTR);
BOOL PopFileSaveDlg (HWND, PTSTR, PTSTR);
BOOL PopFileRead (HWND, PTSTR);
BOOL PopFileWrite (HWND, PTSTR);
// Functions in POPFIND.C
HWND PopFindFindDlg (HWND);
HWND PopFindReplaceDlg (HWND);
BOOL PopFindFindText (HWND, int *, LPFINDREPLACE);
BOOL PopFindReplaceText (HWND, int *, LPFINDREPLACE);
BOOL PopFindNextText (HWND, int *);
BOOL PopFindValidFind (void);
// Functions in POPFONT.C
void PopFontInitialize (HWND);
BOOL PopFontChooseFont (HWND);
void PopFontSetFont (HWND);
void PopFontDeinitialize (void);
// Functions in POPPRNT.C
BOOL PopPrntPrintFile(HINSTANCE, HWND, HWND, PTSTR);
// Globar variables
static HWND hDlgModeless;
static TCHAR szAppName[] = TEXT("PopPad");
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
HACCEL hAccel;
HWND hwnd;
MSG msg;
WNDCLASS wndclass;
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = WndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon(hInstance, szAppName);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wndclass.lpszMenuName = szAppName;
wndclass.lpszClassName = szAppName;
if (!RegisterClass(&wndclass))
{
MessageBox(NULL, TEXT("This program requires Windows NT!"),
szAppName, MB_ICONERROR);
return 0;
}
hwnd = CreateWindow(szAppName, NULL,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, szCmdLine);
ShowWindow(hwnd, iCmdShow);
UpdateWindow(hwnd);
hAccel = LoadAccelerators(hInstance, szAppName);
while (GetMessage(&msg, NULL, 0, 0))
{
if (hDlgModeless == NULL || !IsDialogMessage(hDlgModeless, &msg))
{
if (!TranslateAccelerator(hwnd, hAccel, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
return msg.wParam;
}
void DoCaption(HWND hwnd, TCHAR * szTitleName)
{
TCHAR szCaption[64 + MAX_PATH];
wsprintf(szCaption, TEXT("%s - %s"), szAppName,
szTitleName[0] ? szTitleName : UNTITLED);
SetWindowText(hwnd, szCaption);
}
void OkMessage(HWND hwnd, TCHAR * szMessage, TCHAR * szTitleName)
{
TCHAR szBuffer[64 + MAX_PATH];
wsprintf(szBuffer, szMessage, szTitleName[0] ? szTitleName : UNTITLED);
MessageBox(hwnd, szBuffer, szAppName, MB_OK | MB_ICONEXCLAMATION);
}
short AskAboutSave(HWND hwnd, TCHAR * szTitleName)
{
TCHAR szBuffer[64 + MAX_PATH];
int iReturn;
wsprintf(szBuffer, TEXT("Save current changes in %s?"),
szTitleName[0] ? szTitleName : UNTITLED);
iReturn = MessageBox(hwnd, szBuffer, szAppName, MB_YESNOCANCEL | MB_ICONQUESTION);
if (iReturn == IDYES)
if (!SendMessage(hwnd, WM_COMMAND, IDM_FILE_SAVE, 0))
iReturn = IDCANCEL;
return iReturn;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static BOOL bNeedSave = FALSE;
static HINSTANCE hInst;
static HWND hwndEdit;
static int iOffset;
static TCHAR szFileName[MAX_PATH], szTitleName[MAX_PATH];
static UINT messageFindReplace;
int iSelBeg, iSelEnd, iEnable;
LPFINDREPLACE pfr;
switch (message)
{
case WM_CREATE:
hInst = ((LPCREATESTRUCT)lParam)->hInstance;
// Craete the edit control child window
hwndEdit = CreateWindow(TEXT("edit"), NULL,
WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL |
WS_BORDER | ES_LEFT | ES_MULTILINE |
ES_NOHIDESEL | ES_AUTOHSCROLL | ES_AUTOVSCROLL,
0, 0, 0, 0,
hwnd, (HMENU)ID_EDIT, hInst, NULL);
SendMessage(hwndEdit, EM_LIMITTEXT, 32000, 0L);
// Initialize common dialog box stuff
PopFileInitialize(hwnd);
PopFontInitialize(hwndEdit);
messageFindReplace = RegisterWindowMessage(FINDMSGSTRING);
DoCaption(hwnd, szTitleName);
return 0;
case WM_SETFOCUS:
SetFocus(hwndEdit);
return 0;
case WM_SIZE:
MoveWindow(hwndEdit, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
return 0;
case WM_INITMENUPOPUP:
switch (lParam)
{
case 1: // Edit menu
// Enable Undo if edit control can do it
EnableMenuItem((HMENU)wParam, IDM_EDIT_UNDO,
SendMessage(hwndEdit, EM_CANUNDO, 0, 0L) ? MF_ENABLED : MF_GRAYED);
// Enable Paste if text is in the clipboard
EnableMenuItem((HMENU)wParam, IDM_EDIT_PASTE,
IsClipboardFormatAvailable(CF_TEXT) ? MF_ENABLED : MF_GRAYED);
// Enable Cut, Copy, and Del if text is selected
SendMessage(hwndEdit, EM_GETSEL, (WPARAM) &iSelBeg, (LPARAM)&iSelEnd);
iEnable = iSelBeg != iSelEnd ? MF_ENABLED : MF_GRAYED;
EnableMenuItem((HMENU)wParam, IDM_EDIT_CUT, iEnable);
EnableMenuItem((HMENU)wParam, IDM_EDIT_COPY, iEnable);
EnableMenuItem((HMENU)wParam, IDM_EDIT_CLEAR, iEnable);
break;
case 2: // Search menu
// Enable Find, Next, and Replace if modeless
// dialogs are not already active
iEnable = hDlgModeless == NULL ? MF_ENABLED : MF_GRAYED;
EnableMenuItem((HMENU)wParam, IDM_SEARCH_FIND, iEnable);
EnableMenuItem((HMENU)wParam, IDM_SEARCH_NEXT, iEnable);
EnableMenuItem((HMENU)wParam, IDM_SEARCH_REPLACE, iEnable);
break;
}
return 0;
case WM_COMMAND:
// Mesages from edit control
if (lParam && LOWORD(wParam) == ID_EDIT)
{
switch (HIWORD(wParam))
{
case EN_UPDATE:
bNeedSave = TRUE;
return 0;
case EN_ERRSPACE:
case EN_MAXTEXT:
MessageBox(hwnd, TEXT("Edit control out of space."),
szAppName, MB_OK | MB_ICONSTOP);
return 0;
}
break;
}
switch (LOWORD(wParam))
{
// Message from File menu
case IDM_FILE_NEW:
if (bNeedSave && IDCANCEL == AskAboutSave(hwnd, szTitleName))
return 0;
SetWindowText(hwndEdit, TEXT("\0"));
szFileName[0] = '\0';
szTitleName[0] = '\0';
DoCaption(hwnd, szTitleName);
bNeedSave = FALSE;
return 0;
case IDM_FILE_OPEN:
if (bNeedSave && IDCANCEL == AskAboutSave(hwnd, szTitleName))
return 0;
if (PopFileOpenDlg(hwnd, szFileName, szTitleName))
{
if (!PopFileRead(hwndEdit, szFileName))
{
OkMessage(hwnd, TEXT("Could not read file %s!"), szTitleName);
szFileName[0] = '\0';
szTitleName[0] = '\0';
}
}
DoCaption(hwnd, szTitleName);
bNeedSave = FALSE;
return 0;
case IDM_FILE_SAVE:
if (szFileName[0])
{
if (PopFileWrite(hwndEdit, szFileName))
{
bNeedSave = FALSE;
return 1;
}
else
{
OkMessage(hwnd, TEXT("Could not write file %s"), szTitleName);
return 0;
}
}
// fall through
case IDM_FILE_SAVE_AS:
if (PopFileSaveDlg(hwnd, szFileName, szTitleName))
{
DoCaption(hwnd, szTitleName);
if (PopFileWrite(hwndEdit, szFileName))
{
bNeedSave = FALSE;
return 1;
}
else
{
OkMessage(hwnd, TEXT("Could not write file %s"), szTitleName);
return 0;
}
}
return 0;
case IDM_FILE_PRINT:
if (!PopPrntPrintFile(hInst, hwnd, hwndEdit, szTitleName))
OkMessage(hwnd, TEXT("Could not print file %s"), szTitleName);
return 0;
case IDM_APP_EXIT:
SendMessage(hwnd, WM_CLOSE, 0, 0);
return 0;
// Messages from Edit menu
case IDM_EDIT_UNDO:
SendMessage(hwndEdit, WM_UNDO, 0, 0);
return 0;
case IDM_EDIT_CUT:
SendMessage(hwndEdit, WM_CUT, 0, 0);
return 0;
case IDM_EDIT_COPY:
SendMessage(hwndEdit, WM_COPY, 0, 0);
return 0;
case IDM_EDIT_PASTE:
SendMessage(hwndEdit, WM_PASTE, 0, 0);
return 0;
case IDM_EDIT_CLEAR:
SendMessage(hwndEdit, WM_CLEAR, 0, 0);
return 0;
case IDM_EDIT_SELECT_ALL:
SendMessage(hwndEdit, EM_SETSEL, 0, -1);
return 0;
// Message from Search menu
case IDM_SEARCH_FIND:
SendMessage(hwndEdit, EM_GETSEL, 0, (LPARAM) &iOffset);
hDlgModeless = PopFindFindDlg(hwnd);
return 0;
case IDM_SEARCH_NEXT:
SendMessage(hwndEdit, EM_GETSEL, 0, (LPARAM) &iOffset);
if (PopFindValidFind())
PopFindNextText(hwndEdit, &iOffset);
else
hDlgModeless = PopFindFindDlg(hwnd);
return 0;
case IDM_SEARCH_REPLACE:
SendMessage(hwndEdit, EM_GETSEL, 0, (LPARAM) &iOffset);
hDlgModeless = PopFindReplaceDlg(hwnd);
return 0;
case IDM_FORMAT_FONT:
if (PopFontChooseFont(hwnd))
PopFontSetFont(hwndEdit);
return 0;
// Messages from Help menu
case IDM_APP_HELP:
OkMessage(hwnd, TEXT("Help not yet implemented!"), TEXT("\0"));
return 0;
case IDM_APP_ABOUT:
DialogBox(hInst, TEXT("AboutBox"), hwnd, AboutDlgProc);
return 0;
}
break;
case WM_CLOSE:
if (!bNeedSave || IDCANCEL != AskAboutSave(hwnd, szTitleName))
DestroyWindow(hwnd);
return 0;
case WM_QUERYENDSESSION:
if (!bNeedSave || IDCANCEL != AskAboutSave(hwnd, szTitleName))
return 1;
return 0;
case WM_DESTROY:
PopFontDeinitialize();
PostQuitMessage(0);
return 0;
default:
// Process "Find-Replace" messages
if (message == messageFindReplace)
{
pfr = (LPFINDREPLACE)lParam;
if (pfr->Flags & FR_DIALOGTERM)
hDlgModeless = NULL;
if (pfr->Flags & FR_FINDNEXT)
if (!PopFindFindText(hwndEdit, &iOffset, pfr))
OkMessage(hwnd, TEXT("Text not found!"), TEXT("\0"));
if (pfr->Flags & FR_REPLACE || pfr->Flags & FR_REPLACEALL)
if (!PopFindReplaceText(hwndEdit, &iOffset, pfr))
OkMessage(hwnd, TEXT("Text not found!"), TEXT("\0"));
if (pfr->Flags & FR_REPLACEALL)
while (PopFindReplaceText(hwndEdit, &iOffset, pfr));
return 0;
}
break;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
BOOL CALLBACK AboutDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_INITDIALOG:
return TRUE;
case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDOK:
EndDialog(hDlg, 0);
return TRUE;
}
break;
}
return FALSE;
}
POPFILE.C
/*---------------------------------------------
POPFILE.C -- Popup Editor File Functions
-----------------------------------------------*/
#include <windows.h>
#include <commdlg.h>
static OPENFILENAME ofn;
void PopFileInitialize(HWND hwnd)
{
static TCHAR szFilter[] = TEXT("Text Files (*.TXT)\0*.txt\0")
TEXT("ASCII Files (*.ASC)\0*.asc\0")
TEXT("All File (*.*)\0*.*\0\0");
ofn.lStructSize = sizeof(OPENFILENAME);
ofn.hwndOwner = hwnd;
ofn.hInstance = NULL;
ofn.lpstrFilter = szFilter;
ofn.lpstrCustomFilter = NULL;
ofn.nMaxCustFilter = 0;
ofn.nFilterIndex = 0;
ofn.lpstrFile = NULL; // Set in Open and Close functions
ofn.nMaxFile = MAX_PATH;
ofn.lpstrFileTitle = NULL; // Set in Open and Close functions
ofn.nMaxFileTitle = MAX_PATH;
ofn.lpstrInitialDir = NULL;
ofn.lpstrTitle = NULL;
ofn.Flags = 0; // Set in Open and Close functions
ofn.nFileOffset = 0;
ofn.nFileExtension = 0;
ofn.lpstrDefExt = TEXT("txt");
ofn.lCustData = 0L;
ofn.lpfnHook = NULL;
ofn.lpTemplateName = NULL;
}
BOOL PopFileOpenDlg(HWND hwnd, PTSTR pstrFileName, PTSTR pstrTitleName)
{
ofn.hwndOwner = hwnd;
ofn.lpstrFile = pstrFileName;
ofn.lpstrFileTitle = pstrTitleName;
ofn.Flags = OFN_HIDEREADONLY | OFN_CREATEPROMPT;
return GetOpenFileName(&ofn);
}
BOOL PopFileSaveDlg(HWND hwnd, PTSTR pstrFileName, PTSTR pstrTitleName)
{
ofn.hwndOwner = hwnd;
ofn.lpstrFile = pstrFileName;
ofn.lpstrFileTitle = pstrTitleName;
ofn.Flags = OFN_OVERWRITEPROMPT;
return GetSaveFileName(&ofn);
}
BOOL PopFileRead(HWND hwndEdit, PTSTR pstrFileName)
{
BYTE bySwap;
DWORD dwBytesRead;
HANDLE hFile;
int i, iFileLength, iUniTest;
PBYTE pBuffer, pText, pConv;
// Open the file.
if (INVALID_HANDLE_VALUE ==
(hFile = CreateFile(pstrFileName, GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, 0, NULL)))
return FALSE;
// Get file size in bytes and allocate memory for read.
// Add an extra two bytes for zero termination.
iFileLength = GetFileSize(hFile, NULL);
pBuffer = (PBYTE)malloc(iFileLength + 2);
// Read file and put terminating zeros at end.
ReadFile(hFile, pBuffer, iFileLength, &dwBytesRead, NULL);
CloseHandle(hFile);
pBuffer[iFileLength] = '\0';
pBuffer[iFileLength + 1] = '\0';
// Test to see if the text is Unicode
iUniTest = IS_TEXT_UNICODE_SIGNATURE | IS_TEXT_UNICODE_REVERSE_SIGNATURE;
if (IsTextUnicode(pBuffer, iFileLength, &iUniTest))
{
pText = pBuffer + 2;
iFileLength -= 2;
if (iUniTest & IS_TEXT_UNICODE_REVERSE_SIGNATURE)
{
for (i = 0; i < iFileLength / 2; ++i)
{
bySwap = ((BYTE *)pText)[2 * i];
((BYTE *)pText)[2 * i] = ((BYTE *)pText)[2 * i + 1];
((BYTE *)pText)[2 * i + 1] = bySwap;
}
}
// Allocate memory for possibly converted string
pConv = (PBYTE)malloc(iFileLength + 2);
// If the edit control is not Unicode, convert Unicode text to
// non-Unicode (ie, in general, wide character).
#ifndef UNICODE
WideCharToMultiByte(CP_ACP, 0, (PWSTR)pText, -1, (LPSTR)pConv,
iFileLength + 2, NULL, NULL);
// If the edit control is Unicode, just copy the string
#else
lstrcpy((PTSTR)pConv, (PTSTR)pText);
#endif
}
else // the file is not Unicode
{
pText = pBuffer;
// Allocate memory for possibly converted string.
pConv = (PBYTE)malloc(2 * iFileLength + 2);
// If the edit control is Unicode, convert ASCII text.
#ifdef UNICODE
MultiByteToWideChar(CP_ACP, 0, pText, -1, (PTSTR)pConv,
iFileLength + 1);
// If not, just copy buffer
#else
lstrcpy((PTSTR)pConv, (PTSTR)pText);
#endif
}
SetWindowText(hwndEdit, (PTSTR)pConv);
free(pBuffer);
free(pConv);
return TRUE;
}
BOOL PopFileWrite(HWND hwndEdit, PTSTR pstrFileName)
{
DWORD dwBytesWritten;
HANDLE hFile;
int iLength;
PTSTR pstrBuffer;
WORD wByteOrderMark = 0xFEFF;
// Open the file, creating it if necessary
if (INVALID_HANDLE_VALUE ==
(hFile = CreateFile(pstrFileName, GENERIC_WRITE, 0,
NULL, CREATE_ALWAYS, 0, NULL)))
return FALSE;
// Get the number of characters in the edit control and allocate
// memory for them.
iLength = GetWindowTextLength(hwndEdit);
pstrBuffer = (PTSTR)malloc((iLength + 1) * sizeof(TCHAR));
if (!pstrBuffer)
{
CloseHandle(hFile);
return FALSE;
}
// If the edit control will return Unicode text, write the
// byte order mark to the file.
#ifdef UNICODE
WriteFile(hFile, &wByteOrderMark, 2, &dwBytesWritten, NULL);
#endif
// Get the edit buffer and write that out to the file.
GetWindowText(hwndEdit, pstrBuffer, iLength + 1);
WriteFile(hFile, pstrBuffer, iLength * sizeof(TCHAR), &dwBytesWritten, NULL);
if ((iLength * sizeof(TCHAR)) != (int)dwBytesWritten)
{
CloseHandle(hFile);
free(pstrBuffer);
return FALSE;
}
CloseHandle(hFile);
free(pstrBuffer);
return TRUE;
}
POPFIND.C
/*----------------------------------------------------------
POPFIND.C -- Popup Editor Search and Replace Functions
------------------------------------------------------------*/
#include <windows.h>
#include <commdlg.h>
#include <tchar.h> // for _tcsstr (strstr for Unicode & non-Unicode)
#define MAX_STRING_LEN 256
static TCHAR szFindText[MAX_STRING_LEN];
static TCHAR szReplText[MAX_STRING_LEN];
HWND PopFindFindDlg(HWND hwnd)
{
static FINDREPLACE fr; // must be static for modeless dialog!!
fr.lStructSize = sizeof(FINDREPLACE);
fr.hwndOwner = hwnd;
fr.hInstance = NULL;
fr.Flags = FR_HIDEUPDOWN | FR_HIDEMATCHCASE | FR_HIDEWHOLEWORD;
fr.lpstrFindWhat = szFindText;
fr.lpstrReplaceWith = NULL;
fr.wFindWhatLen = MAX_STRING_LEN;
fr.wReplaceWithLen = 0;
fr.lCustData = 0;
fr.lpfnHook = NULL;
fr.lpTemplateName = NULL;
return FindText(&fr);
}
HWND PopFindReplaceDlg(HWND hwnd)
{
static FINDREPLACE fr; // must be static for modeless dialog!!!
fr.lStructSize = sizeof(FINDREPLACE);
fr.hwndOwner = hwnd;
fr.hInstance = NULL;
fr.Flags = FR_HIDEUPDOWN | FR_HIDEMATCHCASE | FR_HIDEWHOLEWORD;
fr.lpstrFindWhat = szFindText;
fr.lpstrReplaceWith = szReplText;
fr.wFindWhatLen = MAX_STRING_LEN;
fr.wReplaceWithLen = MAX_STRING_LEN;
fr.lCustData = 0;
fr.lpfnHook = NULL;
fr.lpTemplateName = NULL;
return ReplaceText(&fr);
}
BOOL PopFindFindText(HWND hwndEdit, int * piSearchOffset, LPFINDREPLACE pfr)
{
int iLength, iPos;
PTSTR pstrDoc, pstrPos;
// Read in the edit document
iLength = GetWindowTextLength(hwndEdit);
if (NULL == (pstrDoc = (PTSTR)malloc((iLength + 1) * sizeof(TCHAR))))
return FALSE;
GetWindowText(hwndEdit, pstrDoc, iLength + 1);
// Search the document for the find string
pstrPos = _tcsstr(pstrDoc + *piSearchOffset, pfr->lpstrFindWhat);
free(pstrDoc);
// Return an error code if the string cannot be found
if (pstrPos == NULL)
return FALSE;
// Find the position in the document and the new start offset.
iPos = pstrPos - pstrDoc;
*piSearchOffset = iPos + lstrlen(pfr->lpstrFindWhat);
// Select the found text
SendMessage(hwndEdit, EM_SETSEL, iPos, *piSearchOffset);
SendMessage(hwndEdit, EM_SCROLLCARET, 0, 0);
return TRUE;
}
BOOL PopFindNextText(HWND hwndEdit, int * piSearchOffset)
{
FINDREPLACE fr;
fr.lpstrFindWhat = szFindText;
return PopFindFindText(hwndEdit, piSearchOffset, &fr);
}
BOOL PopFindReplaceText(HWND hwndEdit, int * piSerachOffset, LPFINDREPLACE pfr)
{
// Find the text
if (!PopFindFindText(hwndEdit, piSerachOffset, pfr))
return FALSE;
// Replace it
SendMessage(hwndEdit, EM_REPLACESEL, 0, (LPARAM)pfr->lpstrReplaceWith);
return TRUE;
}
BOOL PopFindValidFind(void)
{
return *szFindText != '\0';
}
POPFONT.C
/*----------------------------------------------------------
POPFONT.C -- Popup Editor Font Functions
------------------------------------------------------------*/
#include <windows.h>
#include <commdlg.h>
static LOGFONT logfont;
static HFONT hFont;
BOOL PopFontChooseFont(HWND hwnd)
{
CHOOSEFONT cf;
cf.lStructSize = sizeof(CHOOSEFONT);
cf.hwndOwner = hwnd;
cf.hDC = NULL;
cf.lpLogFont = &logfont;
cf.iPointSize = 0;
cf.Flags = CF_INITTOLOGFONTSTRUCT | CF_SCREENFONTS | CF_EFFECTS;
cf.rgbColors = 0;
cf.lCustData = 0;
cf.lpfnHook = NULL;
cf.lpTemplateName = NULL;
cf.hInstance = NULL;
cf.lpszStyle = NULL;
cf.nFontType = 0; // Returned from ChooseFont
cf.nSizeMin = 0;
cf.nSizeMax = 0;
return ChooseFont(&cf);
}
void PopFontInitialize(HWND hwndEdit)
{
GetObject(GetStockObject(SYSTEM_FONT), sizeof(LOGFONT),
(PTSTR)&logfont);
hFont = CreateFontIndirect(&logfont);
SendMessage(hwndEdit, WM_SETFONT, (WPARAM)hFont, 0);
}
void PopFontSetFont(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);
}
void PopFontDeinitialize(void)
{
DeleteObject(hFont);
}
POPPRNT0.C
/*---------------------------------------------------------------
POPPRNT0.C -- Popup Editor Printing Functions (dummy version)
-----------------------------------------------------------------*/
#include <windows.h>
BOOL PopPrntPrintFile(HINSTANCE hInst, HWND hwnd, HWND hwndEdit, PTSTR pstrTitleName)
{
return FALSE;
}
POPPAD.RC (节选)
// Microsoft Visual C++ generated resource script.
//
#include "resource.h"
/
//
// Dialog
//
ABOUTBOX DIALOG DISCARDABLE 32, 32, 180, 100
STYLE DS_MODALFRAME | WS_POPUP
FONT 8, "MS Sans Serif"
BEGIN
DEFPUSHBUTTON "OK", IDOK, 66, 80, 50, 14
ICON "POPPAD", IDC_STATIC, 7, 7, 20, 20
CTEXT "PopPad", IDC_STATIC, 40, 12, 100, 8
CTEXT "Popup Editor for Windows", IDC_STATIC, 7, 40, 166, 8
CTEXT "(c) Charles Petzold, 1998", IDC_STATIC, 7, 52, 166, 8
END
PRINTDLGBOX DIALOG DISCARDABLE 32, 32, 186, 95
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "PopPad"
FONT 8, "MS Sans Serif"
BEGIN
PUSHBUTTON "Cancel", IDCANCEL, 67, 74, 50, 14
CTEXT "Sending", IDC_STATIC, 8, 8, 172, 8
CTEXT "", IDC_FILENAME, 8, 28, 172, 8
CTEXT "to print spooler.", IDC_STATIC, 8, 48, 172, 8
END
/
//
// Menu
//
POPPAD MENU DISCARDABLE
BEGIN
POPUP "&File"
BEGIN
MENUITEM "&New\tCtrl+N", IDM_FILE_NEW
MENUITEM "&Open...\tCtrl+O", IDM_FILE_OPEN
MENUITEM "&Save\tCtrl+S", IDM_FILE_SAVE
MENUITEM "Save &As...", IDM_FILE_SAVE_AS
MENUITEM SEPARATOR
MENUITEM "&Print\tCtrl+P", IDM_FILE_PRINT
MENUITEM SEPARATOR
MENUITEM "E&xit", IDM_APP_EXIT
END
POPUP "&Edit"
BEGIN
MENUITEM "&Undo\tCtrl+Z", IDM_EDIT_UNDO
MENUITEM SEPARATOR
MENUITEM "Cu&t\tCtrl+X", IDM_EDIT_CUT
MENUITEM "&Copy\tCtrl+C", IDM_EDIT_COPY
MENUITEM "&Paste\tCtrl+V", IDM_EDIT_PASTE
MENUITEM "De&lete\tDel", IDM_EDIT_CLEAR
MENUITEM SEPARATOR
MENUITEM "&Select All", IDM_EDIT_SELECT_ALL
END
POPUP "&Search"
BEGIN
MENUITEM "&Find...\tCtrl+F", IDM_SEARCH_FIND
MENUITEM "Find &Next\tF3", IDM_SEARCH_NEXT
MENUITEM "&Replace...\tCtrl+R", IDM_SEARCH_REPLACE
END
POPUP "F&ormat"
BEGIN
MENUITEM "&Font...", IDM_FORMAT_FONT
END
POPUP "&Help"
BEGIN
MENUITEM "&Help", IDM_APP_HELP
MENUITEM "&About PopPad...", IDM_APP_ABOUT
END
END
/
//
// Accelerator
//
POPPAD ACCELERATORS DISCARDABLE
BEGIN
VK_BACK, IDM_EDIT_UNDO, VIRTKEY, ALT, NOINVERT
VK_DELETE, IDM_EDIT_CLEAR, VIRTKEY, NOINVERT
VK_DELETE, IDM_EDIT_CUT, VIRTKEY, SHIFT, NOINVERT
VK_F1, IDM_APP_HELP, VIRTKEY, NOINVERT
VK_F3, IDM_SEARCH_NEXT, VIRTKEY, NOINVERT
VK_INSERT, IDM_EDIT_COPY, VIRTKEY, CONTROL, NOINVERT
VK_INSERT, IDM_EDIT_PASTE, VIRTKEY, SHIFT, NOINVERT
"^C", IDM_EDIT_COPY, ASCII, NOINVERT
"^F", IDM_SEARCH_FIND, ASCII, NOINVERT
"^N", IDM_FILE_NEW, ASCII, NOINVERT
"^O", IDM_FILE_OPEN, ASCII, NOINVERT
"^P", IDM_FILE_PRINT, ASCII, NOINVERT
"^R", IDM_SEARCH_REPLACE, ASCII, NOINVERT
"^S", IDM_FILE_SAVE, ASCII, NOINVERT
"^V", IDM_EDIT_PASTE, ASCII, NOINVERT
"^X", IDM_EDIT_CUT, ASCII, NOINVERT
"^Z", IDM_EDIT_UNDO, ASCII, NOINVERT
END
/
//
// Icon
//
POPPAD ICON "POPPAD.ICO"
RESOURCE.H (节选)
// Microsoft Visual C++ 生成的包含文件。
// 供 PopPad3.rc 使用
//
#define IDC_FILENAME 1000
#define IDM_FILE_NEW 40001
#define IDM_FILE_OPEN 40002
#define IDM_FILE_SAVE 40003
#define IDM_FILE_SAVE_AS 40004
#define IDM_FILE_PRINT 40005
#define IDM_APP_EXIT 40006
#define IDM_EDIT_UNDO 40007
#define IDM_EDIT_CUT 40008
#define IDM_EDIT_COPY 40009
#define IDM_EDIT_PASTE 40010
#define IDM_EDIT_CLEAR 40011
#define IDM_EDIT_SELECT_ALL 40012
#define IDM_SEARCH_FIND 40013
#define IDM_SEARCH_NEXT 40014
#define IDM_SEARCH_REPLACE 40015
#define IDM_FORMAT_FONT 40016
#define IDM_APP_HELP 40017
#define IDM_APP_ABOUT 40018
POPPAD.ICO
为避免重复第 13 章的源代码,我在 POPPAD3.C 的菜单定义中加上了打印选项以及其他一些相关代码。
POPPAD.C 包含程序的所有基于源代码。POPFILE.C 中包含了激活打开文件和存储文件对话框的代码,以及文件 I/O 例程。POPFIND.C 包含了查找和替换文本的程序逻辑。POPFONT.C 包含了选择字体的程序逻辑。POPPRNT0.C 实现的功能不多,它将在第 13 章被 POPPRNT.C 所替换,并完成最终的 POPPAD 程序。
让我们先看一下 POPPAD.C。POPPAD.C 中维护了两个文件名字符串:第一个存储在 WndProc 中,名为 szFileName。它是一个含有驱动器名、路径名和文件名的带完整路径的文件的名称。第二个只是文件名本身,名为 szTitleName,它用在 POPPAD3 和 DoCaption 函数中,用来在窗口的标题栏中显示文件名。它还用在 OkMessage 和 AskAboutSave 函数中,用来向用户显示消息框。
POPFILE.C 中包含几个函数,它们用来显示打开文件和存储文件对话框,以及实现文件读/写。函数 GetOpenFileName 和 GetSaveFileName 用来显示对话框。这两个函数都使用了一个在 COMMDLG.H 中定义的、类型为 OPENFILENAME 的结构。在 POPFILE.C 中,该结构由一个全局变量 ofn 来标识。ofn 中的大多数字段都在 PopFileInitialize 函数中被初始化。该函数在 WndProc 处理 WM_CREATE 消息时被调用。
将 ofn 定义为静态全局变量有方便之处,因为 GetOpenFileName 和GetSaveFileName 会再改结构中返回一些信息,而这些信息会在后续调用的函数中用到。
尽管公用对话框有许多选择——包括设定自己的对话框模板并挂接入对话框过程——我在 POPFILE.C 中使用的打开文件和存储文件对话框却很简单。在 OPENFILENAME 结构中,我只设定了一下字段:lStructSize(结构的大小),hwndOwner(对话框的所有者),lpstrFilter(我不久将会解释),lpstrFile和nMaxFile(指向用来接收带完整路径的文件名的缓冲区的指针和该缓冲区的大小),lpstrFileTitle和nMaxFileTitle(文件名本身的缓冲区及其大小),Flags(对话框的选项设定),lpstrDefExt(当用户在对话框中输入文件名而没有指定文件名后缀时,所用的默认后缀名字符串)。
当用户在 File 菜单中选择 Open 时,POPPAD3 将调用 POPFILE 的 PopFileOpenDlg 函数,并传递如下参数:窗口句柄、文件名缓冲区指针和文件标题缓冲区指针。PopFileOpenDlg 用这些参数来设定 OPENFILENAME 结构中的 hwndOwner、lpstrFile 和 lpstrFileTitle 字段,并将 Flags 设定为 OFN_CREATEPROMPT,然后调用 GetOpenFileName 来显示大家所熟悉的对话框,如图 11-12 所示。
图 11-12 打开文件对话框
当用户关闭对话框时,GetOpenFileName 函数返回。OFN_CREATEPROMPT 标志使 GetOpenFileName 在所选文件不存在时显示一个消息框,用来提示用户是否需要创建该文件。
在对话框左下角的组合框中列出了所要显示的文件的类型。这就是所说的筛选器(filter)。用户可以通过选择组合框中的其他文件类型来改变筛选器。在 POPFILE.C 的 PopFileInitialize 函数中,我用变量 szFilter(字符串数组)定义了三种文件类型筛选器:后缀为 .TXT 的文本文件、后缀为 .ASC 的 ASCII 文件,以及所有文件。OPENFILENAME 结构的 lpstrFilter 字段值被设定为指向该数组中第一个字符串的指针。
如果用户在使用对话框时改变了筛选器,OPENFILENAME 中的 nFilterIndex 字段会随之改变。由于该结构被存储于静态变量中,所以下次激活对话框时筛选器会被设为上次选中的文件类型。
POPFILE.C 中的 PopFileSaveDlg 函数与之相似。它将 Flags 参数设为 OFN_OVERWRITEPROMT,并通过调用 GetSaveFileName 来激活存储文件对话框。OFN_OVERWRITEPROMT 标志使得当所选文件已经存在时,程序会显示一个消息框来询问用户是否覆盖此文件。
11.3.2 Unicode 文件的读/写操作
对于本书中的许多程序,你也许根本不会注意到 Unicode 和非 Unicode 版本的区别。举例来说,在 Unicode 版本的 POPPAD3 中,编辑控件维护使用了 Unicode 文本,所有公用对话框都使用 Unicode 文本字符串。当程序做查找或替换操作时,整个过程都是使用 Unicode 字符串,根本无需任何转换。
但是,POPPAD3 要进行文件读/写操作,这意味着该程序不是自我封闭的。如果 Unicode 版本的 POPPAD3 获取编辑缓冲区中的内容并将其写入磁盘,则文件将是 Unicode 的。如果非 Unicode 版本的 POPPAD3 读入该文件并将其放入编辑缓冲区中,其结果将是垃圾数据。同样的情况也会发生在用非 Unicode 版本写文件、用 Unicode 版本读文件时。
该问题的解决办法牵涉了标识和转换。首先,在 POPFILE.C 的 PopFileWrite 函数中,Unicode 版本的程序在文件的开始会写入 0xFEFF。这被定义为字节顺序标志,它表示此文本实际上含有 Unicode 文本。
其次,在 PopFileRead 函数中,程序使用 IsTextUnicode 函数来判断文件是否含有字节顺序标志。该函数甚至还会检查字节顺序标志是否是反序。这意味着此 Unicode 文本文件是在 Macintosh 或其他与 Intel 处理器字节顺序相反的机器上生成的。在这种情况下,每一对字节的顺序都是相反的。如果文件是 Unicode 的,而读取文件的是非 Unicode 版本的 POPPAD3,那么程序会用 WideCharToMultiChar 来转换文本,这其实是一个 wide-char-toANSI 函数(除非你用的是远东版本的 Windows)。只有转换之后,该文本才可以被放入编辑缓冲区。
同样的,如果文件是非 Unicode 的,而运行程序是 Unicode 版本的,则文本必须用 MultiBytesToWideChar 来转换。
11.3.3 改变字体
我们将在第 17 章详细介绍字体,但没有比用公用对话框函数来选择字体更好的了。
在处理 WM_CREATE 消息时,POPPAD 调用了 POPFONT.C 中的 PopFontInitialize 函数。该函数获取基于系统字体的 LOGFONT 结构,并由它创建一个字体,然后向编辑控件发送 WM_SETFONT 消息来设定新的字体。(尽管编辑控件的默认字体是系统字体,PopFontInitialize 函数还是为编辑控件创建了一个新字体。这是因为字体最终是会被删除的,而删除系统的备用字体是不明智的。)
当 POPPAD 收到关于程序字体选项的 WM_COMMAND 消息时,它会调用 PopFontChooseFont 函数。该函数会初始化 CHOOSEFONT 结构,然后通过调用 ChooseFont 来显示字体选择对话框。如果用户按下 OK 按钮, ChooseFont 函数会返回 TRUE。然后 POPPAD 会通过调用 PopFontSetFont 函数来为编辑控件设置新的字体。旧字体会被删除。
最后,在处理 WM_DESTROY 消息时,POPPAD 会调用 PopFontDeinitialize 函数来删除 PopFontSetFont 最后创建的字体。
11.3.4 查找和替换
公用对话框库还包含两个用来查找和替换文本的对话框函数。这两个函数(FindText 和 ReplaceText)用到了一个类型是 FINDREPLACE 的结构。在 POPFIND.C 文件中,有两个例程(PopFindFindDlg 和 PopFindReplaceDlg)会调用这些函数,该文件中还用两个函数来在编辑控件中查找和替换文本。
在使用查找和替换函数时有一些需要注意的事项。首先,它们使用的对话框是非模态对话框,这意味着当对话框在使用时消息循环应该调用 IsDialogMessage 函数。其次,传递给 FindText 和 ReplaceText 的 FINDREPLACE 结构必须是一个静态变量;因为是非模态对话框,所以函数将在对话框显示后返回而不是在销毁后才返回。而不管如何,对话框过程必须要能够继续访问该结构。
第三,在 FindText 和 ReplaceText 对话框显示时,它们与拥有它们的窗口通过一种特殊消息进行通信。这个消息的对应值可以通过调用 RegisterWindowMessage 函数(使用 FINDMSGSTRING 参数)来获取。这是在 WndProc 处理 WM_CREATE 消息时完成的,该消息值被存储在一个静态变量中。
在处理默认消息时,WNdProc 比较消息变量和 RegisterWindowMessage 函数的返回值。消息参数 lParam 是一个指向 FINDREPLACE 结构的指针,该结构的 Flags 字段表示用户是否已经在用对话框来查找或替换文本,或是正在关闭该对话框。POPPAD3 通过调用 POPFIND.C 中的 PopFindFindText 和 PopFindReplaceText 函数来实现查找和替换文本的功能。
11.3.4 只调用一个函数的 Windows 程序
到目前为止,我示范了两个可以查看所选颜色的程序:第 9 章中的 COLORS1 和本章中的 COLORS2。现在是示范 COLORS3 的时候了。这是一个只调用一个 Windows 函数的程序。
COLORS3 调用的唯一一个 Windows 函数是 ChooseColor,它是公用对话框库中的另一个函数。它用来显示如图 11-14 所示的对话框。颜色的选择与 COLORS1 和 COLORS2 中相似,但其交互性更强。
/*-----------------------------------------------
COLORS3.C -- Version using Common Dialog Box
------------------------------------------------*/
#include <windows.h>
#include <commdlg.h>
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static CHOOSECOLOR cc;
static COLORREF crCustColors[16];
cc.lStructSize = sizeof(CHOOSECOLOR);
cc.hwndOwner = NULL;
cc.hInstance = NULL;
cc.rgbResult = RGB(0x80, 0x80, 0x80);
cc.lpCustColors = crCustColors;
cc.Flags = CC_RGBINIT | CC_FULLOPEN;
cc.lCustData = 0;
cc.lpfnHook = NULL;
cc.lpTemplateName = NULL;
return ChooseColor(&cc);
}
图 11-14 COLORS3 的显示
ChooseColor 函数使用了一个类型为 CHOOSECOLOR 的结构和一个用来存储用户通过对话框选择的颜色的含有 16 个 DWORD 值的数组。如果 Flags 字段中设定了 CC_RGBINIT 标志,那么 rgbResult 字段可以被初始化为所要显示的颜色值。一般情况下,rgbResult 字段会被设置为用户选择的颜色值。
注意,Color 对话框的 hwndOwner 字段被设置为 NULL。当 ChooseColor 函数调用 DialogBox 来显示对话框时,DialogBox 的第三个参数也被设置为 NULL。这是完全合法的。这意味着该对话框不被任何窗口所拥有。对话框的标题会出现在 Windows 的任何列表中,并且对话框看上去与一般的窗口非常相似。
也可以在自己程序的对话框中使用这种技巧。Windows 程序完全可以只创建一个对话框,并在该对话框过程中完成所有操作。