Глава 16.


Эти разнообразные элементы управления

                                                         Чем сложнее действие, тем проще персонажи.

                                                                                                                   Карел Чапек

Библиотека MFC предоставляет набор классов, которые облегчают создание стандартных элементов управления Windows: кнопок различного вида, статического текста, поля редактирования текста, полос прокрутки, списка и комбинированного списка. В табл. 16.1 приведены названия классов и элементы управления, создаваемые этими классами. Иерархия классов, связанных с элементами управления, представлена на рис. 16.1.

Таблица 16.1. Классы MFC для создания стандартных элементов управления

Класс

Элемент управления

CStatic

Статический текст или изображение

CButton

Командные кнопки, срабатывающие по нажатию, переключатели и флажки

CBitmapButton

Кнопки с заданными рисунками для всех возможных состояний

CListBox

Список

CCheckListBox

Список элементов, реализованных в виде флажков

CDragListBox

Список с возможностью переноса его элементов при помощи мыши

CComboBox

Комбинированный блок списка и текстового поля

CComboBoxEx

Комбинированный список с возможностью непосредственной работы со списками изображений

CEdit

Текстовое поле

CScrollBar

Полоса прокрутки

Рис. 16.1. Место элементов управления в иерархии классов MFC

Создание элементов управления

Практически все элементы управления можно создать двумя способами:

Создание элементов управления в редакторе ресурсов

Элементы управления можно создавать во время создания шаблона блока диалога при помощи редактора ресурсов. При этом пользователь просто перетаскивает с помощью мыши необходимые элементы управления из панели с их изображениями в блок диалога, изменяя по своему желанию размеры, расположение и параметры выбранного элемента управления. Редактор ресурсов помещает описание нового элемента блока диалога в файл ресурсов. При реализации блока диалога вызовом функций Create или DoModal система Windows сама создает элементы управления и размещает их в этом блоке диалога, причем это верно для всех окон на базе шаблона блока диалога — модальных и немодальных блоков диалога и форматных представлений.

Создание элементов управления в тексте приложения

Элементы управления можно задать непосредственно в исходном тексте приложения. Как и другие объекты классов библиотеки MFC, инкапсулирующие визуальные объекты Windows, элементы управления создаются в два этапа: сначала определяется объект данного конкретного класса, а затем вызывается функция Create для реализации соответствующего элемента управления Windows и связывания его с объектом класса. Обычно для блока диалога это выполняется в функции OnlnitDialog, для обычного окна — в функции OnCreate, а для набора свойств — в одной из функций OnSetActive или OnlnitDialog соответствующей страницы.

Следующий пример показывает создание поля редактирования в тексте приложения.

Объект СEdit объявлен в декларации класса CMarsDialog.

class CMarsDialog : public CDialog 

{

...

protected:

CEdit m_edit;

...

public:

virtual BOOL OnlnitDialog();

...

};

Затем в функции OnlnitDialog задается прямоугольник, определяющий размеры и расположение данного объекта, после чего вызывается функция Create для создания поля редактирования Windows и связывания его с созданным объектом CEdit.

BOOL CMarsDialog::OnlnitDialog() 

{

CDialog::OnlnitDialog();

CRect rect(20, 20, 100, 50);

m_edit.Create(WS_CHILD |  WS_VISIBLE | WSJTABSTOP | 

ES_AUTOHSCROLL | WS_BORDER, rect, this, IDC_EDIT); 

m_edit.SetFocus();

...

return FALSE; 

}

После создания элемента управления функцией Create вызывается функция SetFocus для передачи фокуса ввода новому полю редактирования. Для реализации элементов управления можно в равной мере пользоваться как первым способом, используя редактор ресурсов (этот способ применяется чаще), так и вторым, описывая элементы управления непосредственно в тексте программы.

Единственное, что следует помнить — это то, что если элемент управления создается в блоке диалога, то он автоматически уничтожается, когда пользователь закрывает диалоговое окно, а если элемент управления создается программно внутри окна, то его необходимо уничтожать "вручную". Если он создан с помощью функции new, то, соответственно, для уничтожения необходимо вызывать функцию delete.

Теперь перейдем непосредственно к элементам управления.

 

Статические элементы управления

В случаях, когда требуется вывести текст (например, заголовок другого элемента управления или поясняющий текст) или добавить какие-либо дополнительные рисованные элементы, функции GDI, как правило, не используются — для этого в Windows существуют статические элементы управления.

Класс CStatic позволяет выводить на экран текстовые строки, различные закрашенные прямоугольники, значки, курсоры, битовые массивы и расширенные метафайлы. Часто они используются для объединения или разделения других элементов управления. Обычно статические элементы управления не требуют ввода и вывода информации, однако они могут уведомлять родительское окно о нажатии клавиши (в случае, если они созданы со стилем SS_NOTIFY).

Из всех возможностей мы рассмотрим только одну, которая требует написания некоторого кода.

Изменение цвета

Следующий пример иллюстрирует, как на странице набора свойств в ответ на сообщение WM_CTLCOLOR изменяется "кисть" для закрашивания фона статических элементов управления.

Для этого необходимо создать обработчик ON_WM_CTRLCOLOR. Проще всего воспользоваться мастером Class Wizard. Обработчик получает параметр, сообщающий тип элемента управления, для которого запрашиваются параметры рисования. В нашем случае задаем только другую "кисть", т. к. единственный статический элемент управления блока диалога не имеет текста, и достаточно лишь заменить цвет и стиль закрашивания фона.

...

BOOL CMyPropertyPage2::OnInitDialog()

 {

CPropertyPage::OnInitDialog();

m_brush.CreateHatchBrush(HS_CROSS, RGB(255, 255, 0));

return TRUE; 

 }

// Обработчик сообщения WM_CTLCOLOR.

 HBROSH CMyPropertyPage2::OnCtlColor (

CDC* pDC, CWnd* pWnd, UINT nCtlColor)

 {

// Получаем дескриптор текущей кисти страницы свойств

HBRUSH hbr = CPropertyPage::OnCtlColor(pDC, pWnd, nCtlColor);

// Заменяем "кисть" только для статических

// элементов управления

if {nCtlColor == CTLCObOR_STATIC) 

hbr = m__brush;

return hbr; 

}

В результате страница свойств будет иметь следующий вид (рис. 16.2). Обратите также внимание, что на странице представлены два статических элемента управления. С первым мы только что поработали, а второй — некоторый рисунок, и для его отображения я не привел ни одного фрагмента кода. Вполне достаточно оказалось сделать все определения в редакторе ресурсов: сначала создать ресурс BITMAP, импортировав его из заранее подготовленного рисунка, а затем выбрать его в качестве свойства статического элемента управления (рис. 16.3).

Рис. 16.2. Страница свойств со статическими элементами управления

Рис. 16.3. Статический элемент управления будет представлять собой рисунок

Мы не будем рассматривать элемент управления "кнопка", поскольку работа с ним элементарна, а пример реализации вы найдете и в этой главе, и в главе 14, где мы рассматривали работу с блоками диалога

 

Список

С помощью класса CListBox можно создать элемент управления Windows "список", представляющий собой прямоугольник с находящейся внутри последовательностью тестовых или пользовательских элементов, которые пользователь может пролистывать и выбирать. Список используется, например, для просмотра имен файлов на диске.

Бывают списки, позволяющие выбирать только одну запись (элемент) или одновременно несколько записей (списки множественного выбора). Когда пользователь выбирает запись в списке, она выделяется, и окно-список посылает родительскому окну извещение. Вообще, список посылает или, правильнее сказать, может посылать несколько извещений родительскому окну. Для всех них предусмотрены специальные макросы, добавляющие в карту сообщений окна функции обработки. Большинство уведомлений посылается родительскому окну тогда и только тогда, когда список является текущим элементом управления.

 

Изменение параметров списка

Не все параметры списка можно задать при помощи редактора ресурсов или функции Create. Далее приведены описания функций класса CListBox, использование которых позволяет настраивать список в соответствии с требованиями приложения.

Для изменения ширины списка предназначены следующие две функции (обратите внимание, что речь идет не о размерах окна, ограничивающего список, а о поле списка, только часть которого может быть видима "через" это окно):

int CListBox::GetHorizontalExtent ()

Возвращает ширину списка в пикселах, в пределах которой список может прокручиваться по горизонтали. Функцию можно использовать только для списков, имеющих горизонтальную полосу прокрутки.

void CListBox::SetHorizontalExtent (int cxExtent)

Устанавливает ширину списка по горизонтали (в пикселах), в пределах которой список может прокручиваться. Если размер окна списка меньше этого значения, то строки в списке можно прокручивать с помощью горизонтальной полосы прокрутки. Если окно списка такого же размера или больше, то горизонтальная полоса прокрутки делается невидимой. Для того чтобы список реагировал на SetHorizontalExtent, нужно, чтобы он был создан со стилем WSJHSCROLL Для списков, имеющих несколько столбцов, вместо этой функции следует использовать функцию CListBox::SetColumnWidth.

Для получения размера прямоугольника, который ограничивает текущую изображаемую запись списка, предназначена функция:

int CListBox::GetltemRect (

int nlndex,

LPRECT IpRect)

Параметр nlndex задает индекс записи, a IpRect является указателем на структуру RECT, возвращающую координаты прямоугольника, в который выводится запись списка. В случае ошибки возвращает LB_ERR.

Следующая пара функций позволяет получить и изменить высоту записей в списке:

int CListBox::GetltemHeight (int nlndex)

Возвращает высоту записи в списке в пикселах. Если список создан с использованием стиля LBS_OWNERDRAWVARIABLE, то функция возвращает высоту записи списка с указанным индексом (параметр nIndex). В противном случае возвращается LB_ERR.

int CListBox::SetltemHeight (

int nlndex,

DINT cyltemHeight)

Устанавливает высоту записи в списке, где nlndex задает индекс записи в списке. Этот параметр используется лишь в том случае, если список имеет стиль LBS_OWNERDRAWVARIABLE, иначе он должен быть равен нулю. Параметр cyltemHeight задает высоту записи в пикселах. Функция возвращает LB_ERR, если индекс или высота записи имеют неверное значение.

Для изменения ширины столбцов предназначена функция SetColumn Width.

void CListBox::SetColumnWidth (int cxWidth)

Устанавливает ширину столбцов (в пикселах) в списке, имеющем несколько столбцов (созданном со стилем LBS_MULTICOLUMN), где cxWidth задает ширину в пикселах всех столбцов.

 

Функции для работы с содержимым списка

После того как список был создан, необходимо его заполнить. Следующие функции предназначены для работы со строками списка. Они позволяют выполнить такие основные операции, как добавление и удаление строк из списка, вставку строк в определенное место в списке, поиск строк, удаление всех строк, а также заполнение списка имен файлов. К этой же группе относятся функции получения информации о конкретных элементах списка и сопоставления с ними дополнительных данных. Последняя возможность позволяет создавать списки объектов самых разных типов, возлагая при этом работу по отображению их описания на Windows и исключая таким образом необходимость реализации функций отображения элементов, что особенно важно для самоотображаемых элементов управления.

Начнем с наиболее часто используемой функции, которая добавляет строку текста в качестве элемента в список:

int CListBox::AddString (LPCTSTR Ipszltem)

Возвращает индекс новой строки в списке. Параметр Ipszltem указывает на завершающуюся нулевым символом строку, которая должна быть добавлена к списку.

Если список не имеет стиля LBS_SORT, то строка будет добавлена в конец списка. Если же при создании списка этот стиль был задан, то после добавления строки список будет заново отсортирован. Если же список имеет стиль LBS_SORT, но не имеет стиля LBS_HASSTRINGS, то список будет сортироваться путем многократного вызова функции CListBox::CompareItem, которую требуется переопределить, создав соответствующий производный класс.

Списки, не имеющие стиля LBS_SORT, могут использовать функцию InsertString для вставки строкового элемента в заданную позицию:

int CListBox::InsertString (

int nlndex,

LPCTSTR Ipszltem)

Возвращает индекс новой строки в списке. Если список имеет стиль LBS_SORT, то значение параметра nIndex, который и определяет желаемую позицию строки, может не соответствовать возвращаемому значению функции; что вполне естественно. Если nIndex = —1, то строка добавляется в конец списка. Параметр Ipszltem является указателем на оканчивающуюся нулевым символом строку, которая должна быть вставлена в список.

Если необходимо добавить в список большое количество строк (например, больше 100), то лучше воспользоваться следующей функцией:

int CListBox::InitStorage ( int nltems, UINT nBytes)

Позволяет выделить память для хранения строк списка, определяемую с помощью значений параметров nltems (количество записей) и nBytes (размер памяти в байтах на один элемент). Эта функция ускоряет инициализацию списка с большим количеством записей. При ее применении выполнение функций CListBox::AddString, CListBox::InsertString и CListBox::Dir занимает минимальное время. Если при вызове функции памяти будет запрошено меньше, чем нужно для размещения всех строк, то используется обычное выделение памяти для записей, которым не хватило выделенного объема.

Как и любой список вообще, "список" как элемент управления Windows и вместе с ним класс CListBox требуют наличия, наряду с функциями добавления/заполнения элементов, и функции удаления/очистки.

int CListBox::DeleteString (UINT nlndex)

Удаляет из списка элемент, определяемый индексом (параметр nlndex), и возвращает количество оставшихся элементов. Значение LB_ERR возвращается, если заданный индекс записи на удаление больше числа записей в списке.

void CListBox::ResetContent ()

Удаляет все элементы из списка.

Следующие две функции предназначены для поиска строки в списке, совпадающей, начиная с первого символа, с заданной строкой.

int CListBox::FindString

int nStartAfter, 

LPCTSTR Ipszltem)

Осуществляет только поиск строки в списке.

int CListBox::SelectString (

int nStartAfter,

 LPCTSTR Ipszltem)

Ищет и выделяет найденную строку, причем если строка найдена, то она выделяется и, если нужно, список прокручивается так, чтобы строка стала видна.

Обе функции возвращают индекс найденной строки в списке или LB_ERR в случае неудачи. Параметр nStartAfter должен содержать индекс строки списка, находящейся перед строкой, с которой будет начат поиск. Если достигнут конец списка, то поиск будет продолжен с начала списка до строки, заданной в nStartAfter. Если значение параметра nStartAfter = -1, то поиск будет осуществляться по всему списку от его начала. Параметр Ipszltem является указателем на оканчивающуюся нулевым символом строку, которая должна быть найдена, причем поиск идет без учета регистра.

Вторая из описанных функций не может использоваться для списков, созданных со стилем LBS_MULTIPLESEL.

Кроме того, для поиска строк существует еще одна функция, но в отличие от предыдущих она осуществляет поиск строки в списке, точно совпадающей с заданной строкой:

int CListBox::FindStringExact (

int nlndexStart, 

LPCTSTR IpszFind)

Назначение параметра nlndexStart, несмотря на отличающееся название, полностью аналогично назначению параметра nStartAfter предыдущих функций. Если список создан как самоотображаемый, но без стиля LBS_ HASSTRINGS, то функция FindStringExact попытается согласовать 32-битовое значение, связанное с элементом, со значением параметра IpszFind (указатели в Win32 API 32-разрядные).

 

Операции над элементами списка

Функции этой группы предназначены для получения информации о конкретных элементах списка или, наоборот, для выделения элементов списка и связывания с ними 32-разрядных значений и указателей.

Естественно, чтобы работать со списком любого вида, требуется знать, сколько элементов он содержит. Для этого в классе CListBox предназначена функция GetCount.

int CListBox::GetCount ()

Возвращает либо количество элементов в списке, либо LB_ERR в случае ошибки.

Для получения информации о том, выбран ли определенный элемент или нет, следует использовать функцию GetSel, которая может работать как со списками, позволяющими выбирать одну запись (списки одиночного выбора), так и со списками, позволяющими выбирать сразу несколько записей (списки множественного выбора).

Для получения индекса текущей выбранной записи и, наоборот, задания конкретной записи в качестве выбранной предназначены функции GetCurSel, SetCurSel, GetSelltems, SetSel.

 Примечание 

