
ТЕХНОЛОГИИ КОМПАНИИ АРСЕНАЛЪ
Библиотека Lexicon Toolkit
и построение специализированных
редакторов текста
(Продолжение. Начало - КИ/1(25))
Интерфейс ILexDocViewer наследует интерфейс IUnknown и реализует возможности загрузки, просмотра и печати документа.
Для загрузки документа по заданному пути следует использовать метод Load интерфейса ILexDocViewer: ILexDocViewer::Load(LPCSTR path, UINT doctype).
Doctype принимает значения LEX_DOCUMENT_xxx и учитывается при неоднозначном типе файла (например, простой текст/текст в кодировке ДОС/старый Лексикон и т.п.). Например, если переменная lpszFileName содержит путь к загружаемому файлу, то вызов к методу будет выглядеть следующим образом: m_pLexDoc->Load(lpszFileName, LEX_DOCUMENT_AUTO);
Таким образом, мы загрузили документ с диска и имеем к нему доступ через интерфейс ILexDocViewer, указатель на который мы храним в переменной m_pLexDoc.
Заметим, что написание такой программы полностью самостоятельно займет гораздо больше времеми. С помощью же LexiconToolkit мы получили программу, загружающую с диска (но пока не отображающую на экране) файлы различных форматов за очень небольшой срок.
Посмотрим теперь, что нужно сделать для того, чтобы пользователь видел на экране содержимое документа. Для отображения документа необходимо сообщить компоненту дескриптор родительского окна (в нашем случае порожденного от CView), в котором ему надлежит создать собственное дочернее окно, где и будет отображаться загруженный документ и к которому будет направлен фокус ввода. Для создания окна используется метод ранее полученного интерфейса ILexDocViewer:
ILexDocViewer::CreateView(LPLEXVIEW FAR * lplpLexView)
Этот метод возвращает в lplpLexView указатель на интерфейс ILexView. Если указатель m_pLexView уже был получен ранее (в приложение уже загружен предыдущий документ), то следует отпустить интерфейс по этому указателю:
if (m_pLexView)
{
m_pLexView->Release();
m_pLexView = NULL;
}
m_pLexDoc->CreateView(&m_pLexView);
Интерфейс ILexView наследует интерфейс IUnknown. Он реализует возможности просмотра документа в окне, масштабирование, выделение с помощью мыши и клавиш управления курсором, копирование выделенного участка текста в буфер обмена, поиск по тексту. Для создания окна необходимо использовать следующий метод:
ILexView::Initialize(HWND hwndParent, DWORD style, DWORD exstyle),
который создает окно MS Window, передавая ему в качестве родительского окна hwndParent. Остальные аргументы соответствуют аргументам функции API CreateWindowEx.
m_pLexView->Initialize(*this, WS_CHILD|WS_VISIBLE|WS_VSCROLL| WS_HSCROLL, 0, 0);
Для получения дескриптора окна MS Window следует использовать метод:
HWND ILexView::GetHWND(void).
m_hwndLex = m_pLexView->GetHWND();
Полученный дескриптор следует сохранить в переменной m_hwndLex. Его можно использовать в качестве параметра в любых функциях API, управляющих окнами. В нашем случае, сразу после создания окна компонентом Lexicon мы используем этот дескриптор, чтобы установить размеры окна, полностью покрывающими рабочую область родительского окна (порожденного от CView):
CRect r;
GetClientRect(&r);
::SetWindowPos(m_hwndLex, 0, 0, 0, r.Width(), r.Height(), SWP_NOACTIVATE
| SWP_NOZORDER | SWP_SHOWWINDOW); ::InvalidateRect(m_hwndLex, 0, true);
::UpdateWindow(m_hwndLex);
::SetFocus(m_hwndLex);
Сохраненный дескриптор окна просмотра (редактора) также потребуется для передачи ему фокуса ввода и для изменения его размеров при изменении размеров несущего (родительского) окна:
void CLexpadView::OnSetFocus(CWnd* pOldWnd)
{
CView::OnSetFocus(pOldWnd); if (m_hwndLex)
::SetFocus(m_hwndLex);
}
void CLexpadView::OnSize(UINT nType, int cx, int cy)
{
CView::OnSize(nType, cx, cy); if (m_hwndLex)
::SetWindowPos(m_hwndLex, 0, 0, 0, cx, cy, SWP_NOACTIVATE | SWP_NOZORDER
| SWP_SHOWWINDOW); }
Компонент полностью отрисовывает себя сам, поэтому функцию OnDraw вообще следует оставить пустой:
void CLexpadView::OnDraw(CDC* pDC)
{
CLexpadDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc);
}
Это все, что необходимо сделать, чтобы собственное приложение могло просматривать документы в форматах MS-Word, MS-Write и Лексикона. Дополнительно разработчик может использовать следующие методы:
Поскольку интерфейс IlexDocEditor, который реализует возможности редактирования документа, наследует интерфейс ILexDocViewer, то он уже инкапсулирует все методы для просмотра документов. Таким образом, для перехода к редактору нам не придется переписывать ничего из ранее написанного для просмотра документов, а нужно будет лишь добавить новые функции, свойственные редактированию.
Внеся лишь указанные выше изменения и скомпилировав проект заново, мы получаем возможность редактирования загруженного документа (отметим еще раз, что документы могут быть в любом из наиболее распространенных сейчас форматов!).
Для того, чтобы получить полноценный текстовый процессор, наше приложение следует дополнить функциями создания нового документа, сохранения документа на диске и редактирования.
Интерфейс ILexDocEditor предоставляет огромное количество методов как для добавления в документ новых объектов (таблиц, рисунков, врезок), так и для форматирования и изменения параметров существующих. Важно помнить, что для реализации любой из этих функций от нашего приложения потребуется всегда лишь одна типовая и примитивная последовательность действий.
1. Запросить у компонента параметры текущего объекта редактирования (например, абзаца по текущему местоположению каретки или группы абзацев в выделенном фрагменте). В данном случае нас даже и не интересует, какой именно абзац является текущим, выделен ли фрагмент в документе, и относятся ли запрашиваемые нами у компонента параметры к одному или сразу нескольким абзацам.
2. Открыть созданный нами соответствующий диалог и инициализировать его значениями, полученными от компонента.
3. При выходе из диалога получить значения параметров из диалога и просто передать их компоненту обратно.
Иными словами, наше приложение выполняет не больше, чем роль простого посредника между пользователем и компонентом, и при этом разработчику не нужно задумываться ни о структуре данных, хранимых и обрабатываемых компонентом, ни о том, что собственно представляет собой сам компонент Lexicon.
В качестве примера рассмотрим, что нужно добавить в нашу программу, чтобы реализовать некоторые из функций редактирования. Для создания нового документа достаточно вызывать функцию Create интерфейса ILexDocEditor, для сохранения документа по пути, по которому он был загружен - функцию Save, а для сохранения документа по заданному пути - функцию SaveAs:
m_pLexDoc->Create();
m_pLexDoc->Save();
m_pLexDoc->SaveAs(lpszFileName(),
LEX_DOCUMENT_AUTO);
Второй параметр функции SaveAs позволяет сохранить документ в одном из форматов, поддерживаемых Лексиконом-97. Для того, чтобы определить изменился ли документ со времени последнего сохранения на диск в интерфейсе IlexDocEditor, предусмотрен метод:
ILexDocEditor::IsModified(), который возвращает S_OK или S_FALSE. Возвращаемое значение можно использовать, в частности, для того, чтобы сделать доступным или нет пункт меню Файл/Сохранить: pCmdUI->Enable(m_pLexDoc->IsModified() == S_OK);
Для выполнения действий над всем документом или его частью, а также для работы с буфером обмена предусмотрен метод ILexDocEditor::Operation(UINT oper), который переопределен из ILexDocViewer с добавлением операций, свойственных редактированию, таких, как: удалить текущее выделение в документе, поместить текущее выделение в буфер обмена и удалить его из документа, вставить из буфера обмена, отменить последнюю операцию редактирования, повторить последнюю отмененную операцию редактирования, очистить весь документ.
Например, для операций с буфером обмена нашему приложению достаточно
сделать следующие вызовы:
m_pLexDoc>
Operation(LEX_OPERATION_COPY); m_pLexDoc ->Operation(LEX_OPERA-TION_CUT);
m_pLexDoc ->Operation(LEX_OPERA-TION_PASTE);
Следует также обратить особое внимание на возможность библиотеки Лексикон-97 выполнять откат (отмену последней операции, произведенной пользователем) и, наоборот, повтор последней отмененной операции. Глубину отката может установить разработчик, а по умолчанию она довольно внушительна и составляет 50 операций. Соответственно и 50 последних отмененных операций можно повторить опять с помощью операции LEX_OPERATION_REDO:
m_pLexDoc ->Operation(LEX_OPERA-TION_UNDO);
m_pLexDoc ->Operation(LEX_OPERA-TION_REDO);
Для того, чтобы приложение, использующее Lexicon Toolkit, могло определить, возможна ли в данный момент та или иная операция, предусмотрен метод: ILexDocEditor::QueryOperation(UINT oper).
Например, для того, чтобы узнать имеется ли в документе выделенный фрагмент, который можно скопировать в буфер обмена, следует сделать следующий вызов:
m_pLexDoc - >QueryOperation (LEX_OPERATION_COPY);
m_pLexDoc - >QueryOpera-tion(LEX_OPERATION_CUT);
Соответственно, чтобы узнать, имеется ли в буфере обмена информация, которую можно вставить в документ, необходимо написать следующее: m_pLexDoc ->QueryOperation(LEX_OPERA-TION_PASTE);
Возвращаемое значение используется, в частности, чтобы сделать доступным пункт меню Правка/Вставить.
Теперь рассмотрим вопрос форматирования текста на примере изменения параметров абзаца, в частности, типа выравнивания абзаца в документе. Для этого необходимо получить параметры текущего абзаца, установить в них требуемый тип выравнивания и передать параметры обратно в библиотеку Лексикон-97. Для считывания параметров текущего абзаца следует получить указатель на интерфейс ILexParaProp с помощью функции CreateParaProp:
LPLEXPARAPROP pIParaProp;
m_pLexDoc
>CreateParaProp(&pIParaProp, LEX_COLLECT_FULL);
Затем необходимо получить указатель на интерфейс ILexEditParaProp с помощью функции CreateEditParaProp и проинициализировать объект, на который он указывает, текущими параметрами абзаца с помощью функции Set:
LPLEXEDITPARAPROP pIEditParaProp;
m_pLexDoc
>CreateEditParaProp(&pIEditParaProp);
pIEditParaProp->Set(pIParaProp);
Для установки типа выравнивания абзаца интерфейс ILexEditParaProp располагает методом:
ILexEditParaProp::SetAlignment(UINT nAlignment), где nAlignment позволяет выбрать выравнивание по левой границе, по центру, по правой границе, по левой и правой границам.
Например:
pIEditParaProp
>SetAlignment
(LEX_PARA_ALIGNMENT_CENTER);
И, наконец, новые параметры абзаца надо передать обратно библиотеке Лексикон-97:
m_pLexDoc
>SetParaProp(pIEditParaProp);
А по окончании операции необходимо отпустить оба ранее полученных интерфейса.
pIParaProp->Release();
pIEditParaProp->Release();
Точно также, через интерфейс ILexDocEditor можно получить доступ ко всем остальным параметрам документа в целом или отдельным его элементам и модифицировать их. Для этого интерфейс редактора (ILexDocEditor) библиотеки Лексикон 97 предоставляет пользователю обширный набор методов.
Чтобы не превращать эту статью в скучную техническую документацию, остальные методы, предоставляемые Lexicon Toolkit программисту, перечислим вкратце. Важно, что схема работы с ними практически не отличается от описанной выше, что дает разработчику массу возможностей, не заставляя его при этом вникать в детали работы компонента.
Итак, кроме описанных, библиотека содержит в себе следующие группы функций:
Конечно, они никак не могут претендовать на полноту охвата всех возможностей Lexicon Toolkit-а, которые исключительно обширны и позволяют любому программисту, знакомому с основами COM, использовать библиотеку Лексикон-97 как для создания небольших элементов управления, так и для разработки полнофункциональных, ориентированных на специальные задачи, текстовых процессоров.
Александр Алексеев, ведущий программист компании Арсеналъ-СПб,
тел. (812) 112-9066,
E-mail: alex@bikar.spb.su