Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Технология разработки программных систем

..pdf
Скачиваний:
13
Добавлен:
05.02.2023
Размер:
1.31 Mб
Скачать

221

//этот элемент. В противном случае,

//сбрасываем флаг в нулевое значение. if (flag & TVHT_ONITEM) {

flag = !m_tree.ItemHasChildren(hItem); m_tree.Select(hItem, TVGN_CARET);

}

else

flag = 0;

//загружаем меню из ресурсов приложения

CMenu popm, *ptm; popm.LoadMenu(IDR_TEMP_MENU);

//получаем указатель на первое popup меню ptm = popm.GetSubMenu(0);

//блокируем пункт “уничтожить”, если требуется if (!flag)

ptm->EnableMenuItem(ID_TREE_DELETE,

MF_GRAYED | MF_BYCOMMAND);

// отображаем временное меню

CRect rc; GetWindowRect(rc);

ptm->TrackPopupMenu(TPM_CENTERALIGN | TPM_RIGHTBUTTON,

rc.left+point.x, rc.top+point.y, this);

}

18.3.9. Редактирование меток объектов

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

Согласно изложенному, карта сообщений диалога Dlg должна содержать макросы вида:

ON_NOTIFY_REFLECT(TVN_KEYDOWN, OnKeyDown)

ON_NOTIFY_REFLECT(TVN_ENDLABELEDIT, OnEndEdit)

Обработчик нажатия клавиш отслеживает нажатие Insert и включает редактирование выделенного пункта дерева:

void Dlg::OnKeyDown(NMHDR* pNMHDR, LRESULT* pRes)

{

// преобразовываем указатель

222

TV_KEYDOWN* pkd = (TV_KEYDOWN*)lParam;

// проверяем нажатие нужной клавиши if (pkd->wVKey == VK_INSERT) {

// определяем выделенный пункт

HTREEITEM hItem = m_tree.GetSelectedItem();

//включаем редактирование метки

CEdit* ed = m_tree.EditLabel(hItem);

//если требуется, здесь можно выполнить

//дополнительные действия с EditBox

}

*pRes = 0;

}

После того, как пользователь закончит редактирование текста метки элемент управления TreeCtrl посылает родительскому объекту сообщение WM_NOTIFY с кодом извещения TVN_ENDLABELEDIT, обработчик которого обычно имеет вид:

void Dlg::OnEndEdit(NMHDR* pNMHDR, LRESULT* pRes)

{

// преобразовываем указатель

TV_DISPINFO* pdi = (TV_DISPINFO*)lParam;

// проверяем действительность указателя if (pdi->item.pszText) {

//контроль введенного текста

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

//для обновления информации

TV_ITEM ns;

ns.mask

= TVIF_HANDLE | TVIF_TEXT;

ns.hItem

=

pdi->item.hItem;

ns.pszText =

pdi->item.pszText;

ns.cchTextMax = lstrlen(ns.pszText);

//обновляем информацию m_tree.SetItem(&ns);

//пересортировываем текущую ветку дерева

HTREEITEM hParen =

m_tree.GetParentItem(ns.hItem); m_tree.SortChildren(hParen);

}

*pRes = 0;

}

223

18.4. Элемент CSpinButtonCtrl

Наборный счетчик обычно используется совместно с полем ввода. В этом случае поле ввода должно быть расположено слева от наборного счетчика как визуально, так и в порядке навигации с помощью клавиши Tab между элементами управления. Поле ввода является как бы партнером – “buddy” – наборного счетчика для этого случая. При использовании графического редактора необходимо установить следующие атрибуты на закладках свойств наборного счетчика:

Auto buddy; Set buddy integer и Arrow key

В качестве примера управления элементом CSpinButtonCtrl рассмотрим задачу изменения шага наборного счетчика до величины 0.5. Дело в том, что по умолчанию счетчик может работать только с целыми значениями с шагом 1. Это объясняется тем, что разработчики Windows не предусмотрели иного поведения для данного элемента управления. Поэтому в обработчике WM_INITDIALOG применим несложный трюк: установим верхнюю и нижнюю границы изменения наборного счетчика в два раза большими, чем требуется. Например, если мы хотим, чтобы размер шрифта изменялся от 2 до 20 пунктов с шагом 1/2, а стартовое значение было бы 4 пункта, то метод OnInitDialog() класса Dlg, производного от CDialog, может иметь вид:

BOOL Dlg::OnInitDialog()

{

// получаем указатель на наборнй счетчик

CSpinButtonCtrl* pSpin = (CSpinButtonCtrl*)GetDlgItem(IDC_SPIN);

//устанавливаем требуемые значения pSpin->SetRange(4, 40); pSpin->SetPos(8);

//инициализация других элементов

}

