[url=file:///I:/华奇英书海/编程语言/index.htm][/url]
普通的Windows程序
这个程序使用类封装了Windows API。
Controller-- 窗口过程和对象之间的桥梁。
View-- Windows程序的输出封装。
Canvas-- 封装了不同的设备描述符和事件,你可以去使用它们。
Model-- 工作者,你的程序的大脑。从不处理窗口。
注意: 这是一个Win32程序——它将运行在Windows95及WindowsNT下。
注意: _set_new_handler是Microsoft特定的。如果你使用的是其它的编译器,只要移去这行代码。依照当前的C++标准,new操作符无论如何都应该抛弃。
注意: 旧的编译器在模板方面可能有问题。你可以直接调用Get/SetWindowLong来代替Win[Get/Set]Long模板。实例,调用以下的来代替
Controller * pCtrl = WinGetLong (hwnd);
你可以调用
Controller * pCtrl = reinterpret_cast<Controller *> (::GetWindowLong (hwnd, GWL_USERDATA));
让我们从WinMain开始,我们建立一个窗口类及我们的程序的最顶层窗口。我在两个类中封装了这些动作:WinClass和WinMaker。如果在那我们的程序已经运行了一个实例,WinClass可以告诉我们。当这样的事发生时,我们的例子,将简单的激活程序先前的实例后结束。当你只想让你的程序在同一时间运行一个实例时,你应该这样做。
一旦顶层窗口被成功的建立,我们开始消息循环。在这时我们通过调用TranslateMessage来处理快捷键。这是因为我们的程序的菜单条目可以使用Alt+键的组合来访问。
另外这个程序有趣的是我们不能使用很长的字符串去命名我们的资源——我们使用数字标识。即使是API调用的字符串,象窗口类型名或标题,我们都存贮在字符串资源中,通过标识符来访问。你们的Windows开发环境中多数有一个资源编辑器让你去建立图标,菜单,及字符串资源,给它们分配适当的数字标识符。这些标识符的符号名存贮在一个引审了的头文件中——我们程序去调用它resource.h。
常量,ID_MAIN是为实例的主程序引入的图标 (在同一资源中有一大一小),主菜单,及窗口类型句的字符串。ID_CAPTION是窗口标题字符串。
int WINAPI WinMain
(HINSTANCE hInst, HINSTANCE hPrevInst,
char * cmdParam, int cmdShow)
{
_set_new_handler (& NewHandler);
// 使用异常帮助调试你的程序
// 防止异常事件
try
{
// 建立顶层窗口类
TopWinClass topWinClass (ID_MAIN, hInst, MainWndProc);
// 正在运行这个程序的实例吗?
HWND hwndOther = topWinClass.GetRunningWindow ();
if (hwndOther != 0)
{
::SetForegroundWindow (hwndOther);
if (::IsIconic (hwndOther))
::ShowWindow (hwndOther, SW_RESTORE);
return 0;
}
topWinClass.Register ();
// 建立顶层窗口
ResString caption (hInst, ID_CAPTION);
TopWinMaker topWin (topWinClass, caption);
topWin.Create ();
topWin.Show (cmdShow);
// 主消息循环
MSG msg;
int status;
while ((status = ::GetMessage (&msg, 0, 0, 0)) != 0)
{
if (status == -1)
return -1;
::TranslateMessage (&msg);
::DispatchMessage (&msg);
}
return msg.wParam;
}
catch ( WinException e )
{
char buf [50];
wsprintf (buf, "%s, Error %d", e.GetMessage (), e.GetError ());
::MessageBox (0, buf, "Exception", MB_ICONEXCLAMATION | MB_OK);
}
catch (...)
{
::MessageBox (0, "Unknown", "Exception", MB_ICONEXCLAMATION | MB_OK);
}
return 0;
}
让我们一起看一下WinClass类。它封装了被WNDCLASSEX调用的窗口定义结构,为它的所有字段提供了合理的默认值。它来源于一个WinSimpleClass模板类,你可以使用去封装一些固定的窗口类型(象按钮,列表视)
我倘若有一个方法的例子可以不考虑默认。例如,SetBgSysColor可以改变默认的窗口的客户区的背景色。方法SetResIcons从资源中装入适当的图标,并把它们附在窗口类上。这些图标将在当时显示在主窗口和Windows的任务栏中。
TopWinClass来由于WinClass的方法的使用。它同样分配菜单到顶层窗口类中。
class WinSimpleClass
{
public:
WinSimpleClass (char const * name, HINSTANCE hInst)
: _name (name), _hInstance (hInst)
{}
WinSimpleClass (int resId, HINSTANCE hInst);
char const * GetName () const { return _name.c_str (); }
HINSTANCE GetInstance () const { return _hInstance; }
HWND GetRunningWindow ();
protected:
HINSTANCE_hInstance;
std::string_name;
};
WinSimpleClass::WinSimpleClass (int resId, HINSTANCE hInst)
: _hInstance (hInst)
{
ResString resStr (hInst, resId);
_name = resStr;
}
HWND WinSimpleClass::GetRunningWindow ()
{
HWND hwnd = ::FindWindow (GetName (), 0);
if (::IsWindow (hwnd))
{
HWND hwndPopup = ::GetLastActivePopup (hwnd);
if (::IsWindow (hwndPopup))
hwnd = hwndPopup;
}
else
hwnd = 0;
return hwnd;
}
class WinClass: public WinSimpleClass
{
public:
WinClass (char const * className, HINSTANCE hInst, WNDPROC wndProc);
WinClass (int resId, HINSTANCE hInst, WNDPROC wndProc);
void SetBgSysColor (int sysColor)
{
_class.hbrBackground = reinterpret_cast<HBRUSH> (sysColor + 1);
}
void SetResIcons (int resId);
void Register ();
protected:
void SetDefaults ();
WNDCLASSEX _class;
};
WinClass::WinClass (char const * className, HINSTANCE hInst, WNDPROC wndProc)
: WinSimpleClass (className, hInst)
{
_class.lpfnWndProc = wndProc;
SetDefaults ();
}
WinClass::WinClass (int resId, HINSTANCE hInst, WNDPROC wndProc)
: WinSimpleClass (resId, hInst)
{
_class.lpfnWndProc = wndProc;
SetDefaults ();
}
void WinClass::SetDefaults ()
{
// 提供合理的默认值
_class.cbSize = sizeof (WNDCLASSEX);
_class.style = 0;
_class.lpszClassName = GetName ();
_class.hInstance = GetInstance ();
_class.hIcon = 0;
_class.hIconSm = 0;
_class.lpszMenuName = 0;
_class.cbClsExtra = 0;
_class.cbWndExtra = 0;
_class.hbrBackground = reinterpret_cast<HBRUSH> (COLOR_WINDOW + 1);
_class.hCursor = ::LoadCursor (0, IDC_ARROW);
}
void WinClass::SetResIcons (int resId)
{
_class.hIcon = ::LoadIcon (_class.hInstance, MAKEINTRESOURCE (resId));
// 可以使用LoadImage从同样的资源中装入小图标
_class.hIconSm = reinterpret_cast<HICON> (
::LoadImage (
_class.hInstance,
MAKEINTRESOURCE (resId),
IMAGE_ICON,
::GetSystemMetrics (SM_CXSMICON),
::GetSystemMetrics (SM_CYSMICON),
LR_SHARED));
}
void WinClass::Register ()
{
if (::RegisterClassEx (&_class) == 0)
throw WinException ("Internal error: RegisterClassEx failed.");
}
class TopWinClass: public WinClass
{
public:
TopWinClass (int resId, HINSTANCE hInst, WNDPROC wndProc);
};
TopWinClass::TopWinClass (int resId,
HINSTANCE hInst, WNDPROC wndProc)
: WinClass (resId, hInst, wndProc)
{
SetResIcons (resId);
_class.lpszMenuName = MAKEINTRESOURCE (resId);
}
一旦窗口类在系统被注册,你可以建立任意个你想要的这个类的窗口。他们将,当然,他们同享这个类注册的过程。稍后我们将可以了解在过程内窗口的不同实例的区别。
WinMaker类的工作非常像WinClass。它的构造函数提供了切合实际的默认值,可以通过调用细节方法去覆盖。一旦任何事都设置了,你调用Create方法去建立一个窗口,及调用Show方法去显示它。注意,在调用Create的瞬间,你的窗口过程被WM_CREATE消息调用。
顶层窗口使用TopWinMaker类建立,提供了适当的风格和标题。
class WinMaker
{
public:
WinMaker (WinClass & winClass);
operator HWND () { return _hwnd; }
void AddCaption (char const * caption)
{
_windowName = caption;
}
void AddSysMenu () { _style |= WS_SYSMENU; }
void AddVScrollBar () { _style |= WS_VSCROLL; }
void AddHScrollBar () { _style |= WS_HSCROLL; }
void Create ();
void Show (int nCmdShow = SW_SHOWNORMAL);
protected:
WinClass & _class;
HWND _hwnd;
DWORD _exStyle; // 扩展窗口风格
char const * _windowName; // 指向窗口名字的指针p
DWORD _style; // 窗口风格
int _x; // 窗口的水平位置
int _y; // 窗口的升起位置
int _width; // 窗口宽
int _height; // 窗口高
HWND _hWndParent; // 父窗口或所有者的句柄
HMENU _hMenu; // 菜单的句柄,或子窗口的标识符
void * _data; // 指向窗口创造数据
};
WinMaker::WinMaker (WinClass & winClass)
: _hwnd (0),
_class (winClass),
_exStyle (0), // 扩展窗口风格
_windowName (0), // 指向窗口的名字
_style (WS_OVERLAPPED), // 窗口风格
_x (CW_USEDEFAULT), // 窗口的水平位置
_y (0), // 窗口的升起位置
_width (CW_USEDEFAULT), // 窗口宽
_height (0), // 窗口高
_hWndParent (0), // 父窗口或所用者的句柄
_hMenu (0), // 指向菜单,或子窗口的标识符
_data (0) // 指向窗口创造数据
{
}
void WinMaker::Create ()
{
_hwnd = ::CreateWindowEx (
_exStyle,
_class.GetName (),
_windowName,
_style,
_x,
_y,
_width,
_height,
_hWndParent,
_hMenu,
_class.GetInstance (),
_data);
if (_hwnd == 0)
throw WinException ("Internal error: Window Creation Failed.");
}
void WinMaker::Show (int nCmdShow)
{
::ShowWindow (_hwnd, nCmdShow);
::UpdateWindow (_hwnd);
}
// 制造顶层重叠带标题的窗口
TopWinMaker::TopWinMaker ((WinClass & winClass, char const * caption)
: WinMaker (winClass)
{
_style = WS_OVERLAPPEDWINDOW | WS_VISIBLE;
_windowName = caption;
}
在我们开始下一步前,这有一个小的类。WinException抛出任何时刻的Windows API的失败。它需取回的Windows错误代码。(顺便,使用FormatMessage很容易的转换错误代码到字符串。)
ResString类是对你的应用程序的字符串资源中贮存的字符串的简单的封装。
// exception类:贮存消息和错误代码
class WinException
{
public:
WinException (char* msg)
: _err (::GetLastError()), _msg(msg)
{}
DWORD GetError() const { return _err; }
char const * GetMessage () const { return _msg; }
private:
DWORD _err;
char * _msg;
};
// 内存溢出:抛出异常
int NewHandler (size_t size)
{
throw WinException ( "Out of memory" );
return 0;
}
class ResString
{
enum { MAX_RESSTRING = 255 };
public:
ResString (HINSTANCE hInst, int resId);
operator char const * () { return _buf; }
private:
char _buf [MAX_RESSTRING + 1];
};
ResString::ResString (HINSTANCE hInst, int resId)
{
if (!::LoadString (hInst, resId, _buf, MAX_RESSTRING + 1))
throw WinException ("Load String failed");
}
Controller是一个特别的窗口实例的强健的系统。它建立了窗口,贮存了它,并在最后销毁了它。在它的控制内你可以放置一些对特定窗口实例的描述信息。在通常,控件器有一个视(通过在窗口的表面绘制进行交互)并且它有权使用model。它相当于你的应用程序的大脑(在这,MVC或Model-View-Controller通过Smalltalk程序师被调用)。
如果,这常常发生,你的应用程序只有一个最顶层的窗口,你可以在它的控制中立即插入model。这单一化的资源管理,这是以控件器与model的紧密联系的。在大的项目中应该避免这样的联接——首选在控制器里使用一个指向model的指针。
更多的controller方法为了它们操作的利益需要一个指向窗口的句柄。这个句柄是每一个窗口消息通过的句柄,但它只是一次性的简单的贮存它到controller对象里,随时都可以使用它。记住——在窗口实例(因为窗口的句柄)和controller对象之间有一对一的通信。
class Controller
{
public:
Controller(HWND hwnd, CREATESTRUCT * pCreate);
~Controller ();
void Size (int x, int y);
void Paint ();
void Command (int cmd);
private:
HWND _hwnd;
Model _model;
View _view;
};
窗口过程是一个窗口应用程序的主交换器。你不能在你的程序窗口中调用它!每当某件有意义的事件发生,Windows发送条消息给你的程序。这条消息被你的窗口过程传递。你可以处理它,或传递它到默认的窗口过程。
窗口过程被给定消息指定的窗口调用的。这个句柄是独一无二的一个协调窗口实例的内部窗口数据。它发生时,我们可以访问这些数据结构,及使用它去贮存一些实例指定的数据。这是典型的安全的访问结构的途径。顺便,这个结构的GWL_USERDATA成员是所有窗口允许给出的。包含消息框,对话框及平滑按钮。
template <class T>
inline T WinGetLong (HWND hwnd, int which = GWL_USERDATA)
{
return reinterpret_cast<T> (::GetWindowLong (hwnd, which));
}
template <class T>
inline void WinSetLong (HWND hwnd, T value, int which = GWL_USERDATA)
{
::SetWindowLong (hwnd, which, reinterpret_cast<long> (value));
}
每当Windows调用我们的窗口过程时,我们应该首先找回它的控制对象。记住,在这可能几个窗口共享相同的窗口过程,我们应该为每个窗口分别控制。当我们被调用后怎么样知道去使用哪个控制?我们通过找出窗口的句柄。在这个句柄里我们贮存了指向这个特定窗口的控制器,使用Win[Set/Get]Long技巧。
窗口过程首先被WM_CREATE消息调用。在那时我们建立了控制器对象并初始化了带有窗口句柄的它,且通过调用CREATESTRUCT指定了数据结构。一旦我们有了控制器,我们贮存指向在当前hwnd的标签下相应的内部窗口的数据结构。在下次窗口过程被调用,带有一个不同与WM_CREATE的消息,我们只要简单的取回指向我们控制器的指针,使用hwnd。
这个支持是容易的。窗口过程解释消息参数及调用控制器的适当模块。
LRESULT CALLBACK WndProc
(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
Controller * pCtrl = WinGetLong<Controller *> (hwnd);
switch (message)
{
case WM_CREATE:
// 不得不在此捕捉异外错误!
try
{
pCtrl = new Controller (hwnd,
reinterpret_cast<CREATESTRUCT *> (lParam));
WinSetLong<Controller *> (hwnd, pCtrl);
}
catch (WinException e)
{
::MessageBox (hwnd, e.GetMessage(), "Initialization",
MB_ICONEXCLAMATION | MB_OK);
return -1;
}
catch (...)
{
::MessageBox (hwnd, "Unknown Error", "Initialization",
MB_ICONEXCLAMATION | MB_OK);
return -1;
}
return 0;
case WM_SIZE:
pCtrl->Size (LOWORD(lParam), HIWORD(lParam));
return 0;
case WM_PAINT:
pCtrl->Paint ();
return 0;
case WM_COMMAND:
pCtrl->Command (LOWORD (wParam));
return 0;
case WM_DESTROY:
WinSetLong<Controller *> (hwnd, 0);
delete pCtrl;
return 0;
}
return ::DefWindowProc (hwnd, message, wParam, lParam);
}
在这个简单的例子中执行了少许的控制器方法。构造函数为稍后的使用不得不记住窗口的句柄,析构函数不得不发送离开消息,Size方法传递它的参数到View,等等,我们将讨论关于描绘窗口。目前,注意到控制器准备与View去一起描绘。
Controller::Controller (HWND hwnd, CREATESTRUCT * pCreate)
:_hwnd (hwnd),
_model ("Generic")
{
}
Controller::~Controller ()
{
::PostQuitMessage(0);
}
void Controller::Size (int cx, int cy)
{
_view.SetSize (cx, cy);
}
void Controller::Paint ()
{
// prepare the canvas and let View do the rest
PaintCanvas canvas (_hwnd);
_view.Paint (canvas, _model);
// Notice: The destructor of PaintCanvas called automatically!
}
当用户选择了菜单条目之一时,产生WM_COMMAND消息窗口过程被调用。适当的控制方法当足于命命令id分配命令。当你利用你的资源编辑器建立了你的菜单时,你为每个菜单条目选取了这些标识id。它们被贮存在适当的头文件里(可能在resource.h里) ,不得不被包含到控制器的源文件中。
我们的菜单仅仅包含三个条目,它们的标识id为IDM_EXIT,IDM_HELP和IDM_ABOUT。响应IDM_ABOUT时对话框被显示,它也是利用资源编辑器建立的,给定的标识id为IDD_ABOUT。它的对话框过程是AboutDlgProc。
最后,为了显示一个对话框我们需指定应用程序实例的句柄。标准的途径是利用应用程序的hwnd访问内部窗口数据结构取回它。
// 菜单命令处理
void Controller::Command (int cmd)
{
switch (cmd)
{
case IDM_EXIT:
::SendMessage (_hwnd, WM_CLOSE, 0, 0L);
break;
case IDM_HELP:
::MessageBox (_hwnd, "Go figure!",
"Generic", MB_ICONINFORMATION | MB_OK);
break;
case IDM_ABOUT:
{
// 通过HWND取得实例句柄
HINSTANCE hInst = WinGetLong<HINSTANCE> (_hwnd, GWL_HINSTANCE);
::DialogBox (hInst,
MAKEINTRESOURCE (IDD_ABOUT),
_hwnd,
AboutDlgProc);
}
break;
}
}
视对象通常贮存客户范围的尺寸。它们随着控制器过程的WM_SIZE消息更新。首先WM_SIZE消息是在窗口创造期间和WM_PAINT消息前发送的,因此我们可以在描绘调用时安全的呈现已知客户范围的尺寸。
图形输出到窗口是通过适当的Canvas对象的方法完成的。倘若,我们打印的文本是原型是从客户范围的边缘绘制一个10个象素的垂直行。
class View
{
public:
void SetSize (int cxNew, int cyNew)
{
_cx = cxNew;
_cy = cyNew;
}
void Paint (Canvas & canvas, Model & model);
protected:
int _cx;
int _cy;
};
void View::Paint (Canvas & canvas, Model & model)
{
canvas.Text (12, 1, model.GetText(), model.GetLen());
canvas.Line (10, 0, 10, _cy);
}
canvas对象封装了Windows所谓的设备描述符。我们的Canvas非常的简单,我们只要知道怎样打印文本和绘制行,但你的Canvas可以有很多的方法去做创造性的事情。我们将在下一个指南里告诉关于Canvas的更多。
class Canvas
{
public:
operator HDC () { return _hdc; }
void Line ( int x1, int y1, int x2, int y2 )
{
::MoveToEx (_hdc, x1, y1, 0);
::LineTo (_hdc, x2, y2);
}
void Text (int x, int y, char const * buf, int cBuf)
{
::TextOut ( _hdc, x, y, buf, cBuf );
}
void Char (int x, int y, char c)
{
::TextOut (_hdc, x, y, & c, 1);
}
protected:
// 保护构造函数:你不能建造
// 一个Canvas对象,但你可
// 以从它的来源构造对象。
Canvas (HDC hdc): _hdc (hdc) {}
HDC _hdc;
};
你建造的canvas用来响应WM_PAINT消息到特定种类。它们通过调用BeginPaint获得设备描述符并通过调用EndPaint来释放。PAINTSTRUCT包含关于哪部分用户范围应该被写的附加信息,上当我们忽视这种详细资料,但如果你严肃的关注性能,你应该学习关于它的更多。
// canvas的具体的实例。
// 在WM_PAINT消息后建立这个对象。
class PaintCanvas: public Canvas
{
public:
// Constructor obtains the DC
PaintCanvas (HWND hwnd)
: Canvas (::BeginPaint (hwnd, & _paint)),
_hwnd (hwnd)
{}
// 析构函数释放DC
~PaintCanvas ()
{
::EndPaint(_hwnd, & _paint);
}
protected:
PAINTSTRUCT _paint;
HWND _hwnd;
};
CopyRight by VCROAD©2000-2001 All Right Reserve