Выбор элемента приведет при необходимости к прокручиванию списка таким образом, чтобы выбранный элемент стал видимым в окне списка.

Первые две функции предназначены, прежде всего, для списков одиночного выбора, а вторые две — для списков множественного выбора. Однако нет никаких ограничений по использованию функций со стороны библиотеки MFC и Windows, и не удивляйтесь, если функции будут вести себя неадекватно ситуации. Например, функция GetCurSel возвращает значение LB_ERR как в случае отсутствия выбранного элемента, так и в случае ее вызова для списка множественного выбора, независимо от того, есть фактически выбранные элементы или нет. Наиболее интересная из перечисленных — функция GetSelltems:

int CListBox::GetSelltems (

int nMaxItems, 

LPINT rglndex)

Позволяет получить индексы строк, которые выбраны в данный момент в списке.Это значение может быть больше, чем размерность буфера (параметр rglndex),заданная параметром nMaxItems. При использовании этой функции для спискаодиночного выбора она возвратит значение LB_ERR.

В добавление к описанным функциям, класс CListBox содержит функцию SetltemRange, позволяющую выбирать несколько записей, следующих подряд.

Для получения содержимого текстовых элементов списка класс CListBox предусматривает целый набор функций. Во-первых, для надежных программ, которые не полагаются на предположение о длине запрашиваемых данных, существует функция GetTextLen, возвращающая длину строкового элемента в байтах, исключая завершающий нуль-символ. Во-вторых, для получения собственно текста следует использовать функцию GetText, предоставляющую буфер для копирования в него символов.

На первый взгляд может показаться, что элемент управления "список" предназначен, в основном, для текстовых элементов или для визуализации в виде списка произвольных данных, как правило, временного использования. В отличие от чисто "программных" классов-контейнеров, в реализации описываемых списков основное внимание уделено визуализации и они обладают меньшими возможностями для хранения объектов самых разных типов. Действительно, элемент управления "список" существует только тогда, когда он виден или, по крайней мере, может быть немедленно отображен.

Тем не менее и описываемые списки имеют средства для сопоставления элементов списка с любыми объектами, как пользовательскими, так и системными. Для этого списки поддерживают возможность хранения 32-разрядных чисел или указателей для каждого элемента списка. Причем даже в случае изменения индекса элемента, что может происходить вследствие добавления, удаления и пересортировки списка, значение, связанное с конкретным элементом, будет связано именно с ним, независимо от индекса элемента в списке.

Перечислим функции, реализующие возможность как связывания чисел (указателей) с конкретным элементом, так и обратной операции: GetltemData, SetltemData, GetltemDataPtr, SetltemDataPtr.

В ряде случаев бывает необходимо определить, какой из элементов является видимым. Для определения и изменения первой видимой записи списка предназначены функции GetTopIndex и SetToplndex.

И, наконец, последняя функция этой группы позволяет определить, около какого элемента списка находится курсор мыши:

UINT CListBox::ItemFromPoint (

CPoint pt,

BOOL & bOutside)

Возвращает индекс записи списка, ближайшей к указанной точке, заданной параметром pt, причем координаты указаны относительно левого верхнего угла клиентской области окна списка. Параметр bOutside— ссылка на переменную типа BOOL, которая по завершении функции будет установлена в TRUE, если указанная точка лежит вне, и в FALSE, если точка находится внутри клиентской области окна списка.

 

Переопределяемые функции

Если создается список, внешний вид которого определяется программистом (списки со стилями LBS_OWNERDRAWFIXED или LBS_OWNERDRAWVARIABLE), то необходимо переопределить две описанные ниже функции.

Функция перерисовки элементов списка:

virtual void CListBox: : Drawl-tern (LPDRAWITEMSTRUCT

  IpDrawItemStruct)

Вызывается библиотекой, если необходима перерисовка элемента самоотображаемого списка, внешний вид которого определяется пользователем. Параметр IpDrawItemStruct является указателем на структуру DRAWITEMSTRUCT, которая содержит информацию о типе требуемой перерисовки. (Мы уже упоминали эту структуру, рассматривая работу с меню.)

Функция определения размеров элементов списка:

virtual void CListBox::MeasureItem (LPMEASUREITEMSTRUCT

 IpMeasureltem-Struct)

Вызывается при создании списка, внешний вид которого определяется пользователем, для определения размеров элемента списка. Параметр IpMeasureKemStruct является указателем на структуру MEASUREITEMSTRUCT.

По умолчанию эта функция не производит никаких действий. Необходимо переопределить ее, заполнив структуру типа MEASUREITEMSTRUCT, чтобы система Windows могла определить размеры списка. Если список создан со стилем LBS_OWNERDRAWVARIABLE, то функция MeasureItern вызывается для каждой записи списка, если нет, то функция будет вызвана всего один раз.

Функция, вызываемая при сортировке списков, не имеющих стиля LBS_HASSTRINGS и имеющих стиль LBS_SORT, обязательно должна быть переопределена:

virtual int CListBox::Corapareltem (

LPCOMPAREITEMSTRUCT IpCompareItemStruct)

Вызывается библиотекой для определения расположения нового элемента в списке, внешний вид которого задается пользователем и который должен осуществлять сортировку записей (список имеет стиль LBS_SORT).

Параметр IpCompareltemStrudl является указателем на структуру COMPAREITEMSTRUCT, в которой определяется относительное положение двух сравниваемых элементов списка. Функция возвращает следующие значения:

-1         Первый элемент меньше второго

 0         Первый элемент равен второму

 1         Первый элемент больше второго

 

Список, имеющий флажки

Класс CCheckListBox позволяет создать элемент управления, сочетающий свойства списка и флажков (кнопок выбора): он представляет собой обычный список (LIST BOX), в котором к каждой строке добавлен слева флажок (CHECK BOX), устанавливая или снимая который пользователь может выбирать строки (элементы) списка.

Создаваемый таким образом список всегда является списком, внешний вид которого определяется пользователем, так как каждый его элемент содержит не только текстовую строку. В простейшем случае список может содержать текстовые строки и флажки в каждой строке, но это совсем не обязательно: можно определить список, в котором будут выводиться флажки и небольшие растровые изображения.

Если создается список, содержащий только текст и флажки, то для вывода строк списка можно использовать функцию по умолчанию CCheckListBox:: Draw/tern. В любом случае необходимо переопределение функций CListBox:: Compareltem, CCheckListBox::Drawhem и CCheckListBox::MeasureItem.

 

Функции для работы с расширенным списком

Следующая пара функций позволяет программно определить и установить состояние флажка для элемента списка:

int CCheckListBox::GetCheck (int nlndex)

Возвращает состояние флажка для элемента списка.

void CCheckListBox::SetCheck ( int nlndex, int nCheck)

Устанавливает состояние флажка. Параметр nIndex является номером элемента в списке, а параметр nCheck определяет состояние флажка: 0 — снят, 1 — установлен, 2 — неопределенное состояние.

Для того чтобы сделать доступным/недоступным элемент списка и определить его состояние, предназначены следующие функции:

BOOL CCheckListBox::IsEnabled (int nlndex) 

Определяет, доступен ли элемент списка.

void CCheckListBox::Enable (

int nlndex, 

BOOL bEnabled = TRUE) 

Делает доступным или недоступным элемент списка,

Для получения и установления стиля флажков списка предназначены функции:

UINT CCheckListBox::GetCheckStyle ()

Возвращает стиль флажков.

void CCheckListBox::SetCheckStyle (UINT nStyle)

Параметр nStyle задает стиль флажков и может принимать следующие значения: BS_CHECKBOX, BS_AUTOCHECKBOX, BS_AUTO3STATE или BS_3STATE.

 

Виртуальные функции

virtual CRect CCheckListBox::OnGetCheckPosition (

CRect rectltem,

CRect rectCheckBox)

Вызывается библиотекой для определения положения флажка записи списка, где rectrfem определяет расположение и размер элемента списка, a rectCheckBox— размер и расположение (по умолчанию) флажка строки списка.

Реализация по умолчанию этой функции возвращает размер и расположение флажка, созданного с параметрами по умолчанию (выровнен по левому верхнему углу записи и имеет стандартный размер). Однако флажок может располагаться в правой части записи списка и иметь размер больше или меньше размера, установленного по умолчанию. В этих случаях необходимо переопределить эту функцию для того, чтобы библиотека могла получить правильные значения.

В следующем примере функция переопределяет функцию по умолчанию, помещает флажок в правой части элемента списка и переопределяет его размер:

CRect CMercuryCheckListBox::OnGetCheckPosition(

CRect rectltem, CRect rectCheckBox)

 {

CRect rectMyCheckBox;

rectMyCheckBox.top = rectltem.top -1;

rectMyCheckBox.bottom = rectltem.bottom -1;

rectMyCheckBox.right = rectltem.right -1;

rectMyCheckBox.left = rectltem;right -1 - rectCheckBox.Width();

return rectMyCheckBox; 

}

Кроме того, в классе CCheckListBox, естественно, переопределены и функции, отвечающие за самоотображение элементов списка: Drawltem и Measureltem.

Следующий пример иллюстрирует создание самоотображаемого, содержащего флажки списка с использованием класса CChecklistBox. Здесь я привожу только те фрагменты, которые мы должны добавлять сами, поскольку все остальное сделает ClassWizard.

class CMyPropertyPagel : public CPropertyPage

{

...

// Для реализации самоотображаемого списка необходимо создать свой

 // класс, чтобы переопределить соответствующие функции 

CColorBox m_CheckBox;

 // Данные страницы

//{(AFX_DATA(CMyPropertyPagel) 

 enum { IDD = IDD_PROPPAGE1 };

CVListBox m_List; 

//}}AFX_DATA

...

};

Как видите, необходимо создать два новых класса, чтобы "перехватить" отображение элементов списка:

class CColorBox : public CCheckListBox

{

// Construction

public:

CColorBox();

virtual -CColorBox();

 // Operations 

public:

// Эту функцию надо будет реализовать самостоятельно

void AddColorItem(COLORREF color); 

// Overrides

 // ClassWizard generated virtual function overrides

//({AFX_VIRTUAL(CColorBox)

  public:

virtual int CompareItem(LPCOMPAREITEMSTRUCT IpCompareltemStruct); 

virtual void DrawItemfLPDRAWITEMSTRUCT IpDrawItemStruct);

 virtual void MeasureItem(LPMEASUREITEMSTRUCT IpMeasureltemStruct);

 //}}AFX_VIRTUAL

...

};

class CVListBox : public CListBox 

{

...

// Overrides

// ClassWizard generated virtual function overrides

// { {AFX__VIRTUAL (CVListBox) 

 public:

virtual void DrawItemfLPDRAWITEMSTRUCT IpDrawItemStruct);

virtual void MeasureItem(LPMEASUREITEMSTRUCT IpMeasureltemStruct);

//}}AFX_VIRTUAL

...

);

После того как классы созданы, необходимо обеспечить их реализацией соответствующих функций:

// Минимальная величина обрамления пиктограммы в элементе списка

 #define FRAME 2

...

void CVListBox::Drawltem(LPDRAWITEMSTRUCT IpDrawIteroStruct) 

{

CDC dc;

// Контекст устройства уже был создан, и его надо только подключить

dc.Attach(lpDraw!temStruct->hDC);

CRect rect = lpDrawItemStruct->rdtem;

// Смещение для вьтода текста (32 — размер пиктограммы)

rect.left += 32 + FRAME + FRAME;

// Вычисляем требуемую высоту области вывода текста

int nHeight = dc.DrawText{(LPCTSTR)lpDrawItemStruct->itemData,

rect, DT_CALCRECT |  DT_LEFT |  DT_WORDBREAK) + FRAME + FRAME;

 rect.bottom = rect.top + nHeight;

 if (nHeight < 32 + FRAME + FRAME)

rect.bottom = rect.top + 32 + FRAME + FRAME;

 // Изменяем высоту элемента управления

SetltemHeight(lpDraw!temStruct->itemID, rect.Height{));

 // Поле структуры необходимо изменить самостоятельно

 lpDrawItemStruct->rcItem.bottom = rect.bottom;

 rect = lpDrawItemStruct->rdtem;

 // Определяем состояние элемента списка 

if (lpDrawItemStruct->itemState & ODS_SELECTED) 

// Рисуем "утопленную" кнопку

dc.DrawFrameControl(srect, DFC_BUTTON, 

DFCS_BUTTONPUSH | DFCS_PUSHED); 

} else