Теперь все изменения в наборном счетчике будут поступать диалогу через сообщение WM_VSCROLL, обработчик которого может иметь вид:

void Dlg::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrBar)

{

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

//поступило сообщение

if (GetDlgItem(IDC_SPIN) == pScrBar) { CString tt;

// уменьшаем текущее значение счетчика

224

// в два раза

tt.Format("%.1f", 0.5*double(nPos)); // и отображаем в EditBox

CSpinButtonCtrl* ps = (CSpinButtonCtrl*) pScrBar; ps->GetBuddy()->SetWindowText(tt);

}

else

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

}

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

225

19. Архитектура Document-View

В предыдущих лекциях рассматривались основные положения, касающиеся создания MFC приложений, базирующихся на диалогах. Это типичный подход для Windows программирования. Однако для Visual C++ такие приложения не являются основными. В Visual C++ модель визуального программирования получила дальнейшее развитие в виде новой архитектуры приложения, называемой Document-View. Средство AppWizard позволяет создавать приложения, основанные на документах: приложения с однодокументным интерфейсом (Single Document Interface - SDI) и приложения с многодокументным интерфейсом (Multiple Document Interface - MDI). Они то и являются основными для Visual C++. Нужно напомнить, что любое MFC приложение функционирует как набор взаимодействующих объектов. В этом его основная суть. Так вот, в Visual C++ эти объекты организованы в систему со вполне четкой архитектурой Document-View.

19.1. Пользовательский интерфейс

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

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

Приложение должно строиться так, чтобы, видя один или несколько документов, пользователь сосредоточил внимание именно на них самих, а не на средствах работы с документами. Архитектура Document-View как раз предоставляет, и в некотором смысле навязывает, разработчику систему объектов, позволяющих строить приложения, сфокусированные на данных, а точнее – на документах.

19.2. Документ и его представления

Центральными объектами в архитектуре приложения являются один или несколько объектов – документов. Они ориентированы на хранение

226

информации и имеют хорошо развитые методы загрузки, сохранения и управления данными. Документы создаются как объекты классов, производных от класса CDocument библиотеки MFC.

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

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

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

CScrollView.

Запомните два правила:

1.Каждый вид должен быть сопоставлен некоторому документу.

2.Один документ может иметь несколько видов, и в таком случае они обеспечивают различное представление на экране одного документа. Например, некие статистические данные вы одновременно можете показывать пользователю как текстовую таблицу, как график и как столбчатую диаграмму.

19.3. Создание документов и его отображений

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

1. Помимо уже указанных объектов CDocument и CView рассматриваемая архитектура включает еще один специальный объект – шаблон документа (document template). Понятие шаблона документа инкапсулирует класс CDocTemplate и производные от него. Шаблоны задают описание наиболее существенной части архитектуры Document-View, включая взаимосвязи класса документа, класса представления документа и класса окна-рамки документа. В MFC существуют два документных шаблона – для

227

однодокументного интерфейса (SDI) и для многодокументного интерфейса

(MDI).

2.Создание документа и связанных с ним объектов выполняется при открытии пользователем документа – существующего или нового. Это происходит в начале работы приложения, а также когда пользователь выбирает команды Open и New в меню File.

3.Создание начинается с выбора шаблона документа. В соответствии с описанным в шаблоне классом документа создается сам документ. Если шаблон задает MDI-интерфейс, то каждый раз создается новый объектдокумент. Если же шаблон задает SDI-интерфейс, то новый объект создается только один раз, а при выборе пользователем команд Open и New выполняется “очистка” уже имеющегося объекта документа.

4.Для инициализации созданного документа каркас приложения вызывает метод OnNewDocument() при открытии нового документа, а при

открытии уже имеющегося – OnOpenDocument(). Оба метода принадлежат базовому классу CDocument.

Заметим, что указанные методы являются виртуальными, и конечно же, вы, как разработчик, можете их переопределить, например, задавая какие-то необходимые действия по инициализации документа. Однако эти методы и сами по себе выполняют ряд действий, связанных с инициализацией, в частности, проверяют корректность создания объекта документа. Поэтому ваши переопределенные методы должны содержать вызовы методов базового класса. Кстати, заготовки методов OnNewDocument() и OnOpenDocument(), созданные AppWizard, уже содержат такие вызовы, так что не удаляйте их.

Заметим также, что знатоки С++ могли бы использовать конструктор документа для его инициализации. Однако если используется шаблон SDIинтерфейса, то при повторном создании или открытии документа конструктор не вызывается, а производится очистка документа. Поэтому конструктор документа можно использовать для инициализации лишь в случае приложений с MDI-интерфейсом.

