Элемент управления - Сплиттер (Splitter control) - ОС и разработчик - Shelek
Доброе время суток.
В этой статье представлен класс CSplitter1153 (далее - компонент или просто сплиттер), написанный в среде VC++6 с использованием библиотеки MFC. Класс нашего компонента произведён от класса CStatic. Предназначен сплиттер для динамического изменения пользователем размера и положения элементов управления (производных от класса CWnd) на диалоговой форме. Сплиттер, как элемент управления, располагается на любом объекте, класс которого произведён от CWnd (если не вдаваться в крайности , то на объектах типа CDialog, CDialogBar, CFormView и так далее). Также на нашем сплиттере можно отображать полосу прогресса. Код класса открыт, можете делать с ним что угодно.
Исходный код компонента представлен в 2 файлах (их можно взять из файлов проекта, который находится по ссылке в конце статьи):

Splitter1153.h заголовочный файл класса компонента.
Splitter1153.cpp файл реализации класса компонента.



Тестовый проект для этой статьи, как уже было сказано, можно найти по ссылке в конце статьи. Но представим, что проект ещё не создан. Здесь будет описано, как производится вставка компонента в проект и работа с ним.
Создаём тестовый проект SplitterTest (MFC , Dialog-based). Копируем в папку с проектом файлы класса компонента, описанные выше. Добавляем файлы также в дерево файлов проекта. После этого рекомендуется удалить из папки проекта файл "имя_проекта.clw" (можно даже не закрывая студию). Затем в студии нажать Ctrl+W для того, чтобы обновилось дерево классов.
Описываемый класс является производным от MFC-класса CStatic. Поэтому, чтобы поместить компонент на диалоговое окно в режиме редактирования ресурсов, надо положить на диалог контрол CStatic из стандартной палитры конторолов. Затем надо добавляем для статика связанный с ним член-переменную: удерживая Ctrl, дважды щелкаем по статику, пишем имя переменной m_..... , в окне Category -> выбираем Control, в окне Variable Type -> выбираем наш класс CSplitter1153. Также не забываем добавить в заголовочный файл диалога перед описание класса диалога строчку:
Код:
#include "Splitter1153.h"

Тут надо отметить следующий момент. Сплиттер может быть горизонтальный или вертикальный. Когда CStatic кладётся на форму , это никак не предопределено, это будет задано в свойствах объекта в программе. Тем не менее, можно для наглядности придать статику форму , близкую к желаемой. Поскольку границу у статика (когда в нем нет текста) в редакторе ресурсов не видно, то для того, чтоб сделать статик заметным можно вписать, например, "= = = " (чередование "=" и пробелов) по всей длине статика. В вертикальном статике, за счёт наличия пробелов, "=" расположатся на всех строчках и равномерно распределятся по высоте.
У сплиттера имеется два размера - "основной" и "не основной" . Для горизонтального основной - это высота. Для вертикального - ширина. Контрол следит только за своим основным размером, а второй размер остаётся такой, какой был установлен в редакторе ресурсом. Конечно, его можно изменить в процессе работы программы методом CWnd::MoveWindow() . Величина основной размера может задаваться через метод контрола.

Итак, для примера создадим два сплиттера - горизонтальный и вертикальный. Зададим идентификаторы IDC_SP_H (для горизонтального) и IDC_SP_V (для вертикального). Добавляем для них связанные переменные класса CSplitter1153 с именами m_IDC_SP_H и m_IDC_SP_V соответственно (как описано выше - удерживая Ctrl, дважды щелкаем). Поставьте в свойствах статиков следующие галочку Notify. Если не поставить галочку, то контрол не будет реагировать на щелчки мышью. Не забываем добавить в заголовочный файл диалога перед описанием класса диалога строчку:
Код:
#include "Splitter1153.h"
Компилируем проект (нет ли ошибок ?) и сохраняем.