{(

// Рисуем выпуклую кнопку

dc.DrawFrameControl(&rect, DFC_BUTTON, DFCS_BUTTONPUSH); 

}

// Рисуем пиктограмму, центрируя ее по вертикали

 rect.top += (rect.Height() — 32) /2;

 dc.Draw!con(rect.left+FRAME, rect.top,

AfxGetApp()->LoadIcon(IDR_MAINFRAME)); 

rect = lpDrawItemStruct->rdtem; rect.left += 32 + FRAME; 

rect.DeflateRect(FRAME, FRAME);

rect.top += (rect.Height() - (nHeight - FRAME)) / 2;

 // Выводим текст элемента управления

 dc.SetBkMode(TRANSPARENT); 

dc. DrawText (- (LPCTSTR) IpDrawItemStruct-MtemData, rect,

DT_LEFT I DT_WORDBREAK); 

dc.Detach();

// Эту функцию необходимо было перегрузить, но, т. к.

// она вызывается только при добавлении элемента в список,

// а наша реализация настраивает высоту элемента при каждом

// отображении, реализация ее ничего не делает

void CVListBox::MeasureItem(LPMEASUREITEMSTRUCT IpMeasureltemStruct)

{

}

И добавки для класса CColorBox.

#define COLOR_ITEM_HEIGHT 20

...

void CColorBox::AddColorItem(COLORREF color)

 {

// Добавляем строку в список 

AddString((LPCTSTR) color); 

}

// Переопределяем функцию для сравнения строк списка 

int CColorBox::CompareItem(LPCOMPAREITEMSTRUCT IpCompareltemStruct) 

{

// return -1 = элемент 1 расположен перед элементом 2

 // return 0 = элемент 1 и элемент 2 не различаются 

// return 1 = элемент 1 расположен после элемента 2 

COLORREF crl = (COLORREF)lpCompareItemStruct->itemDatal; 

COLORREF cr2 = (COLORREF)lpCompareItemStruct->itemData2;

 if (crl == cr2)

return 0; // строки одинаковы

 // Сначала сортируем строки по интенсивности

int intensityl = GetRValue(crl) + GetGValue(crl) + GetBValue(crl);

 int intensity2 = GetRValue(cr2j + GetGValue(cr2) + GetBValue(cr2);

 if (intensityl < intensity2)

return -1; // сначала идут строки с меньшей яркостью 

else if (intensityl > intensity2)

return 1; // более яркие строки идут вторыми

 // Если строки имеют одинаковую интенсивность, 

//то сортируем по цветам:

 // первый — синий, последний — красный 

if (GetBValue(crl) > GetBValue(cr2))

return -1; else if (GetGValue(crl) > GetGValue(cr2))

return -1; else if (GetRValue(crl) > GetRValue(cr2))

return -1; 

 else

return 1 ;

 return 0; 

}

// Переопределяем функцию для рисования строк списка,

// причем рисуем только содержимое каждого из элементов —

// квадрат и знак в нем (выбрано/не выбрано)

// рисуют без нашего участия

void CColorBox::Drawltern(LPDRAWITEMSTRUCT IpDrawItemStruct)

{

CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC);

 COLORREF cr = (COLORREF)lpDrawItemStruct->itemData;

 if (lpDrawItemStruct->itemAction & ODA_DRAWENTIRE)

 {

CBrush br(cr);

pDC->FillRect(slpDraw!temStruct->rcItem, &br);

 }

if ((lpDraw!temStruct->itemState & ODS_SELECTED) &&

 (lpDrawItemStruct->itemAction &

(ODA_SELECT | ODA_DRAWENTIRE)))

 {

// Выделение рамки, если запись выделена

 COLORREF crHilite = RGB(255-GetRValue(cr),

255-GetGValue(cr), 

255-GetBValue(cr)); 

CBrush br(crHilite);

pDC->FrameRect(&lpDrawItemStruct->rcItem, &br);

 } 

if (!(lpDrawItemStruct->itemState & ODS_SELECTED) &&

(lpDrawItemStruct->itemAction & ODA_SELECT))

 {

// Удаление рамки, если выделение с записи списка снято

 CBrush br(cr);

pDC->FrameRect(&lpDrawItemStruct->rdtem, &br); 

}

 }

// Переопределяем функцию для определения размеров строк списка 

void CColorBox::MeasureItem(LPMEASUREITEMSTRUCT IpMeasureltemStruct) 

{

lpMeasureItemStruct->itemHeight = COLOR_ITEM_HEIGHT; 

}

Нам осталось только создать два элемента управления типа ListBox на странице набора свойств и добавить для этой страницы соответствующий код:

BOOL CMyPropertyPagel::OnInitDialog()

{

CPropertyPage::OnInitDialog();

// Связываем объект класса CColorBox с элементом управления

//по идентификатору

VERIFY (m_CheckBox.SubclassDlgItem(IDC_CHECKBOX, this));

// Заполняем список строками, в которых выводятся 

// различные цвета: цветов всего 8, и их определяет

 // наличие или отсутствие RGB-составляющей 

for (int red = 0; red <= 255; red += 255)

for (int green = 0; green <= 255; green += 255) 

for (int blue = 0; blue <= 255; blue += 255)

m_CheckBox.AddColorItem(RGB(red, green, blue)); 

CRect rect;

 GetClientRect(srect) ;

 rect.left = rect.right/2 - 24;

 rect.right -= 8; 

rect.top += 10; 

rect.bottom -= 10; 

m_List.MoveWindow(& г е с t);

// Настраиваем начальные размеры для всех элементов списка 

// (в нашем примере, в принципе, это делать не обязательно)

 m_List.SetItemHeight(0, 40); 

// Добавляем три элемента в список 

m_List.AddString (_T ("Короткий текст") ) ;

 m_List.AddString(_T("Средний по длине текст\пв две строки"));

 m_List.AddString(_Т("Длинная строка, для отображения которой "

"необходимо несколько строк"));

 return TRUE;

 }

После запуска приложения на экран будет выведен набор свойств. Выбрав страницу "Списки", вы увидите список, в котором строки будут иметь различные цвета, и список, содержащий текст и пиктограмму (рис. 16.4).

Рис. 16.4. Страница свойств с различными списками .

 Примечание 

Обратите внимание на то, что связывание члена CCheckBoxDlg::m_CheckBox происходит без механизма обмена данными DDX (Dialog Data Exchange). Дело в том, что мастер ClassWizard не поддерживает класс CCheckListBox. (В примере класс ССоlогВох изначально был создан на основе класса CListBox, затем вручную имя базового класса было заменено на CCheckListBox.)

 

Комбинированный список

Ранее были рассмотрены классы CEdit и CListBox, позволяющие создавать элементы управления "текстовое поле" и "список", но они настолько часто применяются в паре друг с другом, что в Windows был создан элемент управления, сочетающий свойства обоих этих элементов. Он представляет собой комбинацию окна списка с окном текстового поля и называется "комбинированным списком" (COMBO BOX). Часть комбинированного списка — непосредственно список — может выводиться на экран все время (стиль CBS_SIMPLE) или может как бы раскрываться из текстового поля (стили CBS_DROPDOWN и CBS_DROPDOWNLIST). Текущая выбранная запись списка выводится в прямоугольном окне, которое представляет собой окно простейшего текстового поля и может быть как доступна (стили CBS_SIMPLE и CBS_DROPDOWN), так и недоступна (стиль CBS_DROPDOWNLIST) для редактирования. Если к тому же комбинированный список определен как раскрывающийся, то при нажатии символьной клавиши на клавиатуре строка, начинающаяся с данного символа, будет выделена.

Если вы хотите обрабатывать уведомляющие сообщения Windows, посылаемые комбинированным списком своему родительскому окну (обычно это объект класса, производного от CDialog, или страница набора свойств), то необходимо добавить в карту сообщений этого окна соответствующий обработчик.

Работа с этим элементов управления достаточно проста и мы не будем останавливаться на нем подробно.

 

Просмотр видеоклипов

Класс CAnimateCtrl поддерживает функциональные возможности элемента управления Windows, который реализует просмотр видеоклипов (элемент управления "анимация").

Система Windows поддерживает просмотр видеоинформации, записанной в формате AVI (Audio Video Interleaved) — стандартном формате Windows. Такие видеоклипы представляют собой совокупность растровых изображений — кадров. Сам просмотр осуществляется в прямоугольной области, размер которой определяется либо заданными размерами элемента управления, либо размерами изображения в зависимости от стиля элемента (см. ниже).

Для возможности просмотра видеоинформация должна соответствовать следующим требованиям (можно просматривать только простейшие видеоклипы):

 Примечание 

Для применения в приложениях более совершенных средств мультимедиа следует использовать окна класса объектов Windows — MCIWnd.

Видеоклип может быть добавлен к приложению в качестве ресурса типа AVI, например, вот так:

IDR_VISUAL AVI DISCARDABLE   "res\\Visual.avi"

Иными словами, для просмотра любого видеофайла (соответствующего требованиям) достаточно его имени.

 

Элемент управления "анимация"

Фирма Microsoft рекомендует использовать этот элемент управления для визуализации длительных операций. Сама система использует, прежде всего, видеоклипы для визуализации копирования, перемещения и удаления файлов и папок, а также при поиске объектов, например, компьютеров, по сети.

В библиотеке MFC для этого реализован класс CAnimateCtrl.

Как и для других элементов управления, для просмотра видеоклипов достаточно или создать соответствующий объект в качестве дочернего в любом окне, или добавить этот элемент управления в шаблон блока диалога. Оба способа позволяют задавать дополнительные стили, свойственные режиму просмотра видеоклипов.

BOOL CAnimateCtrl::Play ( 

 UINT nFrom, 

UINT nTo, 

UINT nRep)

Параметры nFrom и nТо определяют начальный и конечный кадры, считая с нулевого, для "проигрывания". Оба значения должны лежать в пределах от 0 и до 65535 включительно. Значение параметра пТо, равное —1, указывает, что "проигрывание" необходимо проводить до конца клипа, таким образом, для "проигрывания" клипа полностью нет необходимости знать количество составляющих его кадров. Третий параметр определяет число "проигрываний" клипа. Значение -1 задает бесконечное "проигрывание".

Для остановки "проигрывания" видеоклипа в любой момент времени необходимо вызвать функцию Stop, а для перемещения к определенному кадру, но без инициализации "проигрывания" — функцию Seek.

BOOL CAnimateCtrl::Seek (DINT nTo)

Единственный параметр nТо определяет номер кадра, начиная с нуля, который необходимо отобразить. Значение параметра, равное —1, указывает на необходимость отображения последнего кадра клипа.

Уведомления

Описываемый элемент управления посылает два вида уведомлений. Причем, как исключение из правила, оба уведомления посылаются не в форме сообщения WM_NOTIFY, что обычно для общих элементов управления, а в форме сообщения WM_COMMAND:

Для того чтобы обработать эти сообщения, в рамках библиотеки MFC существует макрос ON_CONTROL, при помощи которого определяют элемент уведомления, тип уведомления и функцию его обработки.

Пример, созданный на основе страницы свойств, приводится после описания элемента управления "Индикатор".

Индикатор

Индикатор — это дочернее окно, которое можно использовать в приложении для индикации процесса выполнения некоторой длительной операции. Такое окно представляет собой вытянутую и расположенную горизонтально (а теперь и вертикально) прямоугольную область (полосу). По мере выполнения операции содержимое области заполняется слева направо прямоугольниками, цвет которых соответствует системному цвету, используемому для подсвечивания надписей или иных объектов.

В отличие от большинства других элементов управления, цвет которых можно задать в ответ на соответствующий запрос, цвет индикатора определяется системными установками и изменению не подлежит.

Большинство программ реализуют индикацию двумя способами: добавляют элемент индикации в блок диалога либо используют его в специально созданных панелях диалога. И тот, и другой способ подразумевает использование шаблонов блока диалога, что исключает необходимость создания функции для определения окна индикатора; Библиотека MFC имеет в своем составе класс CProgressCtrl, который обеспечивает интерфейс между приложением и объектом Windows "индикатор". Этот класс имеет конструктор, задача которого — создание объекта класса, собственно же элемент индикации может быть создан при помощи функции Create (при задании элемента в шаблоне блока диалога ее использование не требуется).

Реализация процесса и его взаимосвязи с индикацией полностью ложится на разработчика приложения. Собственно индикатор является лишь средством отображения отношения двух чисел: первое определяется величиной некоторого интервала, а второе — смещением от начала интервала. Границы интервала лежат в пределах от —217 483 648 до 217 483 647 включительно, а смещение определяется заданным текущим абсолютным значением и нижней границей интервала. Для задания интервала класс CProgressCtrl имеет две функции: SetRange и SetRange32,

void CProgressCtrl::SetRange (

short nLower,

short nUpper)

 И

void CProgressCtrl::SetRange32 (

int nLower,

int nUpper)

Параметры nLower и nUpper определяют нижнюю и верхнюю границы интервала, соответственно.

Текущее значение индикатора может быть установлено тремя различными способами. Во-первых, можно непосредственно задать текущее значение: 

int CProgressCtrl::SetPos (int nPos)

Эта функция не только задает текущее значение (параметр nPos), но и возвращает предыдущее значение индикатора. При неверном задании текущего значения (вне интервала) новое значение будет приведено к значению ближайшей границы интервала.

Во-вторых, можно осуществить ранее заданное смещение (шаг) относительно текущего значения при помощи функции Step It.

int CProgressCtrl::StepIt ()

Эта функция также возвращает предыдущее значение индикатора. Причем в случае, если в результате добавления к текущему значению величины шага значение индикатора выйдет за пределы интервала, новое значение индикатора будет возвращено в заданные пределы.

Шаг задается функцией SetStep.

int CProgressCtrl::SetStep (int nStep)

Шаг задается параметром nStep, сама функция возвращает предыдущее значение шага. Значение шага может быть отрицательным, что позволяет не только увеличивать значение индикатора, но и уменьшать его.

В-третьих, текущее значение устанавливается непосредственно заданным смещением при помощи функции OffsetPos:

int CProgressCtrl::OffsetPos (int nPos)

Параметр nPos определяет смещение относительно текущего значения. Сама функция, как и все остальные, изменяющие текущее значение, возвращает его предыдущее значение. При выходе за пределы интервала, в отличие от функции Steplt, выполнение функции OffsetPos приведет к тому, что текущим значением интервала будет либо нижняя, либо верхняя границы в зависимости от значения смещения.

При выполнении любой функции, в результате действий которой может измениться отношение величины интервала и текущего значения (SetRange, SetPos, Steplt, OffsetPos), происходит автоматическая перерисовка индикатора. Однако уже при создании (с использованием функции Create или вместе с блоком диалога) индикатор инициализируется следующими начальными значениями (по умолчанию):

Узнать текущий диапазон и положение индикатора можно при помощи функций GetRange и GetPos, соответственно.

 

Пример использования просмотра видеоклипов и индикатора

Давайте рассмотрим страницу свойств, которая содержит два элемента управления для просмотра одного и того же видеоклипа, а также индикатор. "Проигрывание" видеоклипа осуществляется попеременно то одним элементом управления, то другим. Переключение осуществляется по завершению работы одного из элементов. Для индикатора создаются два таймера. Задача первого — имитация начала и окончания некоторой операции, второго — имитация собственно операции. Для наглядности в код "начала операции" добавлены функции начальной инициализации индикатора.

class CMyPropertyPageS : public CPropertyPage

{

...

// Данные

//{{AFX_DATA(CMyPropertyPageS)

enum { IDD = IDD_PROPPAGE3 );

CAnimateCtrl m_Animate_2;

CAnimateCtrl m_Animate_l; 

//}}AFX_DATA

 // Overrides

 // ClassWizard generate virtual function overrides

//{{AFX_VIRTUAL(CMyPropertyPage3)

  public:

virtual BOOL OnSetActive(); 

// virtual BOOL OnKillActive(); 

protected:

virtual void DoDataExchange(CDataExchange* pDX) ;

 //}},AFX_VIRTUAL

 // Implementation protected:

// Generated message map functions 

//({AFX_MSG(CMyPropertyPage3)

afx_msg void OnTimer(UINT nlDEvent);

 afx_msg void AnimateStop_l(); 

afx_msg void AnimateStop_2();

 //}}AFX_MSG

...

};

 Примечание

Обратите внимание, что в данном случае инициализация страницы производится специальной функцией OnSetActive вместо использованной ранее функции родительского класса OnlnitDialog. Такой подход на самом деле является более правильным.

// Функция обработки уведомления от первого элемента

// управления о завершении просмотра — начинаем просмотр

//во втором элементе управления

void CMyPropertyPage3::AnimateStop_l()

{

m_Animate_2.Play(0, (UINTJ-1, 1); 

}

// Функция обработки уведомления от второго элемента 

// управления о завершении просмотра — начинаем просмотр 

//в первом элементе управления

 void CMyPropertyPage3::AnimateStop_2() 

{

m_Animate_l.Play(0, (UINT)-l, 1); 

{

// Инициализируем страницу свойств 

BOOL CMyPropertyPage3::OnSetActive() 

// "Открываем" клип для первого элемента управления

m_Animate_l.Open(IDR_VISUAL);

//и немедленно начинаем его просмотр

m_Animate_l.Play(0, (UINT)-l, 1);

// "Открьшаем" клип для второго элемента управления

m_Animate_2.Open(IDRJVISUAL);

// Получаем указатель на объект, созданный в редакторе ресурсов

CProgressCtrl *pProgress =

(CProgressCtrl)GetDlgItem(IDC_PROGRESS);

 // Устанавливаем диапазон, начальную позицию и шаг

 pProgress->SetRange(0, 1000); 

pProgress->SetPos(0); 

pProgress->SetStep'(10) ; 

SetTimer(ID_POLSE, 5000, NULL); 

SetTimer(ID_INPROG, 50, NULL); 

return CPropertyPage::OnSetActive(); 

}

void CMyPropertyPage3::OnTimer(UINT nIDEvent) 

{

static int nCheck = 1; CProgressCtrl *pProgress =

(CProgressCtrl *)GetDlgItem(IDC_PROGRESS); 

if (nIDEvent == ID_PULSE) 

{

if (nCheck = !nCheck)

 {

pProgress->SetRange(0, 1000);

 pProgress->SetPos(0) ;

pProgress->SetStep(10); 

} else if (nIDEvent == ID_INPROG && nCheck)

pProgress->Step!t();

  CPropertyPage::OnTimer(nIDEvent); 

}

Результат работы программы показан на рис. 16.5.

Рис. 16.5. Страница свойств с просмотром видеоклипов и индикатором

 

Счетчик

Элемент управления "счетчик" (SPIN BUTTON или UP-DOWN) представляет собой пару кнопок со стрелками, назначение которых — смена значения, например, уменьшение либо увеличение числовой величины. Счетчик похож на вырожденную полосу прокрутки (SCROLL BAR) и действует примерно так же. Кроме того, этот элемент управления можно ассоциировать с другим элементом управления так, что его содержимое будет меняться под управлением счетчика. Такие ассоциированные элементы управления называются окнами сопровождения (BUDDY WINDOW).

Если счетчик ассоциирован с другим окном (элементом управления), для пользователя они выглядят как единый элемент управления. Действительно, счетчик может автоматически позиционировать себя вместе с другим элементом управления, располагающимся прямо перед ним, например, в шаблоне блока диалога, и самостоятельно устанавливать заголовок этого элемента.

В случае, когда счетчик меняет числовые значения, нажатие верхней кнопки изменяет значение в сторону максимума, а нижней — в сторону минимума. Максимумом по умолчанию является значение 0, минимумом — значение 100.

 Примечание 

Да, да, именно так. О причинах такого решения можно только догадываться.

Не имея ассоциированного с ним окна, счетчик функционирует как упрощенный вариант полосы прокрутки. Например, элемент управления, реализующий вкладки (TAB), использует счетчик для осуществления доступа к дополнительным вкладкам.

 

Уведомления

Счетчик может посылать до четырех различных сообщений для одной ориентации, первые три из которых — уведомляющие. Все они посылаются родительскому окну и никак не воздействуют на ассоциированное со счетчиком окно. Эти сообщения взаимосвязаны.

Первое сообщение, UDN_DELTAPOS, посылается родительскому окну при нажатии одной из кнопок со стрелками для того, чтобы дать возможность приложению до осуществления модификации содержимого ассоциированного со счетчиком окна предпринять какие-либо действия. Например, проверить допустимость изменений, отменить или разрешить их. Для того чтобы отменить изменения в ассоциированном окне, следует в обработчике этого сообщения установить по указателю pResult значение TRUE. И наоборот — значение FALSE разрешит произвести изменения:

BEGIN_MESSAGE_MAP(CSunDlg, CDialog) 

//{{AFX_MSG_MAP(CSunDlg)

ON_NOTIFY(UDN_DELTAPOS, IDC_SPIN, QnDeltaPos)

...

//}}AFX_MSG_MAP

 END_MESSAGE_MAP()

void CSunDlg::OnDeltaPos(NMHDR* pNMHDR, LRESULT* pResult)

 {

NM_UPDOWN* pNMUpDown = (NMJJPDOWN*)pNMHDR;

// Меняем значения в два раза быстрее, чем задано

pNMUpDown->iDelta *= 2;

//В случае, если ожидаемое значение

// будет больше 10, запрещаем изменение

if (pNMUpDown->iPos + pNMUpDown->iDelta > 10)

*pResult = TRUE; // Запрет изменения 

else

*pResult = FALSE; // Разрешение изменения 

}

Как видно из примера, обработчик получает указатель на структуру NM_UPDOWN, которая содержит необходимую информацию о возможных изменениях:

typedef struct _NM_UPDOWN {

NMHDR hdr;

in't iPos;

int iDelta; 

} NM_UPDOWNW

Первый параметр структуры — заголовок. Он требуется для всех уведомляющих сообщений. Заголовок содержит дескриптор и идентификатор окна, пославшего сообщение, а также код самого сообщения. Параметр iPos содержит текущее значение счетчика, а параметр Delta — возможное изменение текущего положения. Оба параметра числовые и могут быть отрицательными.

Возникает вопрос: что делать, если ассоциированное окно содержит нечисловые значения или ассоциированного окна нет, ведь изменение значения счетчика должно быть связано с выполнением некоторых действий?

Для ответа посмотрим, что же происходит после обработки сообщения UDN_DELTAPOS. В случае, если разрешение изменения получено (а по умолчанию именно так и происходит), счетчик посылает одно из сообщений, аналогичных сообщениям от полосы прокрутки: WM_HSCROLL или WM_HSCROLL. Тип сообщения зависит от того, как ориентирован счетчик, вертикально или горизонтально.

void CSunDlg::OnVScroll(

UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)

{

// Обрабатываем только сообщения от счетчика

if (pScrollBar->m_hWnd == GetDlgItera(IDC_SPIN)->m_hWnd)

{

// Нас интересует завершение "прокрутки" счетчика

if (nSBCode == SB_ENDSCROLL)

{

// Здесь выполняются необходимые действия,

 // связанные с изменением значения счетчика.

 // Текущее значение счетчика содержит 

// параметр nPos

...

CDialog::OnVScroll(nSBCode, nPos, pScrollBar);

}

 

Автоматическое изменение

При наличии ассоциированного окна счетчик может самостоятельно изменять его содержимое только в одном случае. Когда счетчик создан для изменения целочисленных значений, при нажатии одной из кнопок со стрелкой счетчик посылает ассоциированному с ним окну сообщения WM_GETTEXT и WM_SETTEXT.

Первое сообщение используется для того, чтобы определить текущее значение содержимого окна, второе — для его изменения. В качестве значения нового содержимого окна выступает строка, содержащая символьное представление числа — текущего значения счетчика. Такой подход позволяет синхронизировать содержимое окна и значения счетчика, т.е. позволяет изменять содержимое ассоциированного окна и другими средствами помимо счетчика — программно или вручную пользователем.

А что будет, если пользователь ввел строку текста, которая вовсе не является представлением числа?

В этом случае в качестве текущего значения, которое будет увеличено или уменьшено, выступает текущее значение счетчика — окно ведь не содержит числа. Реализация, пожалуй, довольно логична.

Из вышесказанного следует, что связывать счетчик можно только с такими типами окон, которые изменяют свое содержимое при получении сообщения WM_SETTEXT. Однако это не совсем так — любому окну Windows можно посылать любые сообщения, следовательно, и любое окно может быть связано со счетчиком. Другое дело, что содержимое его может не меняться. Например, кнопки, текстовые поля, статические элементы откликаются на воздействие со стороны счетчика, а другие, такие как индикатор (PROGRESS BAR), никак не реагируют.

 

Параметры элемента управления

В случае, если счетчик не имеет стиля UDS_AUTOBUDDY, класс предоставляет возможность связывания с ним окна (функция SetBuddy возвращает указатель на объект класса CWnd — окно, которое было ранее ассоциировано со счетчиком). В то же время можно получить указатель на объект класса CWnd — окно, которое в настоящий момент ассоциировано со счетчиком (функция GetBuddy). Указатель не может быть сохранен для дальнейшего использования, если указывает на временный объект.

Для установки значения счетчика и получения текущего значения вызываются функции SetPos и GetPos, соответственно. Тип текущего значения — целое число, следовательно, для изменения содержимого ассоциированного окна, которое не является числом, требуется некоторое преобразование. Значение счетчика может использоваться в качестве индекса массива строк или объектов другого типа.

При получении текущего значения счетчик запрашивает содержимое ассоциированного с ним окна.

Обе функции проверяют заданное значение или результат преобразования содержимого ассоциированного окна. Это значение должно лежать в пределах заданного диапазона. Напомним, что по умолчанию для счетчика задан диапазон от 100 до 0 (именно так: 100 — минимальное значение, а 0 — максимальное). Для изменения диапазона следует использовать одну из функций:

void CSpinButtonCtrl::SetRange (

 int nLower, 

int nUpper)

 или

void CSpinButtonCtrl::SetRange32 (

 int nLower, 

int nUpper)

Параметры nLower и nUpper содержат значения границ диапазона. Значения обоих параметров должны лежать в пределах от UD_MINVAL до UD_MAXVAL. Кроме того, разница между максимальным и минимальным значениями границ не должна быть больше UD_MAXVAL. В настоящий момент действуют следующие значения:

UD_MAXVAL         0x7FFF (32 767) . 

UD_MINVAL         -UD_MAXVAL (-32 767)

 

Примечание 

Естественно, что в программе для проверки допустимости значений следует использовать не числа, а мнемонические обозначения границ.

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

Если требуется существенно изменить значение счетчика, а вместе с ним и содержимое ассоциированного окна именно с помощью средств счетчика, т. е. его кнопок со стрелками, приходится тратить довольно много времени: одно нажатие кнопки изменяет значение счетчика только на одну единицу. Однако счетчик предоставляет возможность изменять величину приращения в зависимости от времени непрерывного нажатия кнопок. Для этого служат функции SetAccel и GetAccel:

BOOL CSpinButtonCtrl::SetAceel (

int nAccel, 

UDACCEL* pAccel)

Позволяет задать массив, включающий в качестве элементов структуры UDACCEL, которые содержат необходимую информацию для динамического изменения значения приращения:

typedef struct {

int nSec;

 int nInc; 

} UDACCEL;

Поле nSec содержит время в секундах, по истечении которого приращение устанавливается равным значению поля nInc.

Массив должен быть упорядочен по возрастанию поля nSec, которое содержит время от начала взаимодействия пользователя со счетчиком.

И, наконец, последняя группа функций позволяет задавать основание системы счисления для отображения числовых значений счетчика в ассоциированном окне. Всего можно использовать две системы счисления — десятичную и шестнадцатеричную. Для этого используется, как обычно, пара функций SetBase и GetBase: первая служит для назначения системы счисления, а вторая — для получения информации о текущей установленной:

int CSpinButtonCtrl::SetBase {int nBase)

Возвращает основание ранее установленной системы счисления, либо О, если была задана система, не поддерживаемая счетчиком, т. е. не десятичная и не шестнадцатеричная. Параметр nBase может принимать значения 10 или 16. Следует добавить, что при установке шестнадцатеричной системы счисления значения счетчика не могут быть отрицательными.

В рассматриваемом нами приложении Pro мы воспользовались только возможностью установки диапазона:

BOOL CMyPropertyPage6::OnSetActive()

{

// Элемент управления создан в редакторе ресурсов, и поэтому

 // для установки диапазона счетчика

// нам достаточно просто получить указатель на имеющийся объект

 ((CSpinButtonCtrl *)GetDlgItem(IDC_SPINl))->SetRange(D, 20);

 return CPropeirtyPage: rOnSetActive () ; 

}

Соответствующая страница представлена на рис. 16.6. А на рис. 16.7 показаны стили, которые необходимо установить для этого элемента управления, чтобы "подключить" его к предварительно созданному полю ввода.

Рис. 16.6. Страница свойств со счетчиком и ассоциированным с ним полем ввода

Рис. 16.7. Окно свойств элемента управления "счетчик"

 

Просмотр списка

Просмотр списка — мощное средство представления упорядоченного списка элементов, каждый из которых может иметь структуру, состоящую из нескольких полей. С каждой записью могут быть ассоциированы до трех пиктограмм различного размера, отображение которых несет дополнительную визуальную информацию.

Режимы вывода

Элемент "просмотр списка" может выводить записи четырьмя различными способами, наличие которых существенно отличает этот элемент управления

от всех других. При этом возможно переключение от одного режима представления к другому после создания элемента управления. Рассмотрим режимы вывода подробнее:

Режим вывода

Режим больших пиктограмм (Icon view)

Описание

Задается стилем LVS_ICON; каждая запись представляет собой пиктограмму, под которой расположена надпись; записи могут перемещаться пользователем внутри списка просмотра

Режим малых пиктограмм (Small icon)

Задается стилем LVS_SMALLICON; каждая запись выводится в виде маленькой пиктограммы с надписью, расположенной справа от нее; записи могут перемещаться пользователем внутри списка

Режим списка (List view)

Задается стилем LVS_LIST; каждая запись выводится в виде маленькой пиктограммы с надписью, расположенной справа от нее; в отличие от предыдущего способа вывода, записи выводятся столбцами, и пользователь не может менять их расположение внутри списка

Режим таблицы (Report view)

Задается стилем LVS_REPORT; каждая запись с информацией о ней выводится в отдельной строке; пиктограмма и надпись выводятся в столбцах, расположенных слева, а в столбцах справа приложение может размещать дополнительную информацию для каждой записи; если не задан стиль LVS_NOCOLUMNHEADER, то каждый столбец имеет заголовок

Примечание 

Размер больших пиктограмм — 32x32 пиксела, а малых — 16x16.

Примером применения элемента "просмотр списка" может служить правая часть окна программы Windows Explorer, которая позволяет установить перечисленные выше способы вывода записей в окне.

Для задания выравнивания пиктограмм и надписей при первых двух способах вывода служат стили LVS_ALIGNTOP — выравнивание по верхнему краю (по умолчанию) или LVS_ALIGNLEFT — выравнивание по левому краю. Способ выравнивания также можно изменять уже после создания просмотра списка. Для отделения стилей, задающих выравнивание записей, можно применять стиль LVS_ALIGNMASK.

Дополнительные стили окна позволяют реализовать другие возможности, например, автоматически выбирать элемент, когда курсор мыши "задерживается" над ним определенное время, редактировать надписи под пиктограммами непосредственно в просмотре списка, выбирать больше одной записи, в режиме таблицы изменять порядок расположения столбцов и т. д.

 

Создание элемента "просмотр списка"

Библиотека MFC имеет в своем составе класс, инкапсулирующий возможности рассматриваемого элемента управления. Это класс CListCtrl, позволяющий существенно упростить использование самого элемента управления. Как и большинство других элементов управления, "просмотр списка" создается или в блоке диалога путем добавления в шаблон блока диалога соответствующего элемента, или в обычном окне при помощи вызова функции Create в функциях окна OnlnitDialog (для диалогов) или OnCreate.

Для того чтобы добавить записи в список, нужно воспользоваться одной из версий функции Insertltem. Первая версия использует заранее подготовленную структуру LV_ITEM, а последняя в качестве параметров принимает значения, аналогичные значениям полей структуры LV_ITEM. Вторая и третья версии функции полезны для списков, элементы которых не требуют для отображения дополнительных данных.

Мы подробно рассмотрим только структуру, т. к. имена и типы параметров последней версии функции говорят сами за себя:

typedef struct _LV_ITEM (

UINT mask;

int i I tern;

int iSubltem;

UINT state;

UINT stateMask;

LPSTR pszText;

int cchTextMax; ......

int ilmage;

LPARAM IParam; 

#if (_WIN32_IE >= 0x0300)

int iIndent;

 #endif 

} LVITEM;

Поле mask определяет, какие из оставшихся полей несут информацию, и может быть комбинацией следующих значений:

LVIF_TEXT 

Поле pszText содержит данные

LVIF_MAGE 

Поле Imagе содержит данные

LVIF_PARAM

 Поле IParam содержит данные

LVIF_STATE 

Поле state содержит данные

LVIF_NDENT

 Поле Undent содержит данные

LVIF_NORECOMPUTE

 Для получения текстовой информации элемент управления не генерирует уведомление LVN_GETDISPINFO

LVIF_DI_SETITEM 

Список затребованной информации об элементах просмотра списка хранится в операционной системе

Поле iltem задает индекс записи, перед которой осуществляется вставка новой записи; поле iSubltem содержит порядковый номер (начиная с 1) дополнительного поля (в функции Insertltem не используется); поля state и stateMask задают текущее и возможные состояния для данной записи. Эти поля могут содержать комбинации следующих флагов:

LVIS_ACTIVATING 

Элемент активизируется в уведомлении LVN_ACTIVATE

LVIS_CUT 

Запись выделена для операции вырезания или вставки (cut-and-paste)

LVIS_DROPHILITED 

Запись выделена для переноса (drag-and-drop)

LVIS_FOCUSED 

Запись имеет фокус ввода; естественно, что может быть выделено несколько записей одновременно, но только одна может иметь фокус ввода

LVIS_SELECTED

 Запись выбрана

Поле pszText является указателем на текстовую строку (буфер) (если параметр имеет значение LPSTR_TEXTCALLBACK, то это строка обратного вызова); поле cchTextMax задает размер буфера, на который указывает поле pszText, используется только для получения параметров записи в функции Getltem (а в функции Insertltem значение этого параметра игнорируется); поле Нтаде задает индекс пиктограммы в списке изображений пиктограмм (если параметр имеет значение IJMAGECALLBACK, то это запись обратного вызова); поле IParam задает 32-битное значение, ассоциированное с записью. Новое поле Undent определяет величину отступа элемента (в "единицах" ширины изображения) и может использоваться только для самих элементов, но не для подэлементов.

Для удаления записи списка по индексу следует использовать функцию Deleteltem, а для единовременного удаления всех записей — DeleteAlIItem.

 

Работа со столбцами

Если появляется необходимость реализовать поддержку режима отображения содержимого элемента "просмотр списка" в виде таблицы, следует в обязательном порядке, независимо от количества полей представления записей списка, создать столбцы. Для определения требуемой ширины столбца служит функция:

int CListCtrl::GetStringWidth (LPCTSTR Ipsz)

Позволяет на основании строки текста определить минимальную ширину столбца, необходимую для ее вывода на экран целиком. Для добавления (вставки) нового столбца следует использовать функцию InsertColumn:

int CListCtrl::InsertColumn (

int nCol,

const LV_COLUMN* pColumn)

И

int CListCtrl::InsertColumn (

 int nCol,

LPCTSTR IpszColurnnHeading, i

int nFormat = LVCFMT _LEFT,

int nWidth = -1,

int nSubltem = -1) 

 Обе версии взаимозависимы. Параметр nCol определяет индекс нового столбца. Рассмотрим ту реализацию, которая в качестве параметров, в данном случае для нового столбца, использует структуру LV_COLUMN:

 typedef struct _LV_COLUMN {

 UINT mask; 

int fmt;

 int ex;

LPSTR pszText; int cchTextMax; 

int iSubltem; 

#if (_WIN32_IE >= 0x0300)

 int iImage;

 int iOrder; 

#endif 

} LV__COLUMN;

Поле mask определяет, какие из остальных полей структуры заданы для описания столбца, и может быть равно нулю или содержать комбинацию следующих флагов:

LVCF_FMT            Поле fmt содержит данные

LVCF_SUBITEM        Поле iSubltem содержит данные

LVCF_IMAGE          Поле Imagе содержит данные

LVCF_ORDER          Поле iOrder содержит данные

LVCF_TEXT П         Поле pszText содержит данные

LVCF_WIDTH          Поле сх содержит данные

Поле сх определяет ширину столбца, при значении этого поля, равным —1, ширина не задается, т. е. содержимое столбца не будет отображаться; поле fmt задает режим выравнивания столбцов, причем этот режим относится как к заголовку столбца, если он задан и отображается, так и к содержимому полей всех записей просмотра списка. Всего таких режимов три, и названия флагов, их определяющих, говорят сами за себя:

LVCFMT_BITMAP_ON_RIGHT 

Битовый массив располагается справа от текста. Не относится к рисунку из списка образов, присвоенного элементу заголовка

LVCFMT_LEFT 

Текст сдвинут влево

LVCFMT_CENTER 

Текст располагается по центру

LVCFMT_RIGHT 

Текст сдвинут вправо

LVCFMT_IMAGE 

Для изображения элемента используется рисунок из списка образов

LVCFMT_COL_HAS_IMAGES 

Элемент заголовка имеет рисунок в списке образов

Поле pszText содержит указатель на строку, содержащую наименование заголовка столбца (при получении информации о столбце это поле должно указывать на буфер, куда будет помещено текущее название, длина буфера задается значением поля cchTextMax); поле iSubltem связывает столбец с полем записи. Поле Нтаде определяет индекс изображения внутри списка образов, a /Order— порядковый номер столбца, начиная с крайнего левого.

Для удаления столбца предназначена функция Delete Column. Для получения параметров столбца, а также для их изменения предназначены следующие функции.

BOOL CListCtrl::GetColumn

 int nCol,

LV_COLUMN* pColumn) 

Возвращает параметры столбца.

BOOL CListCtrl::SetColumn

int nCol,

const LV_COLUMN* pColumn) 

Устанавливает новые параметры столбца.

int CListCtrl::GptColunmWidth (int nCol)

 Возвращает ширину столбца.

BOOL CListCtrl::SetColmnnWidth (

int nCol, 

int cx)

Устанавливает ширину столбца.

В режиме просмотра списка в виде таблицы элемент управления имеет специальные заголовки столбцов, доступ к которым обеспечивается функцией

CHeaderCtrl* CListCtrl::GetHeaderCtrl().

 

Параметры просмотра списка

Как уже упоминалось, стиль просмотра списка можно менять в любое время уже после его создания. Соответствующие функции для этого библиотекой MFC не предусмотрены, поэтому следует использовать функции API — :: Get Window Long и ::SetWindov>Long. Следующий пример показывает, как это сделать.

void CSaturnListCtrl::SetStyle(DWORD dwViewStyle) 

{

ASSERT(::IsWindow(m_hWnd));

// Получаем текущие стили окна

DWORD dwStyle = GetWindowLong(m_hWnd, GWL_STYLE);

// Устанавливаем стиль, если только он не был задан ранее

if ((dwStyle & LVSJTYPEMASK) != dwViewStyle) 

SetWindowLong(m_hWnd, GWL_STYLE,

(dwStyle & -LVSJTYPEMASK) |  dwViewStyle); 

}

Для работы с дополнительными стилями в классе CListCtrl реализованы функции SetExtendedStyle и SetExtendedStyle.

Кроме того, класс CListCtrl предусматривает возможность установки и изменения цвета фона списка просмотра. Следующая пара функций предназначена для получения значения текущего цвета фона (GetBkColor) и установки нового (SetBkColor). Если значение текущего цвета равно CLR_NONE, т. е. цвет фона не задан, перерисовка списка просмотра осуществляется значительно медленнее, чем при задании цвета.

Цвет фона — не единственный цвет, значение которого можно получить или изменить. Функции GetTextColor и SetTextColor используются для работы с цветом букв текста, функции GetTextBkColor и SetTextBkColor — для работы с цветом фона текстовых строк, а функции GetBklmage и SetBklmage — для работы с цветом фона изображений.

 

Работа со списком в целом

Мы рассмотрим только несколько функций данной группы.

BOOL CListCtrl::RedrawIterns (

 int nFirst,

 int nLast)

Заставляет элемент "просмотр списка" перерисовать записи с индексами, лежащими в диапазоне от nFirst и до nLast включительно. Однако указанная группа записей не перерисовывается немедленно — это происходит по получении "списком сообщения WM_PAINT. Для немедленной перерисовки конкретной записи предназначена функция Update.

BOOL CListCtrl:rUpdate (int nltem)

Перерисовывает заданную запись списка. При этом возможно переупорядочивание записей (если просмотр списка имеет стиль LVS_AUTOARRANGE).

BOOL CListCtrl::Arrange (UINT nCode)

Инициирует переупорядочивание записей. Способ упорядочивания задается параметром nCode, который может принимать одно из следующих значений:

LVA_ALIGNLEFT

 Записи должны быть выровнены по левому краю окна

LVA_ALIGNTOP 

Записи должны быть выровнены по верхнему краю окна

LVA_DEFAULT 

Записи должны быть переупорядочены в соответствии с текущим установленным типом выравнивания

LVA_SNAPTOGRID

 Пиктограммы должны быть помещены в ближайшую точку сетки для выравнивания  Указанные значения могут комбинироваться также со следующими:

LVA_SORTASCENDING 

Сортировка записей в порядке возрастания

 LVA_SORTDESCENDING 

Сортировка записей в порядке убывания

В режиме "просмотр списка" реализована поддержка "рабочих областей" — прямоугольных виртуальных областей, используемых для упорядочивания элементов просмотра списка. В этом случае перемещение элементов возможно только внутри области. Каждая из таких областей может иметь свои горизонтальные полосы прокрутки.

Для создания (а также переопределения) рабочей области необходимо вызвать функцию

void CListCtrl::SetWorkAreas(

int nWorkAreas,

LPRECT prc)

Параметр nWorkAreas определяет номер создаваемой рабочей области, а ргс указывает на адрес памяти, где записаны ее координаты (в единицах клиентской области).

 Примечание 

Создавать рабочие области можно только для режимов больших и малых пиктограмм.

Получить координаты конкретной рабочей области можно с помощью функции

void CListCtrl::GetWorkAreas

int nWorkAreas, 

LPRECT prc) 

Параметры такие же, как у функции SetWorkAreas.

Общее число созданных в элементе "просмотр списка" рабочих областей можно узнать, воспользовавшись функцией Get Number OJWorkAreas. Для одного списка просмотра можно создавать до LV_MAX_WORKAREAS (на настоящий момент равно 16) рабочих областей.

Разные рабочие области могут пересекаться. При этом элемент будет относиться к той области, индекс которой меньше.

 

Списки изображений

Пиктограммы для записей элемента "просмотр списка" содержатся в списках изображений, которые необходимо создать и определить, если требуется сопровождать записи пиктограммами. Один список изображений содержит полноразмерную пиктограмму (для режима больших пиктограмм), второй — пиктограммы меньшего размера (для остальных режимов). Можно определить и третий список, который будет отображать определяемые приложением состояния записей.

Для работы со списком изображений предназначены две функции:

CImageList* CListCtrl::GetlmageList (int nlmageList)

 и

CImageList* CListCtrl::SetlmageList

CImageList* plmageList,

int nlmageList)

Первая возвращает указатель на текущий список изображений, а вторая устанавливает текущий новый список изображений. Параметр nlmageList в обеих функциях может принимать одно из следующих значений:

LVSIL_NORMAL   Список изображений с большими пиктограммами

LVSIL_SMALL    Список изображений с малыми пиктограммами

LVSIL_STATE Список с изображениями для отображения состояний

Наличие пиктограмм только одного размера (только малых или только больших) позволяет при помощи масштабирования использовать имеющиеся пиктограммы для всех режимов вывода. В любом случае для поддержки всех режимов отображения требуется создание обоих списков изображений, причем важно, чтобы они имели одинаковое количество изображений, и соответствующие изображения располагались в одинаковой последовательности, поскольку одно и то же значение (номер по порядку) используется для идентификации необходимой пиктограммы в обоих списках.

Если список состояний изображений задан, то элемент "просмотр списка" резервирует место слева от каждой пиктограммы в записи для изображения состояния. Можно использовать эти изображения, например, выбрана/не выбрана, для индикации состояния записей, которые определяются приложением. Ненулевое значение в битах с 12 по 15 задает индекс изображения состояния, нуль обозначает отсутствие изображения состояния. В случаях, когда элемент "просмотр списка" использует режим вывода "большие пиктограммы", изображения состояний обычно не используются.

По умолчанию, когда уничтожается элемент "просмотр списка", уничтожаются и его списки изображения. Однако если элемент имеет стиль LVS_SHAREIMAGELISTS, то приложение само отвечает за уничтожение списка изображений в случае, когда он больше не нужен. Этот стиль, например, следует указывать, если один список изображений используется для нескольких просмотров списка.

BOOL CListCtrl::GetCheck(int nltem)

и

BOOL CListCtrl::SetCheck(

int nltem,

BOOL fCheck = TRUE)

Позволяют получить и, соответственно, установить состояние (видимое — fCheck = TRUE, или невидимое — fCheck= FALSE) изображения элемента.

 

Виртуальные списки

Виртуальный список — это список, число записей которого неограниченно. Список такого типа удобно использовать при работе с базами данных. Для его создания необходимо вызвать одну из функций Create Window или CreateWindowEx, передав в качестве части параметра dwStyle значение LVS_OWNERDATA, которое может использоваться с большинством стилей, кроме LVS_SORTASCENDING и LVS_SORTDESCENDING. Кроме того, автоматически устанавливается стиль LVS_AUTOARRANGE.

В рассматриваемом классе для работы с виртуальными списками есть функция

void CListCtrl::SetltemCountEx(

int iCount,

DWORD dwFlags = LVSICF_NOINVALIDATEALL)

Устанавливает число элементов в списке — параметр iCount. Параметр dwFlags позволяет определить поведение виртуального списка после установки нового размера. Возможны комбинации следующих значений:

LVSICF_NOINVALIDATEALL 

Элемент управления не перерисовывается до того, как не обработается какой-либо его компонент. Устанавливается по умолчанию

LVSICF_NOSCROLL

 Позиция отображаемого элемента не изменяется

 

Основные и дополнительные поля

Каждая запись просмотра списка может состоять из изображения, показывающего текущее состояние записи, пиктограммы, основного поля (собственно записи), и значения, определяемого приложением. С каждой записью может быть связано одно или более дополнительных полей. Дополнительное поле — это текстовая строка, которая в режиме таблицы (см. выше раздел "Режимы вывода") может быть выведена в столбце справа от пиктограммы и основного поля. Все записи в одном просмотре списка могут иметь одинаковое количество полей. Используя сообщения элемента "просмотр списка", можно добавлять, изменять, получать информацию и удалять записи. Можно также находить запись с заданными атрибутами.

Структура LVITEM определяет записи, включая основное поле, и дополнительные поля списка. Поле структуры iltem задает индекс записи (нумерация начинается с нуля), iSubltem задает индекс дополнительного поля (нумерация начинается с единицы). Дополнительные поля задают текст, номер пиктограммы из списка изображений, состояние и данные записи. Под данными записи здесь понимается определяемое приложением значение, связанное с конкретной записью в списке.

Записи по запросу (Callback Items)

Записи по запросу (иначе, записи обратного вызова) — это записи, для которых приложение (а не элемент управления) сохраняет текст или пиктограммы, или то и другое. Несмотря на то, что элемент "просмотр списка" может сохранять их, возможно создание записей по запросу, например, для исключения дублирования информации, в частности, текста. Информация для таких записей будет запрашиваться по мере необходимости. Для реализации этого механизма необходимо предусмотреть обработку уведомлений LVN_GETDISPINFO и LVN_GETDISPINFO.

Кроме того, приложение может взять на себя (временно или постоянно) обработку ряда событий. В этом случае речь идет о задании или изменении маски обратного вызова всего элемента управления. Маска определяет, какие биты состояния записей поддерживаются приложением. По умолчанию маска обратного вызова равна нулю — это означает, что элемент управления самостоятельно отслеживает все изменения состояния записей.

Для получения текущей маски обратного вызова или ее изменения используются две следующие функции:

UINT CListCtrl::GetCallbackMask ()

И

BOOL CListCtrl::SetCallbackMask (UINT nMask)

Параметр nMask, a также возвращаемое значение функции GetCallbackMask могут содержать любую комбинацию следующих значений:

LVIS_CUT 

Требует обработки выбора записи списка для выполнения операции вырезания и вставки (cut-and-paste)

LVIS_DROPHILITED 

Требует обработки назначения целевой записи списка для выполнения операции переноса (drag-and-drop)

LVIS_FOCUSED

 Требует обработки получения фокуса записи списка

LVIS_SELECTED

 Требует обработки при выборе записи списка

LVIS_OVERLAYMASK 

Приложение берет на себя выбор индекса пиктограммы из списка изображений для каждого элемента списка

LVIS_STATEIMAGEMASK

 Приложение берет на себя выбор индекса пиктограммы из списка изображений состояний для каждого элемента списка

 

Изменение содержимого списка

Следующая группа функций предназначена для работы с конкретными записями, а также для их добавления, изменения и удаления.

int CListCtrl::GetltemCount ()

Возвращает число записей в списке.

void CListCtrl::SetltemCount (int nltems)

Подготавливает список к добавлению большого количества новых записей. При этом перед добавлением записей элемент управления один раз производит перераспределение памяти, вместо того, чтобы делать это всякий раз при добавлении новой записи.

UINT CListCtrl::GetSelectedCount ()

Возвращает число выбранных записей в списке (для списков множественного выбора).

int CListCtrl::SetSelectionMark(int ilndex; 

и

int CListCtrl::GetSeleotionMark()

Устанавливает / возвращает индекс первого выделенного элемента при множественном выборе.

После вставки новой записи ее атрибуты можно запросить или изменить. Для этого применяются функции, использующие структуру LV_ITEM:

BOOL CListCtrl::GetItem (LV_ITEM* pltem)

Возвращает атрибуты записи в списке. Для получения текстовых строк следует предусмотреть буфер и указать его размер. В случае, если запись содержит строку большего размера, чем размер буфера, она будет усечена.

BOOL CListCtrl::SetItem (const LV_ITEM* pltem)

 И

BOOL CListCtrl::Setltem (

 int nltem,

int nSubltem,

UINT nMask,

LPCTSTR Ipszltem,

int nlmage, 

UINT nState, 

UINT nStateMask, 

LPARAM IParam) 

Устанавливают все или только некоторые атрибуты записи в просмотре списка.

Следующие функции позволяют работать с частью атрибутов записи списка без использования структуры LV_ITEM и громоздких функций, описанных ранее.

Для получения и назначения 32-битного значения, ассоциированного с записью, предназначены следующие функции:

DWORD CListCtrl::GetltemData (int nltem)

Возвращает значение, связанное с записью.

BOOL CListCtrl::SetltemData (

 int nltem, 

DWORD dwData)

 Связывает новое значение с записью.

Для получения и программного изменения состояния записи предназначены следующие функции:

UINT CListCtrl::GetltemState

int nltem,

UINT nMask)

 Возвращает текущее состояние записи в просмотре списка.

BOOL CListCtrl::SetltemState (

int nltem,

LV_ITEM* pltem)

И

BOOL CListCtrl::SetltemState (

int nltem,

UINT nState,

UINT nMask)

Изменяют текущее состояние записи в просмотре списка.

Для получения копии текстовой строки записи или, наоборот, установки нового текста используются функции GetltemText и SetltemText.

Следующие функции предназначены для управления положением записи в окне просмотра списка:

BOOL CListCtrl::GetltemPosition (

 int nltem, 

LPPOINT IpPoint) 

Возвращает расположение заданной записи в просмотре списка.

BOOL CListCtrl::SetltemPosition (

 int nltem, 

LPPOINT IpPoint) 

Помещает запись в заданную позицию в просмотре списка.

 Примечание 

В разных режимах позиция определяется по-разному, например, в режиме больших пиктограмм начало координат определяется левым верхним углом всего поля, требуемого для вывода всех записей, даже если это поле смещено вверх относительно окна просмотра списка; в режиме таблицы начало координат определяется уже самим окном просмотра списка, и позиция записи может иметь, в том числе, и отрицательные значения по каждой из осей.

 

Поиск и сортировка записей

Первая функция этой группы предназначена для нахождения записи с определенными свойствами:

int CListCtrl::GetNextltem (

int nltem, 

int nFlags)

В случае успешного завершения возвращает индекс найденной записи, иначе -1. Параметр nltem задает индекс записи, с которой начинается поиск, сама она из поиска исключается. Если задано значение —1, то поиск идет с начала списка. Параметр nFlags задает пространственное расположение записи, поиск которой осуществляется, и ее состояние. Расположение может определяться следующими флагами:

LVNI_ABOVE     Поиск идет вверх от указанной записи

LVNI_ALL       Поиск идет по индексам (по умолчанию)

LVNI_BELOW     Поиск идет вниз от указанной записи

LVNI_TOLEFT    Поиск идет влево от указанной записи

LVNI_TORIGHT   Поиск идет вправо от указанной записи

Состояние может не указываться или быть комбинацией следующих флагов:

LVNI_DROPHILITED 

Запись имеет установленный флаг LVIS_DROPHILITED

LVNI_FOCUSED 

Запись имеет установленный флаг LVIS_FOCUSED

LVNI_HIDDEN

Запись имеет установленный флаг LVIS_HIDDEN

LVNI_MARKED

 Запись имеет установленный флаг LVIS_MARKED 

LVNI_SELECTED 

Запись имеет установленный флаг LVIS_SELECTED

Если у записи, которая проверяется на соответствие заданным условиям, установлены не все заданные в параметре nFlags флаги, то поиск продолжается со следующей записи.

POSITION CListCtrl::GetFirstSelectedltemPosition()

и

int CListCtrl::GetNextSelectedItem(POSrnON& pos)

Позволяют получить индекс, соответственно, первого и последующих элементов просмотра списка. Если функции возвращают NULL, то в элементе управления нет выбранных элементов.

int CListCtrl:rFindltem (

LV_FINDINFO* pFindlnfo,

int nStart = -1)

Осуществляет поиск строки, имеющей заданные характеристики, и возвращает индекс найденной строки или —1. Параметр nStart задает индекс записи, с которой должен начаться поиск, при этом сама она из поиска исключается, или -1, если нужно искать с начала списка. Параметр pFindlnfo является указателем на структуру LV_FINDINFO, содержащую информацию для осуществления поиска.

Структура LV_FINDINFO имеет следующий вид:

typedef struct tag LV_FINDINFO {

UINT flags;

LPCSTR psz;

LPARAM IParam;

POINT pt;

UINT vkDirection; 

} LV_FINDINFO;

Поле flags задает тип поиска. Оно может содержать комбинацию следующих флагов:

LVFI_PARAM 

При поиске используется значение, содержащееся в поле IParam, если задано данное значение, то все другие значения игнорируются

LVFI_PARTIAL 

Поиск ведется до совпадения строки, заданной в поле psz, и текстового значения записи, т. е. его основного поля; длина текста записи может быть больше, чем строка, заданная в поле psz; этот параметр предполагает использование также флага LVFI_STRING

LVFI_STRING

 Поиск ведется до совпадения строки, заданной в поле psz, и текстового значения записи, т. е. его основного поля; если дополнительно не задан флаг LVFI_ PARTIAL, то поиск идет до полного совпадения проверяемой строки и строки, указываемой параметром psz

LVFI_WRAP 

Продолжение поиска с начала списка, если строка не найдена по достижению его конца

LVFI_NEARESTXY 

Задает поиск ближайшей в пространстве поля списка записи от позиции, заданной индексом записи

Поле psz является указателем на текстовую строку, которая должна быть найдена, если в параметре flags заданы значения LVFI_STRING или LVFI_PARTIAL. Поле IParam задает 32-битное значение, которое сравнивается со значениями, ассоциированными с элементами списка. Начальная точка поиска задается параметром pt, а направление — параметром vkDirection. Последние два параметра используются только с флагом LVFI_NEARESTXY.

Просмотр списка в ряде случаев никак не реагирует на действия мышью, если только при этом не происходит изменение состояния его записей. Однако класс CListCtrl позволяет определять запись по координатам, и если есть необходимость узнать, например, в обработчике нажатия кнопки мыши, на какой записи находится ее курсор, следует воспользоваться одной из функций HitTest или SubhemHitTest. При этом надо иметь в виду, что из-за зависимости "системы координат" списка от режима отображения в случае, когда все элементы просмотра списка не могут быть одновременно отображены на экране, для правильной работы этих функций следует осуществлять преобразование координат.

int CListCtrl::HitTest (LV_HITTESTINFO* pHitTestlnfo) 

ИЛИ

int CListCtrl::HitTest (

CPoint pt,

UINT* pFlags = NULL)

 И

int CListCtrl::SubItemHitTest(LPLVHITTESTINFO pInfo);

Возвращают индекс записи (подзаписи) в просмотре списка, расположенной в заданных координатах, если такой записи нет, функция возвращает -1. Обе версии функции HitTest отличаются только способом передачи данных для поиска. Структура LV_HITTESTINFO, указатель на которую передается в параметре pHitTestlnfo, содержит поля, аналогичные параметрам второй версии функции HitTest. Описание структуры следующее:

typedef struct _LV_HITTESTINFO {

POINT pt;

UINT flags;

int iItem;

int iSubltem; 

} LV_HITTESTINFO;

Поле pt задает координаты точки, которая предположительно находится в рамках занимаемой некоторым элементом области. Поле flags используется для получения дополнительной информации и может быть заполнено комбинацией из следующих флагов:

LVHT_ABOVE 

Проверяемая точка расположена выше клиентской области элемента управления

LVHT_BELOW 

Проверяемая точка расположена ниже клиентской области элемента управления

LVHT_NOWHERE

 Проверяемая точка расположена в клиентской области просмотра списка, но лежит не в области собственно списка

LVHT_ONITEMICON

 Проверяемая точка расположена в границах пиктограммы записи

LVHT_ONITEMLABEL 

Проверяемая точка расположена в границах надписи записи

LVHT_ONlTEMSTATEICON 

Проверяемая точка расположена в границах пиктограммы состояния записи

LVHT_TOLEFT 

Проверяемая точка расположена левее клиентской области элемента управления

LVHT_TORIGHT

 Проверяемая точка расположена правее клиентской области элемента управления

Флаги LVHT_ABOVE, LVHT_BELOW, LVHT_TOLEFT и LVHT_TORIGHT можно использовать для определения необходимости прокрутки списка в соответствующем направлении. Значение LVHT_ONITEM можно использовать для определения того, находится ли вообще точка над записью, это значение является комбинацией значений LVHT_ONITEMICON, LVHT_ONITEMLABEL и LVHT_ONITEMSTATEICON. Поля Ittern и iSubHem определяют индекс записи и подзаписи в просмотре списка.

Следующая функция осуществляет сортировку записей в просмотре списка:

BOOL CListCtrl::SortItems (

PFNLVCOMPARE pfnCompare,

DWORD dwData)

Использует определяемую приложением функцию для сравнения записей. Индексы записей при этом изменяются, отражая новый Порядок записей в списке. Параметр pfnCompare указывает на определяемую приложением функцию сравнения записей. Эта функция вызывается всякий раз в процессе сортировки, когда необходимо сравнить две записи. Второй параметр позволяет передавать в функцию сравнения некоторое значение в качестве третьего параметра. Вот как должна быть описана функция сравнения:

int CALLBACK CompareFunc(

LPARAM IParaml, 

LPARAM lParam2, 

LPARAM IParamSort)

Первые два параметра содержат 32-битные данные, ассоциированные со сравниваемыми записями. Последний параметр является копией значения параметра dwData функции CListCtrl::Sortltems. Функция возвращает отрицательное значение, если первая запись должна предшествовать второй записи, положительное значение, если первая запись должна быть расположена после второй записи, и нуль, если записи эквивалентны.

 

Редактирование надписей записей

Поддержка пользовательского редактирования надписей записей осуществляется заданием стиля LVS_EDITLABELS и обработкой уведомления LVN_ENDLABELEDIT, что и отражено в сопровождающем раздел примере. Обработчик уведомления получает указатель на новую строку, которую ввел пользователь. Для изменения предыдущего наименования следует самостоятельно заменить содержимое записи, а также при необходимости произвести дополнительные действия, например, при изменении копии имени файла, содержащейся в записи просмотра списка, требуется также изменить его имя и на диске или ином носителе. В случае, если значение указателя равно нулю, — пользователь отменил изменения, нажав клавишу <Esc> — ничего делать не надо.

Класс CListCtrl предоставляет дополнительные функции, расширяющие возможности поддержки процесса редактирования надписей записей.

CEdit*- CListCtrl::GetEditControl ()

Возвращает дескриптор редактора текста, используемого для редактирования надписи записи. В случае удачного завершения возвращается указатель на объект класса CEdit, в случае неудачи — NULL.

CEdit* CListCtrl::EditLabel (int nltem)

Позволяет программно инициировать редактирование надписи записи в просмотре списка, если просмотр списка имеет стиль LVSJEDITLABELS.

 

Обработка уведомлений

Когда пользователь щелкает на колонке заголовков, перемещает пиктограммы, редактирует надписи и т. п., элемент "просмотр списка" посылает родительскому окну уведомления, которые можно обрабатывать в функции родительского окна OnNotify.

Элемент "просмотр списка" посылает следующие уведомления:

LVN_BEGINDRAG

 Сигнализирует о начале операции перемещения при помощи левой кнопки мыши (drag-and-drop)

LVN_BEGINLABELEDIT 

Сигнализирует о начале операции редактирования надписи записи

LVN_BEGINRDRAG

 Сигнализирует о начале операции перемещения при помощи правой кнопки мыши (drag-and-drop)

LVN_COLUMNCLICK 

Сигнализирует о том, что пользователь нажал кнопку мыши, когда курсор находился на элементе заголовка в режиме таблицы

LVN_DELETEALLITEMS

 Сигнализирует об удалении всех записей просмотра списка

LVN_DELETEITEM

 Сигнализирует об удалении конкретной записи просмотра списка

LVN_ENDLABELEDIT 

Сигнализирует о завершении операции редактирования надписи записи

LVN_GETDISPINFO 

Запрашивает информацию о записи по запросу просмотра списка для ее отображения

LVN_INSERTITEM 

Сигнализирует о добавлении новой записи к просмотру списка

LVN_ITEMCHANGED

 Сигнализирует об изменении записи в просмотре списка

LVN_ITEMCHANGING 

Запрашивает подтверждение изменения записи в просмотре списка

LVN_KEYDOWN 

Сигнализирует о нажатии клавиши на клавиатуре

LVN_SETDISPINFO 

Уведомляет родительское окно о необходимости изменения информации о записи по запросу, которая предоставляется приложением

Обработка уведомлений происходит так же, как и для других элементов управления, за исключением того, что приложение получает информацию в структуре LV_DISPINFO, которая, кроме обычного для уведомлений заголовка, содержит структуру LV_ITEM со всей необходимой для обработки информацией:

BOOL CMercuryWnd::OnNotify(

WPARAM wParam, 

LPARAM IParam, 

LRESULT* pResult)

{

...

LV_DISPINFO *pdi = (LV_DISPINFO *)IParam; 

LV_ITEM item = pdi->item;

...

}

Некоторые уведомляющие сообщения работают с другой структурой, а именно, со структурой NM_LISTVIEW:

NM_LISTVIEW *plv = (NM_LISTVIEW *•) IParam;

 

Реализация просмотра списка с 

возможностью перемещения записей

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

не будут отражать их фактический порядок на экране — порядок размещения записей в просмотре списка автоматически не меняется.

Для поддержки перемещения записей в просмотре списка требуется обрабатывать следующие уведомления: LVN_BEGINDRAG, LVN_BEGINRDRAG, а также сообщения от мыши: отпускание левой и правой кнопок и перемещение курсора.

Кроме того, следует создать еще один список изображений, который будет содержать элемент для визуализации перемещения записи списка при помощи функции CreateDraglmage:

CImagelist* CListCtrl::CreateDraglmage (

int nltem,

LPPOINT IpPoint)

Параметр nItem задает индекс записи, а параметр IpPoint — текущие координаты ее левого верхнего угла.

Порядок расположения столбцов можно узнать и установить с помощью функций

BOOL CListCtrl::GetColumnOrderArray( .

LPINT piArray,

 int iCount = -1) 

И

BOOL CListCtrl::SetColumnOrderArray

int iCount, 

LPINT piArray)

Параметр piArray получает указатель на массив значений индексов столбцов (которые расположены в новом порядке — для функции SetColumnOrderArray). Число столбцов задается параметром iCount. Если в функции GetColumnOrder-Arrау этот параметр равен —1, то число столбцов определяет сама библиотека.

 

Переопределяемые функции

Для реализации самоотображаемого списка следует переопределить функцию Dmwltem, которая, как и для других элементов управления, получает всю необходимую информацию для осуществления рисования записей в просмотре списка.

Пример реализации просмотра списка

В этом разделе приведен пример реализации просмотра списка, в качестве записей которого используется информация о файлах текущего (для приложения) каталога, а именно — имя, тип (определяемый по расширению), длина в килобайтах, а также дата и время последней операции записи.

BOOL CMyPropertyPage4::OnSetActive() 

{

static BOOL bFlag = TRUE;

if(bFlag)

{

// Создание и заполнение списков изображений

 m_ImageNormal.Create(32, 32, TRUE, I, 1);

 m_ImageNormal.Add(AfxGetApp()->LoadIcon(IDI_FILE));

 m_ImageSmall.Create(16, 16, TRUE, 1, 1);

 m_ImageSmall.Add(AfxGetApp()-XLoadlcon(IDI_FILE));

 m_List = (CListCtrl *)GetDlgItem(IDC_LIST_CTRL);

 m_List->Set!mageList(&m_ImageNormal, LVSIL__NORMAL);

 m_List->Set!mageList(&m_ImageSmall, LVSIL_SMALL); 

CRect rect;

m_List->GetClientRect(rect);

 // Добавление в список просмотра

 // четырех колонок, для реализации 

// режима таблицы

 m_List->InsertColumn(0, _Т("Name"),

LVCFMT_LEFT, rect.Width() / 3);

 m_List->InsertColumn(l, JTC'Type"),

LVCFMT_LEFT, 2 * rect.Widthf) / 9);

 m_List->InsertColumn(2, _T("Size"),

LVCFMT_RIGHT, rect.Width() / 9) ;

 m_List->InsertColumn(3, _T("Modified"),

LVCFMT_LEFT, rect.Width() /3);

 // Поиск файлов и создание записей с

 // информацией о найденных файлах

 WIN32_FIND_DATA FindFileData;

HANDLE hFile = FindFirstFileC'*.*", SFindFileData); 

if (hFile != INVALID_HANDLE_VALUE) 

{

LV_ITEM item; item.iltem = 0;

 item.iImage = 0; 

CString csText; 

SYSTEMTIME stTime; 

do

 {

// Директории игнорируем

if (FindFileData.dwFileAttributes &

FILE_ATTRIBUTE_DIRECTORY)

 continue;

 item.mask = LVIFJTEXT |  LVIF_IMAGE |  LVIF_PARAM;

// Для реализации сортировки сохраняем дополнительно

// информацию о найденном файле

WIN32_FIND_DATA *lpFindFileData = new WIN32_FIND_DATA;

*lpFindFileData = FindFileData;

item.lParam = (DWORD)IpFindFileData;

// Имя файла

item.iSubltem = 0;

item.pszText = FindFileData.cFileName;

m_List->InsertItem(&item);

item.mask = LVIFJTEXT;

// Тип файла

item.iSubltem = 1;

csText=(strrchr(FindFileData.cAlternateFileName, '.') + 1) ;

csText += " File";

item.pszText = (LPTSTR)(LPCTSTR)csText;

m_List->SetItem(sitem);

// Длина файла

item.iSubltem = 2;

csText.Format("%dKB", FindFileData.nFileSizeLow / 1024);

item.pszText = (LPTSTR)(LPCTSTR)csText; i

m_List->SetItem(Sitem);

// Дата и время последней записи в файл

item.iSubltem = 3;

FileTimeToSystemTime(SFindFileData.ftLastWriteTime,

SstTime);

csText = CTime(stTime).Format(" %d/%m/%Y %H:%M:%S");

 item.pszText = (LPTSTR)(LPCTSTR)csText;

 m__List->SetItem(&item) ;

  item.iltem++;

}

 while (FindNextFile(hFile, SFindFileData));

 } .

bFlag = FALSE;

 }

OnLargeicons();

return CPropertyPage::OnSetActive (); 

}

