ResString caption(hInst, ID_CAPTION);
TopWinMaker topWin(caption, ID_MAIN, hInst);
topWin.Create();
topWin.Show(cmdShow);
В начале мы регистрируем классы. Верхний оконный класс связан с его оконной процедурой WndProcMain, которую мы рассмотрим через мгновение. Два дочерних подокна совместно используют тот же самый класс окна, связанный с WndProcPane. Затем регистрируется наш собственный класс сплиттера (мы скоро увидем его код). В заключение, создается и отображается верхнее окно. Дочерние окна создаются динамически во время инициализации родительского окна.
Приведем оконную процедуру верхнего окна.
LRESULT CALLBACK WndProcMain(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
Controller* pCtrl = GetWinLong(hwnd);
switch (message) {
case WM_CREATE:
try {
pCtrl = new Controller(hwnd, reinterpret_cast(lParam));
SetWinLong(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(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.
Читать дальше