Нам понадобятся ещё некоторые элементы управления, которые будут участвовать в демонстрации. Набросаем на форму несколько элементов, скажем Edit , которые будут, собственно, "двигаться" сплиттерами. Дадим им ID == IDC_EDIT1 , IDC_EDIT2, IDC_EDIT3, IDC_EDIT4.
Теперь вся наша форма выглядит примерно так:


IDC_EDIT1






IDC_EDIT2





IDC_EDIT3




IDC_EDIT4

То есть вертикальный сплиттер делит форму на 2 части. Справа - элементы IDC_EDIT3 и IDC_EDIT4 , слева - горизонтальный сплиттер, над которым - IDC_EDIT1 , а под - IDC_EDIT2.

Теперь надо создать объект каждого сплиттера. Если бы главное окно было дитём от CView, то создание нужно было бы делать в методе OnInitialUpdate(). В диалоге создание производим в методе OnInitDialog() (обработчик сообщения WM_INITDIALOG).
Создаётся сплиттер методом Create(...) :
Код:
BOOL CSplitter1153::Create(
CWnd* pParent,
bool bHorizontal,
bool bAutoArrowEnable)


В методе OnInitDialog(...) пишем:

Код:
//создание горизонтального сплиттера
m_IDC_SP_H.Create(this,true,true);

//создание вертикального сплиттера
m_IDC_SP_V.Create(this,false,true);

Пол дела уже сделано. Можете скомпилировать и запустить проект. Убедитесь, что уже можно передвигать сплиттеры мышкой. Обратите внимание, что "не основной" размер остался таким, как вы указали в редакторе ресурсов. Как мы и указали в Create(...), автоматически появляется двухсторонняя стрелка-курсор.
Если попытаться задвинуть сплиттер за край родительского окна, он будет против этого и остановится у самого края. Однако, если начать двигать край самого родительского окна, то есть риск оставить сплиттер за краем... Как с этим бороться, будет рассказано ниже (метод OnWindowPosChanged(...) родительского окна ).

Прежде чем продолжить дальше, рассмотрим обработку сообщений от сплиттера. Сообщений немного - всего одно. Конечно же вы можете добавить и новые сообщения, если это потребуется...
IDmess_OnChangePos Пользователь перемещает сплиттер

Для реализации обработки сообщений добавляем в главный диалог виртуальную функцию OnNotify(...).
В конце файла Splitter1153.h приведён пример, как обрабатывать сообщения от нашего контрола. Код вставляется в OnNotify(...) родительского окна:

Код:
//обработка сообщений от сплиттера CSplitter1153
{{{
//ЗДЕСЬ УКАЗЫВАЕТСЯ ИДЕНТИФИКАТОР СПЛИТТЕРА
BEGINMAP_for_CSplitter1153(IDC_xxxxxxx)
SWITCH_for_CSplitter1153
{
case pSP->IDmess_OnChangePos:
{
//...
}
break;

//case...
}
ENDMAP_for_CSplitter1153_and_return1_if_processed
}}}

Пояснения:
1) IDC_xxxxxxx - идентификатор контрола-сплиттера в ресурсах.
2) Добавьте свой код блоках case обработчиков.
3) Если сообщение не было предназначено для контрола, идентификатор которого указан в enum{...}, то станет выполнятся код функции OnNotify(), расположенный далее "}}}". Если же сообщение обработано, то произойдёт выход из OnNotify() со значением 1 ("сообщение обработано").

В блоках case обработчиков доступны следующие переменные :
CSplitter1153 *pSP; Указатель на объект контрола
const NMHDR* pNMHDR; указатель на структуру NMHDR


С обработкой сообщений вроде разобрались.
Итак, как бороться с уползанием сплиттера за край родительского окна? Добавим в тестовый диалог диалог обработчик сообщения WM_WINDOWPOSCHANGED (посылается окну, когда меняется его размер) , и напишем две строки - корректировку положения сплиттеров.
Код:
void CSplitterTestDlg::OnWindowPosChanged(WINDOWPOS FAR* lpwndpos)
{
CDialog::OnWindowPosChanged(lpwndpos);

//корректировака только сплиттеров
m_IDC_SP_H.RefreshCurrentRectFromParent();
m_IDC_SP_V.RefreshCurrentRectFromParent();
}