// Обработчики команд

 void CMyPropertyPage4::OnSmallIcon() 

{

SetStyle(LVS_SMALLICON); }

void CMyPropertyPage4::OnLargeicons() 

{

SetStyle(LVS_ICON); 

}

void CMyPropertyPage4::OnDetails() 

{

SetStyle(LVS_ICON ! LVS_REPORT); 

}

void CMyPropertyPage4::OnLists() 

{

SetStyle(LVS_LIST);

 }

// Функция изменения стиля для просмотра списка

 // при смене режимов отображения

void CMyPropertyPage4::SetStyle(DWORD dwViewStyle) 

{

DWORD dwStyle = GetWindowLong(m_List->m_hWnd, GWL_STYLE);

if ((dwStyle & LVSJTYPEMASK) != dwViewStyle) 

SetWindowLong(m_List->m_hWnd, GWL_STYLE,

(dwStyle & -LVSJTYPEMASK) |  dwViewStyle); 

{

// т. к. бьло динамическое вьделение памяти 

// для хранения информации о файлах, 

// при уничтожении списка просмотра 

// необходимо освободить эту память 

BOOL CMyPropertyPage4::DestroyWindow() 

 {

for (int i = 0; i < m_List->Get!temCount(); i++)

 delete (WIN32_FIND_DATA *)m_List->Get!temData(i);

return CPropertyPage::DestroyWindow(); 

}

Внешний вид страницы представлен на рис. 16.8.

Рис. 16.8. Окно свойств с просмотром списка в режиме больших пиктограмм

 

Просмотр дерева

Кроме возможности выводить записи последовательных списков при помощи таких элементов, как список или просмотр списка, Windows предоставляет возможность отображения (и управления) списков, организованных по принципу дерева. Для этого поддерживается элемент управления "просмотр дерева" (TREE VIEW). Первое, для чего этот элемент использует система Windows — это организация просмотра дерева каталогов, а также различных индексов в справочных файлах и т. п.

Древовидные структуры предполагают наличие некоторой иерархии узлов и поэтому естественно, что данный элемент управления может применяться только для информации, имеющей иерархическую структуру. Для создания этого элемента в библиотеке MFC предназначен класс CTreeCtrl. Каждая запись в просмотре дерева состоит из текстовой строки (надписи) и заданного битового изображения и может иметь одну или более подзаписей, связанных с ней. Щелкнув мышью по записи, пользователь может развернуть или свернуть список связанных с записью подзаписей. На экране подзаписи могут соединяться с записью линиями для того, чтобы наглядно представлялась их взаимосвязь.

Любая запись просмотра дерева может иметь связанные с ней подзаписи, которые называются дочерними записями, а сама запись называется родительской записью. При выводе на экран дочерние записи изображаются ниже родительской и со сдвигом в сторону для наглядного представления их зависимости от родительской записи. Запись, которая не имеет родительской и, следовательно, лежит на самом верхнем уровне иерархии, называется корневой записью.

Кроме класса CTreeCtrl, библиотека MFC содержит еще и класс CTree View. Класс CTreeCtrl предназначен для реализации просмотра дерева в блоке диалога, особенно при наличии в блоке диалога других элементов управления. Напротив, если просмотр дерева должен быть реализован в архитектуре "документ/представление" и будет занимать собой все окно, автоматически изменяя свои размеры при изменении размеров родительского окна, обрабатывать сообщения от меню и т. п., то необходимо использовать класс CTree View.

 

Создание элемента управления "просмотр дерева"

Для начала просмотр дерева необходимо создать. Если он задан в шаблоне блока диалога, то он будет автоматически создан при создании блока диалога. Если же он создается как дочернее окно в другом окне, то необходимо воспользоваться функцией Create для создания соответствующего элемента управления Windows и связывания его с объектом класса CTreeCtrl.

Списки изображений

Каждая запись в просмотре дерева может иметь две ассоциированных с ней пиктограммы. Первая пиктограмма выводится на экран, когда запись развернута, вторая — когда запись свернута (например, пиктограммы, изображающие открытый и закрытый каталог).

Кроме того, приложение может задать список изображений для обозначения состояний записи. В этом случае элемент "просмотр дерева" резервирует место слева от записи для размещения данного изображения.

Для работы с изображениями, связанными с просмотром дерева, предназначены функции:

CImageList* CTreeCtrl::GetlmageList (UINT nlmage)

Возвращает указатель на список изображений, связанных с просмотром дерева, или NULL — в случае неудачного завершения. Параметр nlmage задает тип списка и может принимать одно из следующих значений:

TVSIL_NORMAL

 Предписывает возвращать список обычных изображений, который содержит изображения для выбранных и невыбранных записей

TVSIL_STATE

 Предписывает возвращать список изображений, который содержит изображения для определяемых пользователем состояний

CImageList* CTreeCtrl::SetTmageList .(

 CImageList* plmageList, 

int nlmageListType)

Устанавливает новый список изображений для просмотра дерева, тип списка задается параметром nlmageListType.

 

Функции для работы с просмотром дерева в целом

Когда ветка дочерних записей развернута, то они изображаются с некоторым сдвигом относительно родительской записи. Для определения и изменения сдвига дочерней записи относительно родительской предназначены функции:

UINT CTreeCtrl::GetIndent ()

Возвращает сдвиг (в пикселах) дочерней записи относительно родительской.

void CTreeCtrl::SetIndent (UINT nШndent)

Задает сдвиг (в пикселах) дочерней записи относительно родительской. Если задается значение меньшее, чем определяемое системой, то функция игнорируется.

 

Изменения содержимого дерева

Следующая группа функций предназначена для работы с конкретными записями, а также для их добавления, изменения и удаления.

UINT CTreeCtrl::GetCount ()

Возвращает число записей, вставленных в просмотр дерева. 

UINT CTreeCtrl::GetVisibleCount ()

Возвращает текущее количество видимых записей в просмотре дерева.

Для того чтобы удалить все записи из просмотра дерева, можно воспользоваться функцией DeleteAllltems.

Начальное положение записи при ее добавлении в просмотр дерева определяется с помощью функции Insertltem. При вызове этой функции задаются дескриптор родительской записи и дескриптор записи, после которой должна быть вставлена новая запись. Второй из указанных дескрипторов должен быть дескриптором одной из дочерних записей, принадлежащих родительской записи, или принимать одно из следующих значений: TVI_FIRST, TVI_LAST или TVI_SORT. Значения TVI_FIRST и TVI_LAST определяют, что запись должна быть помещена первой или последней в списке дочерних записей родительской записи. Если задано значение TVI_SORT, то после вставки записи дочерние записи, принадлежащие данной родительской записи, будут отсортированы по алфавиту.

Функция, осуществляющая вставку новых записей, реализована в нескольких версиях:

TREEITEM CTreeCtrl::Insertltem (LPTV_INSERTSTRUCTlpInsertStruct)

HTREEITEM CTreeCtrl::Insertltem (

UINT nMask,

LPCTSTR Ipszltem,

int nlmage,

int nSelectedlmage,

UINT nState,

UINT nStateMask,

LPARAM IParam,

HTREEITEM hParent,

HTREEITEM hlnsertAfter) 

HTREEITEM CTreeCtrl::Insertltem (

LPCTSTR Ipszltem,

HTREEITEM hParent = TVI_ROOT,

HTREEITEM hlnsertAfter = TVI_LAST)

 HTREEITEM CTreeCtrl::Insertltem (

LPCTSTR Ipszltem,

int nlmage,

int nSelectedlmage,

HTREEITEM hParent = TVI_ROOT,

HTREEITEM hlnsertAfter = TVI_LAST)

Все версии этой функции вставляют новую запись в просмотр дерева и возвращают дескриптор вставленной записи. Первые два варианта практически аналогичны по своим возможностям. Два оставшихся обеспечивают просмотр дерева лишь частью информации о новой записи. Их целесообразно использовать в относительно простых реализациях, когда от просмотра дерева не требуется поддержка всех функциональных возможностей. Кроме того, существуют функции, позволяющие впоследствии дополнить информацию о записи.

Чтобы не дублировать описание, приведем более подробно только описание структур TV_INSERTSTRVCTu TV_ITEM, которые вместе содержат полный набор параметров, описывающих добавляемую запись и необходимых для реализации добавления.

Структура TV_INSERTSTRUCT имеет следующий вид:

typedef struct _TV_INSERTSTRUCT (

HTREEITEM hParent;

 HTREEITEM hlnsertAfter;

 lif (_WIN32_IE >= 0x0400) 

union {

TVITEMEX itemex; 

TVITEM item; 

} DUMMYUNIONNAME;

 else

TVITEM item; 

#endif 

} TV_INSERTSTRUCT;

Поле hParent задает дескриптор родительской записи. Если параметр равен TVI_ROOT или NULL, то запись вставляется как корневая. Поле hlnsertAfter задает дескриптор записи, после которой запись должна быть вставлена. Оно может принимать также следующие значения:

TVI_FIRST  Запись вставляется в начало списка

TVI_LAST   Запись вставляется в конец списка

TVI_SORT   Запись вставляется в список в алфавитном порядке

Поле item является структурой TVITEM, а поле HtemEx— структурой TVITEMEX. Именно в этом поле содержится информация о добавляемой записи.

Структура TV_ITEM имеет следующий вид:

typedef struct _TV_ITEM(

 UINT mask; 

HTREEITEM hltem;

 UINT state; 

UINT stateMask;

LPSTR pszText;

 int cchTextMax; 

int iImage;

 int iSelectedlmage;

 int cChildren;

 LPARAM IParam;

 } TV_ITEM;

Поле mask определяет, какие из оставшихся полей несут информацию, и может содержать комбинацию следующих значений:

TVIF_CHILDREN 

Поле cChildren содержит данные

TVIF_HANDLE

 Поле hItem содержит данные

TVIF_IMAGE 

Поле iImage содержит данные

TVIF_PARAM 

Поле IParam содержит данные

 TVIF_SELECTEDIMAGE 

Поле iSelectedlmage содержит данные

TVIF_STATE

 Оба поля state и stateMask содержат данные

TVIF_TEXT

 Поле pszText содержит данные

Поле hltem содержит дескриптор записи, информация о которой содержится в структуре. Разумеется, при добавлении новой записи этот дескриптор не задается, т. е. поле используется для запроса информации о существующей записи.

Поля state и stateMask задают текущее и возможные состояния для данной записи. Эти поля могут содержать комбинации следующих флагов (за исключением двух последних):

TVIS_BOLD 

Запись отображается полужирным шрифтом

TVIS_CUT 

Запись помечена для операции вырезания или вставки (cut-and-paste)

TVIS_DISABLED 

Запись недоступна и для ее перерисовки применяется стандартный стиль и цвет

TVIS_DROPHILITED

 Запись выделена для переноса (drag-and-drop)

TVIS_EXPANDED

 Список дочерних записей должен быть развернут так, чтобы дочерние записи были видимы; данное значение применимо только для родительской записи

TVIS_EXPANDEDONCE 

Список дочерних записей был развернут по крайней мере один раз; для родительской записи, имеющей данный стиль, уведомления TVNJTEMEXPANDING и TVNJTEMEXPANDED не посылаются; данное значение применимо только для родительской записи

TVIS_FOCUSED 

Запись имеет фокус ввода; естественно, что может быть выделено несколько записей одновременно, но фокус ввода может иметь только одна

TVIS_EXPANDPARTIAL 

Запись частично развернута TVIS_SELECTED Запись выбрана

TVIS_OVERLAYMASK

Подгружаемое изображение для записи включается, когда запись перерисовывается; индекс подгружаемого изображения должен быть задан в параметре state структуры TV/TE/И при использовании макроса INDEXTOOVERLAYMASK; подгружаемая запись должна быть добавлена в список изображений дерева с помощью функции ClmageList:SetOverlayImage; это значение не может применяться совместно с другими значениями

TVIS_STATEIMAGEMASK

 Изображение состояния записи включается, когда запись перерисовывается; индекс состояния записи должен быть задан в параметре slate структуры TVJTEM при использовании макроса INDEXTOSTATEIMAGEMASK; это значение не может применяться совместно с другими значениями

Поле pszText является указателем на строку, содержащую надпись записи, если заданы соответствующие атрибуты в структуре. Если параметр имеет значение LPSTR_TEXTCALLBACK, то это строка обратного вызова (подробнее об обратном вызове см. "Записи по запросу" в разделе, посвященном просмотрам списка), и родительское окно само отвечает за хранение текстовой строки. В этом случае просмотр дерева посылает родительскому окну уведомление TVN_GETDISPINFO, когда необходимо вывести на экран, отсортировать или отредактировать надпись, и уведомление TVN_SETDISPINFO, если надпись записи изменена. Если структура должна получать атрибуты записи, то это указатель на буфер, в который она получит надпись. Поле cchTextMax определяет размер буфера, на который указывает параметр pszText, если структура получает атрибуты записи. Если же она задает атрибуты записи, то данный параметр игнорируется. Поля Imаmе и iSelectedlmage задают индексы пиктограмм для невыбранного и выбранного состояний. Если значения одного или обоих полей равны IJMAGECALLBACK, то родительское окно отвечает за хранение изображений. Поле cChildren содержит количество дочерних записей, связанных с данной записью. Если значение данного поля равно I_CHILDRENCALLBACK, то родительское окно отвечает за перерисовку дочерних записей. Это поле заполняется функцией, возвращающей информацию о записи. Поле IParam задает некоторое 32-битное значение, ассоциируемое с записью.

Структура TV_ITEMEX имеет одно дополнительное поле в дополнение к полям структуры TV_ITEM int ilntegral , в котором задается высота записи в приращениях стандартной высоты. Кроме того, для поля mask может быть установлен еще один флаг — TVIF_INTEGRAL, открывающий доступ к полю ilntegral. Для удаления ранее вставленной записи используется функция:

BOOL CTreeCtrl::DeleteItem (HTREEITEM hltem)

Обратите внимание, что вместо индексов записей используются их дескрипторы. Если параметр hltem равен TVI_ROOT, то будут удалены все записи.

Для изменения или получения всех или части параметров записи предназначены следующие функции:

BOOL CTreeCtrl:rSetltem (TV_ITEM* pltem)

И

BOOL CTreeCtrl::SetItem (

HTREEITEM hltem,

UINT nMask,

LPCTSTR Ipszltem,

int nlmage,

int nSelectedlmage,

UINT nState,

UINT nStateMask,

LPARAM IParam)

Устанавливают параметры записи в просмотре дерева. Назначение и тип параметров аналогичны рассмотренным ранее.

BOOL CTreeCtrl::GetItern (TV_ITEM* pltem)

Возвращает параметры записи в структуре, на которую указывает параметр pltem.

Каждая запись в просмотре дерева имеет текущее состояние, например, она может быть выбрана, недоступна, развернута и т. д. -Во многих случаях элемент "просмотр дерева" автоматически устанавливает состояния записей, отражая действия, производимые пользователем. Для того чтобы узнать или изменить только состояние записи, можно воспользоваться следующими функциями:

UINT CTreeCtrl::GetltemState (

 HTREEITEM hltem, 

UINT nStateMask)

 Возвращает состояние записи.

BOOL CTreeCtrl::SetltemState

HTREEITEM hltem, 

UINT nState, 

UINT nStateMask) 

Устанавливает состояние записи.

Для того чтобы узнать или изменить только изображения, связанные с записью, можно воспользоваться следующими функциями:

BOOL CTreeCtrl::Getltemlmage

HTREEITEM hltem, 

ints nlmage, 

int& nSelectedlmage)

