Приведем оконную процедуру верхнего окна.
LRESULT CALLBACK WndProcMain(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
Controller* pCtrl = GetWinLong<Controller *>(hwnd);
switch (message) {
case WM_CREATE:
try {
pCtrl = new Controller(hwnd, reinterpret_cast<CREATESTRUCT *>(lParam));
SetWinLong<Controller*>(hwnd, pCtrl);
} catch (char const* msg) {
MessageBox(hwnd, msg, "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 MSG_MOVESPLITTER:
pCtrl->MoveSplitter(wParam);
return 0;
case WM_DESTROY:
SetWinLong<Controller*>(hwnd, 0);
delete pCtrl;
return 0;
}
return ::DefWindowProc(hwnd, message, wParam, lParam);
}
Имеем обычную оконную процедуру за исключением одного сообщения: MSG_MOVESPLITTER. Это — наше собственное, определяемое пользователем сообщение, которое послано сплиттером его родительскому окну. Но сначала давайте взглянем на контроллер главного окна.
class Controller {
public:
Controller(HWND hwnd, CREATESTRUCT* pCreat);
~Controller();
void Size(int cx, int cy);
void MoveSplitter(int x);
private:
enum { splitWidth = 8 }; // width of splitter
// User Interface
HWnd _hwnd;
//Main controller window
HWnd _leftWin;
HWnd _rightWin;
HWnd _splitter;
int _splitRatio; // in per cent
int _cx;
int _cy;
};
Контроллер содержит дескриптор своего окна, двух дочерних подокон, и окна сплиттера. Он также сохраняет текущий коэффициент разбиения, в процентах.
Конструктор контроллера отвечает за создание дочерних окон.
Controller::Controller(HWND hwnd, CREATESTRUCT * pCreat) : _hwnd (hwnd), _leftWin (0), _rightWin (0), _splitter (0), _splitRatio (50) {
// Create child windows
{
ChildWinMaker leftWinMaker(IDC_PANE, _hwnd, ID_LEFT_WINDOW);
leftWinMaker.Create();
_leftWin.Init(leftWinMaker);
leftWinMaker.Show();
}
{
ChildWinMaker rightWinMaker(IDC_PANE, _hwnd, ID_RIGHT_WINDOW);
rightWinMaker.Create();
_rightWin.Init(rightWinMaker);
rightWinMaker.Show();
}
Splitter::MakeWindow(_splitter, _hwnd, ID_SPLITTER);
}
Когда пользователь перемещает полоску расщепителя, родитель получает сообщение MSG_MOVESPLITTER . Параметр wParam содержит новое расстояние полосы расщепителя от левого края родительского окна. В ответ на такое сообщение, родитель должен также изменить размеры дочерних подокон и переместить расщепитель. Он делает это, вызывая метод Size.
void Controller::MoveSplitter(int x) {
_splitRatio = x * 100 / _cx;
if (_splitRatio < 0) _splitRatio = 0;
else if (_splitRatio > 100) _splitRatio = 100;
Size(_cx, _cy);
}
Вообще же, Size вызывается всякий раз, когда пользователь изменяет размеры основного окна, но тот код который вытолько что видели, мы вызываем также тогда, когда сплиттер перемещается.
void Controller::Size(int cx, int cy) {
_cx = cx;
_cy = cy;
int xSplit = (_cx * _splitRatio) / 100;
if (xSplit < 0) xSplit = 0;
if (xSplit + splitWidth >= _cx) xSplit = _cx - splitWidth;
_splitter.MoveDelayPaint(xSplit, 0, splitWidth, cy);
_leftWin.Move(0, 0, xSplit, cy);
_rightWin.Move(xSplit+splitWidth, 0, cx-xSplit-splitWidth, cy);
_splitter.ForceRepaint ();
}
Обратите внимание на используемый здесь важный прием. Мы перемещаем расщепитель, но задерживаем его перерисовку до изменения его обоих подокон, расположенных слева и справа. Эта методика устраняет некоторое неприятное смазывание изображения.
Выше была представлена реализация клиентского кода. Теперь, я надеюсь, Вам будет интересно увидеть реализацию сплиттера.
Прежде всего нам приятно объединить зависимые функции в пространство имен. Вам видны вызовы Splitter::RegisterClass и Splitter::MakeWindow. Splitter в этих именах — это namespace.
namespace Splitter {
void RegisterClass(HINSTANCE hInst);
void MakeWindow(HWnd& hwndSplitter /* out */, HWnd hwndParent, int childId);
};
Ниже приводится реализация этих функций.
LRESULT CALLBACK WndProcSplitter(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
void Splitter::RegisterClass(HINSTANCE hInst) {
WinClassMaker splitterClass(WndProcSplitter, "RsSplitterClass", hInst);
splitterClass.SetSysCursor(IDC_SIZEWE);
splitterClass.SetBgSysColor(COLOR_3DFACE);
splitterClass.Register();
}
void Splitter::MakeWindow(HWnd& hwndSplitter, HWnd hwndParent, int childId) {
ChildWinMaker splitterMaker("RsSplitterClass", hwndParent, childId);
splitterMaker.Create(); hwndSplitter.Init(splitterMaker);
splitterMaker.Show();
}
Курсор мыши IDC_SIZEWE мы связываем с классом расщепителя — это стандартная, «направленная с запада на восток», двунаправленная стрелка. Мы также устанавливаем фоновую кисть к COLOR_3DFACE.
Оконная процедура расщепителя имеет дело с созданием/разрушением расщепителя, прорисовкой и перемещением мыши.
LRESULT CALLBACK WndProcSplitter(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
SplitController* pCtrl = GetWinLong<SplitController*>(hwnd);
switch (message) {
case WM_CREATE:
try {
pCtrl = new SplitController(hwnd, reinterpret_cast<CREATESTRUCT *>(lParam));
SetWinLong<SplitController*>(hwnd, pCtrl);
} catch (char const * msg) {
MessageBox(hwnd, msg, "Initialization", MB_ICONEXCLAMATION | MB_OK);
return -1;
} catch (...) {
MessageBox(hwnd, "Unknown Error", "Initialization", MB_ICONEXCLAMATION | MB_OK);