Запустите программу. Обратите внимание, как теперь ведут себя сплиттеры, если при изменении размеров диалога они оказываются возле самого края диалога - сплиттеры сдвигаются. Однако, они помнят последнюю позицию, заданную им мышью (или методом CSplitter1153::MoveWindow_SetTheMinimumMainCoordinate(...). Обратите внимание на этот момент - если вы хотите программно, а не мышью, переместить сплиттер, то кроме MoveWindow(), новую минимальную координату "основного размера" надо указать в упомянутом методе MoveWindow_SetTheMinimumMainCoordinate. ) . Если начать увеличивать размер диалога, сплиттеры будут стремиться вернуться к прежнему положению.
Теперь добавим в диалог метод RefreshControlsPositions() , в котором происходит перемещение всех элементов управления в соответствии с текущим положением сплиттеров. Этот метод нужно вызвать из обработчиков сообщения IDmess_OnChangePos всех сплиттеров, а также в методе OnWindowPosChanged(...) (вообще - после любого перемещения сплиттеров) :

Код:
BOOL CSplitterTestDlg::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
//обработка сообщений от сплиттера CSplitter1153
{{{
...
case pSP->IDmess_OnChangePos:
{
RefreshControlsPositions();
}
break;
...
}}}

//обработка сообщений от сплиттера CSplitter1153
{{{
...
case pSP->IDmess_OnChangePos:
{
RefreshControlsPositions();
}
break;
...
}}}
...
}

void CSplitterTestDlg::OnWindowPosChanged(WINDOWPOS FAR* lpwndpos)
{
CDialog::OnWindowPosChanged(lpwndpos);

// //корректировака только сплиттеров
// m_IDC_SP_H.RefreshCurrentRectFromParent();
// m_IDC_SP_V.RefreshCurrentRectFromParent();

//передвижение всех контролов диалога
RefreshControlsPositions();
}

В методе RefreshControlsPositions все вычисления производятся в абсолютных координатах, а перемещение каждого подвижного элемента управления (только кнопки в нашем примере не перемещаются) производится макросом def_MW, который переводит прямоугольник в координаты клиентской области перед вызовом MoveWindow(). Переменная nTopBorder задаёт верхний отступ. Происходит следующее
1) Определяются максимальные и минимальные координаты клиентской области
2) В соответствии с режимом перемещения окошек (кнопка "Режим размещения" на диалоге) задаём положения сплиттеров и их отступы.
3) Добываются прямоугольники сплиттеров - rH и rV (нужны будут для дальнейших расчётов)
4) В соответствии с режимом перемещения окошек размещаем все окошки CEdit.

Все подробности описаны в комментариях к коду в проекте.

На диалоге расположены кнопки и переключатели для демонстрации некоторых возможностей сплиттеров.
1) показ полосы прогресса. В проекте изменение значения полосы прогресса сделано по сообщению WM_MOUSEMOVE. При перемещении указателя мыши по диалогу полоски прогресса уменьшаются. (удобно смотреть при "режиме размещения" , когда справа есть чистая область диалога)
2) режим размещения - переключение между двумя (из, конечно же, множества возможных) вариантами размещения окошек
3) невидимые границы - режим "невидимых" сплиттеров
4) передвижение сплиттеров программно - демонстрация работы метода MoveWindow_SetTheMinimumMainCoordinate(...))

Ну вот , в принципе, и всё. Кстати - в заголовочном файле содержится много комментариев о назначении методов и переменных , а в файле реализации прокомментировано внутреннее устройство методов.

Как и всегда надеюсь, что мой компонент окажется полезен :)

Весь тестовый проект (вместе с файлами класса, они лежат в папке проекта) можно найти здесь.


Автор: Алексей1153
Information
  • Posted on 01.02.2010 00:35
  • Просмотры: 2392