Записывает номера изображений в параметры nlmage и nSelectedlmage.

BOOL CTreeCtrl::Setltemlmage

HTREEITEM hltem, 

int nlmage,

int nSelectedlraage) 

Устанавливает изображения для заданной записи.

Для того чтобы узнать или изменить только надпись записи, можно воспользоваться следующими функциями:

CString CTreeCtrl::GetltemText (HTREEITEM hltem)

Возвращает объект типа CString, содержащий надпись записи.

BOOL CTreeCtrl::SetltemText (

HTREEITEM hltem, 

LPCTSTR Ipszltem)

Заменяет текущее название записи новым.

Следующая пара функций предназначена для получения и изменения 32-битного значения, ассоциированного с записью в просмотре дерева:

DWORD CTreeCtrl::GetltertData (HTREEITEM hltem) 

Позволяет получить 32-битное значение.

BOOL CTreeCtrl::SetltemData

HTREEITEM hltem,

 DWORD dwData) 

Устанавливает 32-битное значение, заданное параметром dwData.

В случае, когда необходимо получить размеры прямоугольника, ограничивающего запись в просмотре дерева, и узнать при этом, видима запись или нет, используется функция GetltemRect.

BOOL CTreeCtrl::GetltemRect (

HTREEITEM hltem,

LPRECT IpRect,

BOOL bTextOnly)