5.Создание окон приложения. После создания документа создается объект главного окна-рамки, который является Windows-окном приложения. Внутри этого окна будут располагаться окна-рамки документов. Объект окно-рамка документа создается главным окном-рамкой. Затем окно-рамка документа создает объект-облик, которого и отображает содержимое документа.

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

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

228

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

19.4. Взаимодействие документа и его видов

Как уже отмечалось, виды логически связаны со своим документом. У классов CDocument и CView конечно же имеются механизмы реализации этой связи. Так, класс CDocument инкапсулирует список, хранящий все облики данного документа. Имеются методы, с помощью которых документ добавляет новый вид к списку или удаляет его из списка, вы можете осуществить просмотр списка и получить доступ к любому виду.

Метод GetDocument() класса CView возвращает указатель на объект документа и позволяет виду получить доступ к открытым полям объекта документа.

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

класса CDocument.

Классы объектов-обликов должны быть устроены так, чтобы при получении сообщения Update() обрабатывающий их метод производил синхронизацию облика с документом. Обычно это означает синхронизацию данных облика и синхронизацию изображения в окне облика. Простейший вариант такой синхронизации дает метод OnUpdate() класса CView. Он состоит в отправке объектом сообщения OnDraw() самому себе. Если разработчик не предусмотрел своего метода обработки сообщения Update(), то при получении этого сообщения облик выполняет метод OnUpdate() базового класса Cview.

19.5. SDI-приложение

В первую очередь приведем последовательность действий MFC каркаса в архитектуре документ-вид при запуске SDI приложения:

1)создается документный шаблон – объект класса, производного от

CDocTemplate;

2)динамически создается документ – объект класса, производного от

CDocument;

3)динамически создается MFC объект главного окна приложения – окна рамки, которое является объектом класса, производного от

CFrameWnd;

229

4)динамически создается MFC объект окно-вид класса, производного от CView;

5)создаются Windows окна соответствующими вызовами метода

OnCreate() для CFrameWnd и CView классов;

6)вызывается метод CDocument::OnNewDocument(). Напомним,

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

7)вызывается метод CView::OnInitialUpdate(). Здесь вы можете провести инициализацию полей объекта вид, точно также как это делалось в обработчике OnInitDialog() класса CDialog;

8)клиентская область объекта вид объявляется недействительной, следовательно, вызывается метод OnDraw() класса CView.

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

нием средств MFC AppWizard.

19.5.1. Ресурсы приложения

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

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

Панель управления. Кнопки панели управления, как правило, имеют одинаковые идентификаторы с пунктами меню, и следовательно, дублируют функции меню.

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

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

Наибольший интерес представляет текстовая строка с идентификатором IDR_MAINFRAME. В этой строке собрана различная информация, относящаяся к типу документов приложения. Формирование этой строки выполняется MFC, однако полезно знать структуру этой строки. Итак, строка состоит из 7 частей, разделенных символом ‘\n’.

230

Класс CDocTemplate содержит метод GetDocString(), который производит разбор строки IDR_MAINFRAME и выдает каждую ее часть.

BOOL GetDocString(CString& str, int index);

Параметер index должен быть одним из следующих значений, вхо-

дящих в enum CDocTemplate::DocStringIndex.

CDocTemplate:: Имя приложения, например, “Microsoft Excel” windowTitle

CDocTemplate:: Имя документа по умолчанию, например “Sheet” docName

CDocTemplate:: Имя типа документа, например, “Worksheet”. Если приложеfileNewName ние обслуживает более одного типа документа, это имя появ-

ляется в диалоге открытия документа

CDocTemplate:: Имя документа, для заполнения ComboBox в диалоге открытия filterName файлов, например, “Worksheets (*.xls)”

CDocTemplate:: Расширение для файлов документа, например, “.xls” filterExt

CDocTemplate:: Имя, под которым данный документ будет зарегистрирован в regFileTypeId базе данных Windows, например, “ExcelWorksheet”. После регистрации, Explorer будет запускать ваше приложение при

обработке документов с этим расширением

CDocTemplate:: Имя типа документа, которое будет занесено в файл реестра, regFileTypeName например, “Microsoft Excel Worksheet”

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

IDR_MAINFRAME.

19.5.2. Динамическая замена одного объекта вид другим

Как было указано в п.6.5, последовательность действий MFC каркаса при запуске SDI приложения строго предопределена и, обычно, не требует вмешательства разработчика.

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

1)вызывается метод CWinApp::OnFileOpen(). Отображается диалог открытия файлов;

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]