Возвращает TRUE, если запись hltem видима, в этом случае размеры прямоугольника содержатся в переменной IpRect. Координаты задаются относительно левого верхнего угла окна просмотра дерева. Если значение bTextOnly равно TRUE, то прямоугольник содержит только размеры надписи для данной записи. Иначе он определяет размеры прямоугольника, который содержит запись целиком.

Следующая описываемая функция возвращает значение TRUE, если указанная запись имеет дочерние записи:

BOOL CTreeCtrl::ItemHasChildren (HTREEITEM hltem)

Функцией GetChildltem стоит воспользоваться, если нужно получить дескриптор первой дочерней записи:

HTREEITEM CTreeCtrl::GetChildtItern (HTREEITEM hltem)

Записи могут быть свернуты или развернуты простым щелчком мыши. Если дочерние записи некоторой записи развернуты, то они выводятся ниже ее. Если они свернуты, то они на экран не выводятся. Переключение из одного состояния в другое происходит автоматически при двойном щелчке мыши на записи или нажатием на специальную кнопку, расположенную слева от родительской записи (если запись имеет стиль TVS_HASBUTTONS). Приложение также может разворачивать и сворачивать ветви дерева путем вызова функции Expand:

BOOL CTreeCtrl::Expand

HTREEITEM hltem, 

UINT nCode)

Сворачивает или разворачивает дочерние записи для родительской записи, заданной параметром hltem. Параметр nCode определяет, что нужно сделать с веткой дочерних записей, и может содержать одно из следующих значений:

TVE_COLLAPSE 

Сворачивание поддерева записей

TVE_COLLAPSERESET

 Сворачивание поддерева с одновременным удалением всех дочерних записей

TVE_EXPAND 

Разворачивание поддерева

TVE_TOGGLE 

Разворачивание поддерева, если оно свернуто, и cворачивание, если развернуто

Следующая функция только выбирает запись:

HTREEITEM CTreeCtrl::Select

HTREEITEM hltem, 

UINT nCode)

Осуществляет выбор записи с одновременной прокруткой элемента управления так, чтобы запись стала видимой. Параметр nCode определяет действия, которые следует произвести помимо собственно выбора записи. Возможны следующие значения этого параметра:

TVGN_CARET 

Установить выбор заданной записи

TVGN_DROPHILITE 

Перерисовать запись как запись назначения операции перетаскивания (drag-and-drop)

TVGN_FIRSTVISIBLE 

Прокрутить записи так, чтобы заданная запись стала видимой

Выполняемые данной функцией операции могут также выполняться следующей парой функций:

HTREEITEM CTreeCtrl::Selectltem (HTREEITEM hltem)

Выделяет заданную параметром hltem запись просмотра дерева.

HTREEITEM CTreeCtrl::SelectDropTarget (HTREEITEM hltem)

Перерисовывает заданную запись как запись назначения операции перетаскивания.

 

Поиск и сортировка записей дерева

Функции этой группы предназначены для поиска записей по различным критериям, но в отличие от последовательных списков для просмотра дерева поиск осуществляется по тому, как искомая запись соотносится с другими.

HTREEITEM CTreeCtrl::GetNextItem (

HTREEITEM hltem,

UINT nCode)

Возвращает дескриптор следующей записи, подходящей по заданным параметрам, или NULL, если таковая не найдена. Параметр hltem определяет дескриптор записи, относительно которой производится поиск. Параметр nCode задает критерии поиска и может содержать один из следующих параметров:

TVGN_CARET 

Вернуть текущую выбранную запись

TVGN_CHILD

 Вернуть первую дочернюю запись; в этом случае параметр hltem должен быть равен NULL

TVGN_DROPHILITE 

Вернуть запись, которая является конечной целью при операции перетаскивания записи просмотра дерева

TVGN_FIRSTVISIBLE

 Вернуть первую видимую запись

TVGN_NEXT 

Вернуть следующую запись, которая лежит на том же уровне, что и запись, относительно которой производится поиск

TVGN_NEXTVISIBLE 

Вернуть видимую запись, следующую за данной 

TVGN_PARENT 

Вернуть родительскую запись

TVGN_PREVIOUS

 Вернуть предыдущую запись, которая лежит на том же уровне, что и запись, относительно которой производится поиск

TVGN_PREVIOUSVISIBLE

 Вернуть видимую запись, предшествующую данной записи

TVGN_ROOT

 Вернуть первую дочернюю запись корневой записи, поддереву которой принадлежит указанная в параметре hltem запись

Если нужно получить записи, лежащие на том же уровне иерархии, что и данная запись, то можно воспользоваться следующими функциями:

HTREEITEM CTreeCtrl::GetNextSiblingltem (HTREEITEM hltem) 

Возвращает дескриптор следующей записи.

HTREEITEM CTreeCtrl::GetPrevSiblingltem (HTREEITEM hltem) 

Возвращает дескриптор предыдущей записи.

Следующая функция возвращает дескриптор родительской записи для записи, заданной параметром hltem:

HTREEITEM CTreeCtrl::GetParentItem (HTREEITEM hltem)

Класс CTreeCtrl позволяет определить запись по координатам и, если необходимо, узнать, на какой записи находится курсор мыши. Для этого следует воспользоваться функцией HitTest, например, в обработчике нажатия кнопки мыши:

HTREEITEM CTreeCtrl::HitTest (

CPoint pt,

UINT* pFlags)

или

HTREEITEM CTreeCtrl::HitTest (TV_HITTESTINFO* pHitTestlnfo)

Возвращает дескриптор записи, которой принадлежит указанная точка, или NULL, если точка не принадлежит ни одной записи. Параметр pFlags первой версии функции является указателем на переменную целого типа, принимающую результаты проверки. Может иметь одно или больше значений, указанных в описании параметра flags структуры TV_HITTESTINFO. Параметр pHitTestlnfo является указателем на структуру типа TV_HITTESTINFO, содержащую координаты точки для проверки и принимающую результаты проверки.

Приведем описание структуры TV_HITTESTINFO:

typedef struct _TV_HITTESTINFO (

POINT pt;

UINT flags;

HTREEITEM hi tern;

 } TV_HITTESTINFQ;

Поле flags используется для хранения результатов проверки и может содержать комбинацию следующих значений:

TVHT_ABOVE 

Проверяемая точка расположена выше клиентской области элемента управления

TVHT_BELOW 

Проверяемая точка расположена ниже клиентской области элемента управления

TVHT_NOWHERE 

Проверяемая точка расположена в клиентской области просмотра дерева, но лежит вне области собственно дерева

TVHT_ONITEM 

Проверяемая точка расположена в границах записи

TVHT_ONITEMBUTTON 

Проверяемая точка расположена в границах кнопки записи

TVHT_ONITEMICON 

 Проверяемая точка расположена в границах пиктограммы записи

TVHT_ONITEMINDENT 

Проверяемая точка расположена в границах отступа записи

TVHT_ONITEMLABEL 

Проверяемая точка расположена на метке, ассоциированной с записью

TVHT_ONITEMRIGHT 

Проверяемая точка расположена правее надписи записи

TVHT_ONITEMSTATEICON 

Проверяемая точка расположена в границах пиктограммы состояния записи

TVHT_TOLEFT 

Проверяемая точка расположена левее клиентской области элемента управления

TVHT_TORIGHT

 Проверяемая точка расположена правее клиентской области элемента управления

К этой же группе я отнес функции, осуществляющие сортировку записей в просмотре дерева. Их две. Первая предназначена для сортировки по алфавиту дочерних записей родительской записи. Это функция SortChildren. Она определяет уровни дочерних записей, которые должны быть отсортированы:

BOOL CTreeCtrl::SortChildren (HTREEITEM hltem)

Сортирует дочерние записи родительской записи, указанной в hltem. Если значение параметра hltem равно NULL, то будет отсортирован весь просмотр дерева.

Если же необходимо задать собственный критерий для сортировки записей, то это позволяет сделать функция SortChildrenCB:

BOOL CTreeCtrl::SortChildrenCB (LPTV_SORTCB pSort)

Сортирует дочерние записи, используя определяемую приложением функцию сортировки. Параметр pSort является указателем на структуру TV_SORTCB:

typedef struct _TV_SORTCB {

HTREEITEM hParent;

PFNTVCOMPARE IpfnCompare;

LPARAM IParam;

 } TV_SORTCB;

Поле IpfnCompare является указателем на определяемую приложением функцию сортировки записей. Она вызывается всякий раз, когда необходимо сравнить две записи и имеет описание:

int CALLBACK CompareFunc(

LPARAM 1Parami,

LPARAM !Param2,

LPARAM IParamSort)

Возвращает отрицательное значение, если первая запись должна предшествовать второй, положительное, если первая запись должна следовать за второй, и нуль, если записи равны. Параметры функции сортировки аналогичны параметрам функции сортировки для просмотра списка, которая была описана в соответствующем разделе.

 

Обработка уведомлений

При работе с просмотром дерева пользователь посылает различные уведомления. Если необходимо, то для их обработки можно назначить функции-обработчики. Перечень уведомляющих сообщений, посылаемых элементом "просмотр дерева", приведен ниже:

TVN_BEGINDRAG

 Сигнализирует о начале операции перемещения при помощи левой кнопки мыши (drag-and-drop)

TVN_BEGINLABELEDIT

 Сигнализирует о начале операции редактирования надписи записи

TVN_BEGINRDRAG

 Сигнализирует о начале операции перемещения при помощи правой кнопки мыши (drag-and-drop)

TVN_DELETEITEM 

Сигнализирует об удалении конкретной записи просмотра дерева

TVN_ENDLABELEDIT 

Сигнализирует о завершении операции редактирования надписи записи

TVN_GETDISPINFO

 Запрашивает информацию о записи по запросу элемента "просмотр дерева" для ее отображения

TVN_ITEMEXPANDED 

Сигнализирует о смене состояния записи в просмотре дерева (сворачивании или разворачивании)

TVN_ITEMEXPANDING

 Запрашивает подтверждение смены состояния записи в просмотре дерева (сворачивании или разворачивании)

TVN_KEYDOWN 

Сигнализирует о нажатии клавиши на клавиатуре 

TVN_SELCHANGED 

Сигнализирует о смене выбора записи в просмотре дерева

TVN_SELCHANGING 

Запрашивает подтверждение смены выбора записи в просмотре дерева

TVN_SETDISPINFO 

Уведомляет родительское окно о необходимости изменения информации о записи по запросу, которая предоставляется приложением

Элемент "просмотр дерева" посылает уведомления в форме сообщения WM_ NOTIFY, например, так:

BOOL CMarsWnd::OnNotify(

WPARAM wParam, LPARAM IParam, LRESULT* pResult) 

{

...

TVDISPINFO *ptvdi = (TV_DISPINFO *)IParam;

 TVITEM item = ptvdi->item;

...

}

Некоторые уведомления работают с другой структурой, а именно, со структурой NMTREEVIEW:

NMTREEVIEW *pnmtv = (NMTREEVIEW *)IParam;

Механизм обработки уведомлений в принципе такой же, как и рассмотренный в предыдущем разделе для просмотра списка.

Так же, как и класс CListCtrl для просмотра списка, класс CTreeCtrl имеет в своем составе функции поддержки редактирования надписей записи GetEditControl и EditLabel, функцию CreateDraglmage, создающую временный список изображений для реализации механизма перемещения записей при просмотре дерева, функции GetBkColor, SetBkColor, GetTextColor, SetTextColor, GetlmertMarkColor и SetlnsertMarkColor для работы с цветами фона, текста и вставляемой записи, функции GetToolTips и SetToolTips для работы с всплывающими подсказками, функцию EnsureVisible, гарантирующую видимость конкретного элемента дерева и некоторые другие, в работе которых вы без труда сможете разобраться.

 

Пример реализации просмотра дерева

В этом разделе приведен пример просмотра дерева, в качестве записей которого используется информация о файлах текущего (для приложения) каталога (рис. 16.9).

BOOL CMyPropertyPageT::OnSetActive()

{

HTREEITEM hTreeCurrent;

CTreeCtrl *pTree = (CTreeCtrl *)GetDlgItem(IDC_TREEl);

// Поиск файлов и создание записей с

// информацией о найденных файлах

WIN32_FIND_DATA FindFileData;

HANDLE hFile = FindFirstFile("*.*", &FindFileData);

if (hFile != INVALID_HANDLE_VALUE)

{

TV_INSERTSTRUCT tis;

TV_ITEM item;

tis.hlnsertAfter = TVI_LAST; item.mask = TVIF_TEXT; 

CString csText;

 do

{

// Отображаем только имена каталогов,

// игнорируя их содержимое - кроме корневого

if (FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)

{

item.pszText = FindFileData.cFileName;

tis.hParent = TVI_ROOT; tis.item = item;

// Если каталог корневой — вставляем его

 if(!strcmp(item.pszText, "..")) {

hTreeCurrent = pTree->Insert!tem(&tis); continue;

} else if(!strcmp(item.pszText, "..")) 

{

continue; 

} else 

{

// Поскольку мы просматриваем файлы только текущего

 // каталога, то не меняем "текущего корня"

 pTree->Insert!tem(stis); continue;

 } 

}

// Имя файла

item.pszText = FindFileData.cFileName;

 tis.hParent = hTreeCurrent; tis.item = item;

 pTree->Insert!tem(&tis);

}while (FindNextFilethFile, SFindFileData)); 

}

return CPropertyPage::OnSetActive(); 

}

Рис. 16.9. Окно свойств с просмотром дерева

Конечно же мы рассмотрели далеко не все имеющиеся элементы управления. Но я убежден, что теперь вы в состоянии самостоятельно разобраться с тем, как создавать и работать с любыми элементами управления.