/
Текст
1118 Visual С++ 2010. Полный курс
Текущий стиль линии необходимо rдето хранить, поэтому добавьте в класс
Forml закрытую переменнуючлен lineStyle типа System: : Drawing: : Drawing2
о: : DashStyle. Это тип перечисления, используемый классом Реп для определе"
нмя стиля рисуемой линии. Если добавить директиву using для пространства имен
System: : Drawing: : Drawing2D, тЬ можно будет определять тип переменной lineStyle
как DashStyle.
В перечислении DashStyle ОПР2делены члены Solid, Dash, Dot, DashDot,
DashDotDot и Custom. Значение первых пяти очевидно. Стиль Custom позволяет опр
делить стиль линии как практически любую последовательность линейных cerMeHTOB
и промежутков необходимых длин. Стиль линии определяется в свойстве DashPa t tern
объекта Реп. Для определения шаблона используется массивом значений типа float,
которые определяют длины линейных cerMeHToB и пробелов.
аrrау<flоаt>Л pattern {5.0f, З.Оf};
pen>DashPattern pattern;
Этот код определяет для объекта пера пунктирную линию, состоящую из cerMeHToB
длиной 5 . О , разделенных пробелами длиной 3 . О f. Указанный шаблон повторяется по
длине всей линии. Вполне очевидно, что, определяя большее количество элементов в
массиве, вы можете определить шаблоны любой сложности.
Чтобы узнать, коrда необходимо изменить значение переменнойчлена lineStyle
класса Forml, следует отследить, коrда изменится значение раскрывающеrося
списка. Самый простой способ подразумевает добавление обработчика события
SelectedIndexChanged объекта СотЬоВох. Добавьте этот обработчик в окне Properties
и реализуйте ero так, как показано ниже.
private:
System::Void lineStyleComboBoxSelectedlndexChanged(
Sуstеm::ОЬjесt Л sender, Sуstеm::ЕvепtАrgs Л е)
switch(lineStyleComboBox>SelectedIndex)
{
case 1:
lineStyle = DashStyle::Dash;
break;
case 2:
lineStyle = DashStyle::Dot;
break;
case з:
lineStyle = DashStyle::DashDot;
break;
case 4:
lineStyle = DashStyle::DashDotDot;
break;
default:
lineStyle = DashStyle::Solid;
break;
}
Этот код лишь присваивает переменнойчлену lineStyle одно из значений пере
числения DashStyle на основании значения свойства SelectedIndex объекта paCKpы
вающеrося списка.
rлава 18. Работа с диалоrовыми окнами и элементами управления 1119
Чтобы нарисовать элементы с различными стилями линии, необходимо добавить
еще один параметр типа DashStyle в каждый из конструкторов элемента. Сначала дo
бавьте следующую директиву using в файл Elements . h.
using namespace System::Drawing::Drawing2D;
Измените конструктор класса Line, чтобы обеспечить рисование линий разными
стилями.
Line(Color color, Point start, Point end, float penWidth,
DashStyle style)
реп gcnew Pen(color, penWidth);
pen>DashStyle = style;
// Остальная часть кода такая же, как и раньше...
Здесь только одна новая строка кода, которая присваивает свойству DashStyle объ
екта пера значение переданноrо конструктору последнеrо apryMeHTa. Измените друrие
конструкторы класса элемента таким же образом.
Последнее, что осталось сделать, изменить вызовы конструктора в обработчике
событий перемещения мыши в классе Forml.
private:
System::Void FоrmlМоusеМоvе(Sуstеm::ОЬjесtЛ sender,
Sуstеm::Wiпdоws::Fоrms::МоusеЕvепtАrgs Л е)
{
if(drawing)
{
if(tempElement)
// Пометить старую область элемента как недействительную
Invalidate(tempElement>bound);
switch(elementType)
{
case ElementType::LINE:
tempElement = gcnew Line(color, firstPoint, e>Location,
penWidth,lineStyle);
break;
case ElementType::RECTANGLE:
tempElement = gcnew Rectangle(color, firstPoint, e>Location,
penWidth,lineStyle);
break;
case ElementType::CIRCLE:
tempElement = gcnew Circle(color, firstPoint, e>Location,
penWidth,lineStyle);
break;
case ElementType::CURVE:
if(tempElement)
sаfесаst<СurvеЛ>(tеmрЕlеmепt)>Аdd(е>Lосаtiоп);
else
tempElement = gcnew Curve(color, firstPoint, e>Location,
penWidth,lineStyle);
break;
}
// Остальная часть кода такая же, как и раньше...
1120 Visual С++ 2010. Полный курс
Если перекомпилировать и запустить версию CLR приложения Sketcher, то pac
крывающийся список позволит выбрать новый стиль линии для рисуемых элементов.
Стили линий не будут работают с кривыми изза способа их рисования, так как они
представляют собой набор очень коротких cerMeHToB линий. У друrих элементов ли
нии со стилями будут выrлядеть лучше, если ширина пера будет больше, чем принято
по умолчанию.
Создание текстовых элементов
Рисование текста в версии CLR проrраммы Sketcher будет выrлядеть примерно так
же, как в версии MFC. Выбор пункта меню Text или соответствующей кнопки панели
инструментов будет приводить к установке режима рисования Text. Щелчок rденибудь
в форме в этом режиме будет, в свою очередь, приводить к от.ображению модально
ro окна, позволяющеrо вводить текст, а закрытие этоrо диалоrовоrо окна с помощью
кнопки ОК к отображению введенноrо текста в позиции курсора. Тема рисования
текста является очень обширноЙ, поэтому ее обсуждение здесь оrраничивается paCCMO
трением лишь основных моментов.
Сначала добавьте перечислитель ТЕХТ в класс перечисления ElementType, а затем
пункт Text в меню Element и соответствующую кнопку в панель управления. Измените
для пункта меню значение свойства name на textToolStripMenuItem и создайте для
Hero обработчик событий Click. Можете также добавить для Hero и соответствующей
кнопки панели инструментов желаемое значение для свойства ToolTipText. Далее
создайте для кнопки панели инструментов растровый рисунок, изображающий TeKcT<r
вый режим, и укажите, что для нее должен использоваться тот же обработчик событий
Click, что и для соответствующеrо пункта меню. Сам обработчик событий Click pea
лизуйте так, как показано ниже.
private: System::Void tехtТООlStriрМепuItеmСliСk(Sуstеm::ОЬjесtЛ sender,
Sуstеm::ЕvепtАrgs Л е)
elementТype = ElementТype::TEXT;
SetElementTypeButtonState();
Эта функция просто приравнивает elementType к перечислителю ElementType,
представляющему режим рисования текста.
Не збудьте добавить пункт меню Text и в контекстное меню.
Рисование текста
Прежде чем версия CLR приложения Sketcher сможет рисовать текст, неободи
мо рассмотреть несколько вопросов. Начнем со способа рисования текста. В классе
Graphics для рисования текста доступна функция DrawString () . у этой функции
имеется несколько переrруженных версией, краткое описание которых приведено в
табл. 18.6.
Прежде чем продолжить рассмотрение процесса создания элементов Text, вкратце
рассмотрим, как создаются шрифты и кисти.
Создание шрифтов
Шрифт, предназначенный для рисования строки, задается с помощью объекта
System: : Drawing: : Font, который определяет rарнитуру, стиль и размер прорисо
вываемых символов. Объект типа System: : Drawing: : FontFamily определяет rруппу
rлава 18. Работа с диалоrовыми окнами и элементами управления 1121
шрифтов с определенной rарнитурой, например" Ar ial" или" Т irnes New Rornan".
Перечисление Systern: : Drawing: : FontStyle определяет возможные стили шриф
тов, которые MOryт иметь вид одноrо из следующих значений: Regular, Bold, Italic,
Underline или Strikeout.
Таблица 18.6. Переrруженные версии функции DrawString ()
Функция
DrаwStriпg(Striпg Л str, Fопt Л
font, Вrush Л brush, PointF point)
DrаwStriпg(Striпg Л str, Fопt Л
font, Вrush Л brush, float Х,
float У)
DrаwStriпg(Striпg Л str, Fопt Л
font, Вrush Л brush, RectangleF
rect)
DrаwStriпg(Striпg Л str, Fопt Л
font, Вrush Л brush, PointF point,
StriпgFоrmаt Л format)
DrаwStriпg(Striпg Л str, Fопt Л
font, Вrush Л brush, float Х,
float У, StriпgFоrmаt Л format)
DrаwStriпg(Striпg Л str, Fопt Л
font, Вrush Л brush, RectangleF
rect, StriпgFоrmаt Л format)
Описание
Рисует строку s tr в позиции poin t С использованием
шрифта, указанноrо в aprYMeHTe font, и цвета, заданно
ro aprYMeHToM brush. Под Windows: : Drawing: : PointF
подразумевается точка, представленная с помощью KO
ординат типа f loa t. В любой из версий этой функции в
качестве aprYMeHTa для параметра PointF может исполь
зоваться объект Point
Рисует строку s tr В позиции (х, У) С использованием
шрифта, указанноrо в aprYMeHTe font, и цвета, задан
Horo aprYMeHToM brush. При вызове этой функции для
указания координат также допускается использование
aprYMeHToB типа int
Рисует строку str В пределах прямоуrольника rect
С использованием шрифта, указанноrо в aprYMeH
те font, и цвета, заданноrо aprYMeHToM brush. Под
Wiпdоws::Drаwiпg::RесtапglеFподразумеваетсяпря
моуrольник, позиция, ширина и высота KOToporo указаны
с помощью значений типа float. В любой из версий этой
функции в качестве арrументадля параметра RectangleF
может использоваться объект Rectangle
Рисует строку str В позиции point с использованием
\
шрифта, указанноrо в aprYMeHTe font, цвета, заданноrо
aprYM8HToM brush, и форматирования строки, указанноrо
aprYMeHTOM format. Объект StringFormat определяет
выравнивание и друrие свойства форматирования строки
Рисует строку str В позиции (Х,У) с использованием
шрифта, указанноrо в aprYMeHTe font, цвета, заданноrо
aprYMeHToM brush, и форматирования строки, указанноrо
aprYMeHTOM format
Рисует строку str В пределах прямоуrольника rect С
использованием шрифта, указанноrо в aprYMeHTe font,
цвета, заданноrо aprYMeHToM brush, и форматирования
строки, указанноrо aprYMeHTOM format
Создать объект Font можно следующим образом.
FопtFаmilуЛ family == gcnew FontFamily(L"Arial");
Sуstеm::Drаwiпg::Fопt Л font == gcnew System::Drawing::Font(family, 10,
FontStyle::Bold, GraphicsUnit::Point);
Объект FontFarnily создается за счет передачи имени шрифта конструктору. Apry
ментами для конструктора класса Font являются название Сelvlейства, которому при
надлежит данный шрифт, размер этоrо шрифта, ero стиль и какоенибудь значение из
перечисления GraphicUni t., опредеЛЯЮllее единицы, в которых должен измеряться
размер шрифта. Возможные значения перечисления приведены в табл. 18.7.
1122 Visual С++ 2010. Полный курс
Таблица 18.7. Значения перечисления GraphicUni t
World
Display
ЕдИНИЦЫ измерения мировая система координат
'Единица измерения единица измерения устройства отображения: для мониторов
пиксели и 1/100 дюйма для принтеров
Единица измерения пиксель устройства
Единица измерения пункт (1/72 дюйма)
Единица измерения дюйм
Единица измерения единица документа, которая составляет 1/300 дюйма
Единица измерения миллиметр
Pixel
Point
Inch
.Document
Millimeter
Таким образом, приведенный выше фраrмент кода указывает, что использоваться
должен шрифт Arial размером в 10 пунктов И С полужирным начертанием.
Обратите внимание на то, что имя типа Font в этом фраrменте полностью квалифи
цировано изза Toro, что объект формы в приложении Windows Forms происходит от
базовоrо класса Forrn, свойство KOToporo тоже имеет имя Font. Приложения Windows
Forms поддерживают rлавным образом шрифты TrueType, поэтому шрифты ОрепТуре
для них лучше не выбирать. В случае примененuя шрифта, который либо не поддержи"
вается, либо не был установлен на данном компьютере, будет использоваться шрифт
Мiсrоsоft Sans Serif.
Создание кистей
Объект Systern: : Drawing: : Brush определяет цвет, используемый при рисова
нии строк заданным шрифтом, а также может применяться для указания цвета и
текстуры, которые должны использоваться для заливки фиryры. Создавать объект
Brush напрямую нельзя, потому что он является абстрактным классом. Поэтому ки"
сти создаются с применением таких типов классов, как SolidBrush, TextureBrush и
LinearGradientBrush, которые происходят от класса Brush. Объект SolidBrush пред"
ставляет одноцветную кисть, объект TextureBrush кисть, использующую рисунок ДЛЯ
заливки внутренней области фиryры, а объект LinearGradientBrush кисть, приме..
няющую rрадиентную смесь, которая обычно состоит из двух цветов, но может также
состоять из нескольких цветов. Для рисования текста используется объект SolidBrush,
создаваемый следующим образом.
SоlidВrush Л brush gcnew SolidBrush(Color::Red);
Здесь создается одноцветная кисть, которая будет рисовать текст KpacHoro цвета.
Выбор шрифта
Вы можете сохранить ссылку на текущий объект Font, который должен использ
ваться при создании элементов Text, за счет добавления в класс Forrnl переменной
типа Systern: : Drawing: : Font л по имени textFont. Инициализируйте ее в KOHCTPYК
торе класса Forrnl со свойством Font, которое представляет собой свойство формы,
указывающее, какой шрифт должен использоваться для нее по умолчанию. Обязатель
но сделайте это в теле конструктора следом за комментарием / / TODO:, поскольку при
попытке инициализировать ее в списке инициализации проrрамма выдаст ошибку, так
как свойство Font еще не определено.
Далее можете добавить кнопку в панель инструментов для предоставления пользова..
телю возможности выбирать желаемый шрифт для ввода текста, возможно, снабдив ее
растровым рисунком с изображением буквы F (сокраIценно от Foпt). Измените значение
rлава 18. Работа с диалоrовыми окнами и элементами управления 1123
ее свойства паrnе на чтото более подходящее, например fontToolStripButton, а затем
добавьте для нее обработчик событий Click.
В окне Toolbox имеется стандартное диалоrовое окно для выбора шрифта, в KO
тором отображаются все доступные в данной системе шрифты и которое позволяет
tI
выбирать для шрифта желаемый стиль и размер. Перейдите в открытое для файла
Forrnl . h окно конструктора и перетащите из окна Toolbox в форму элемент управления
FontDialog. Отвечать за ero отображение может обработчик событий Click кнопки
fontToolStripButton.
private:
System::Void tооlStriрFопtВuttОПСliСk(Sуstеm::ОЬjесtЛ sender,
Sуstеm::ЕvепtАrgs Л е)
if(fontDialogl>ShowDialog() ==
System: :Windows: : Forms: : DialogResul t: : ОК)
{
textFont = fontDialogl>Font;
}
Здесь диалоrовое окно для выбора шрифта отображается вызовом функции
ShowDialog ( ) , точно так же, как это было и в случае диалоrовоrо окна для выбора тол
щины пера. Если эта функция возвращает значение DialogResul t: : ОК, то это означа
ет, что данное диалоrовое окно было закрыто щелчком на кнопке ОК. В таком случае
далее выбранный шрифт извлекается из свойства Font объекта диалоrовоrо окна и
сохраняется в переменной textFont в объекте класса Forrnl. Если хотите, можете ис
пробовать, как это работает. Диалоrовое окно для выбора шрифта должно выrлядеть
примерно так, как показано на рис. 18.23.
_'. ... .:;71 .
, Font
I . -.
....., -'..,..".,....""'......,.....,..., .ЦL.. ltrJ '-"""
--,
font:
: r. ,
Size:
8 [: O: I
t. о 1 :
ObIiqиe 1'0 1=
I L
r ВoId 11
iВoldOb/iqШ II:: I
Font st)4e:
, ,Regu1ar
-ft,''''''
11w.J.
тrOfJ CCr.m J 4
iMS Reference 1
I
LM Reference 2
Effects
L" I ЭПkеоtЛ
...J Underm1e
Sampfe
АаВЬУуЬ
Scфt:
Westem
....
.
Рис. 18.23. ДиалО20вое lЖ'I-lО для выlораa шрифта
Теперь необходим класс для представления элемента Text, поэтому и определим ero.
Определение класса TeKCToBoro элемента
Базовым классом у элемента TextElernent будет, как и у элементов друrих типов,
класс Elernent, поэтому функцию Draw () можно вызвать полиморфным образом.
,
1124 Visual с++ 2010. Полный курс
Базовая информация о размещении и цвете TeKcToBoro элемента будет фиксироваться
в членах, наследуемых от базовоrо класса, но вам необходимо добавить дополнитель"
ные члены для сохранения текста и информации, относящейся к шрифry Ниже при
ведено первоначальное определение класса.
.
public ref class TextElement : Element
{
protected:
Striпg Л text;
SоlidВrush Л brush;
Fопt Л font;
public:
TextElement(Color color, Point р, Striпg Л text, Fопt Л font)
{
this>color color;
brush gcnew SolidBrush(color);
position р;
this>text text;
this>font font;
int height font>Height; // Высота текстовой строки
int width staticcast<int>(font>Size*text>Length); // Ширина
boundRect System::Drawing::Rectangle(position, Size(width,
height) ) ;
boundRect.Inflate(2,2); // Учет подстрочнь элементов
virtual void Drаw(Grарhiсs Л g) override
brush>Color highlighted ? highlightColor : color;
g>TranslateTransform(safecast<float>(position.X),
safecast<float>(position.Y));
g>DrawString(text, font, brush, Point(O,O));
g>ResetTransform();
} ;
Имя TextElernent, а не просто Text здесь было выбрано для Toro, чтобы не возни..
кало путаницы с членом Text класса Forrn. Объект TextElernent обладает членами для
сохранения строки текста, а также шрифта и кисти, которые должны применяться для
рисования этоrо текста.
Определение оrраничивающеrо прямоyrольника для TeKcToBoro элемента сопряжС"
но с небольшими трудностями, причиной которых является зависимость от размера
шрифта в пунктах и необходимость вычислять из Hero ширину и высоту. Высота BЫ
числяется леrко, поскольку у объекта font есть свойство Height, которое делает ДО"
ступным информацию о междустрочном интервале шрифта, который является xop
шей оценкой для высоты текстовой строки. Свойство Size возвращает размер шрифта
(соответствующий Iпирине символа М). Таким образом, вы можете примерно оценить
ширину прямоyrольника строки, умножив размер ет на количество символов строки.
Я увеличил прямоуrольник вдвое, так как междустрочный интервал не всеrда COOTBeT
ствует описывающему прямоуrольнику, поскольку он не учитывает подстрочные эле..
менты, как усимволов р и q.
rлава 18. Работа с диалоrовыми окнами и элементами управления 1125
Создание диалоrовоrо окна для ввода текста
При создании элемента TextElernent понадобится вводить текст с клавиатуры, по
этому необходимо диалоrовое окно, в котором это можно было бы делать. Чтобы соз..
дать такое окно, перейдите к проводнику решения и добавьте в версию CLR проrраммы
Sketcher новую форму, щелкнув правой кнопкой мыши на имени проекта и выбрав в
контекстном меню пункт AddNew Item... (ДобавитьНовый элемент). Затем введите
в качестве имени TextDialog. Измените значение свойства Text формы на Create Text
Element, а значения дрyrих свойств так, как делали это для диалоrовоrо окна PenDialog.
В частности, потребуется изменить значение свойства FormВorderStyle на FixedDialog
и установить свойства MaxirnizeBox, MinirnizeBox и ControlBox в состояние false.
Следующим шаrом является добавление кнопок для закрытия диалоrовоrо окна.
Поэтому добавьте в диалоrовое окно TextDialog кнопки ОК и Cancel с соответствую
щими значениями в свойстве Dial.ogResul t. Измените значение их свойства пате на
textOKButton и textCancelButton соответственно. И наконец, установите для свой
ства AcceptButton диалоrовоrо окна TextDialog значение textOKButton, а для свой
ства CancelButton значение textCancelButton.
Использование элемента управления Textвox
Элемент управления TextBox позволяет вводить как одну, так и несколько строк
текста, поэтому он, конечно, подходит для преследуемых здесь целей. Добавьте ero
в диалоrовое окно TextDialog, перетащив из окна Toolbox в окно конструктора. По
умолчанию он позволяет вводить только одну строку текста, и здесь предполаrается,
что именно это и требуется. Если нужно, чтобы он позволял вводить несколько строк
текста, щелкните на отображаемой в окне конструктора справа от TextBox стрелке,
направленной вниз, и установите значение Mul tiline. Свойство Text элемента управ
ления TextBox предоставляет доступ к вводимым данным и делает их доступными в
виде объекта String.
В идеале нужно, чтобы диалоrовое окно TextDialog открывал ось сразу же с HaBe
денным фокусом на текстовом поле, позволяющем вводить текст. Тоrда можно будет
сразу же после открытия диалоrовоrо окна вводить текст и нажимать клавишу <Enter>
для ero закрытия так, будто бы был выполнен щелчок на кнопке ОК. То, на каком эле..
менте управления будет изначально находиться фокус, определяет устанавливаемый
для Э-':lементо управления диалоrовоrо окна порядок перехода по клавише табуляции,
который зависит Toro, какие значения указываются в свойстве TabIndex этих элемен
тов. Указание для свойства TabIndex элемента управления TextBox значения О, а для
свойства TabIndex кнопок ОК и Cancel значений 1 и 2 приведет к тому, что при OTO
бражени диалоrовоrо окна фокус изначально будет находиться именно на текстовом
поле. Порядок перехода по клавише табуляции также определяет последовательность
переноса фокуса с одноrо элемента управления на друrой при нажатии клавиши <ТаЬ>.
Чтобы увидеть, как выrлядит порядок перехода по клавише табуляции в окне KOHCTPYK
тора, выберите в rлавном меню пункт ViewTab Order (ВидПорядок табуляции);
выбор этоrо же пункта меню снова позволит убрать из вида порядок табуляции.
Элемент управления TextBoxl будет хранить вводимую строку, но, поскольку он
является закрытым (pr i va te) членом объекта диалоrовоrо окна, получать к нему дo
ступ напрямую нельзя. Вместо этоrо нужно добавить механизм для извлечения строки
из объекта диалоrовоrо окна. Друrой проблемой является то, что элемент управления
TextBoxl будет сохранять вводимый текст и отображать ero снова при следующем от..
крытии диалоrовоrо окна. Вы вряд ли хотите, чтобы такое происходило, а это значит,
1126 Visual С++ 2010. qолный курс
что необходим какойто способ для сбрасывания значения свойства Text у элемента
управления TextBoxl. Можно добавить в класс TextDialog открытое свойство, KOTO
рое будет решать сразу обе задачи. Чтобы сделать это, вставьте в определении класса
TextDialog сразу же следом за директивой #pragrna endregion следующий код.
// Свойство для доступа к тексту в поле редактирования
public:
property Striпg Л TextString
{
Striпg Л get() { return textBoxl>Text; }
void sеt(Striпg Л text) { textBoxl>Text text; }
Здесь вызываемая для свойства TextString функция get () делает доступной ту
строку, которая вводится, а функция set () позволяет ее сбрасывать.
Было бы неплохо, если бы текст отображался в поле редактирования текущим
шрифтом, чтобы пользователь Mor сразу увидеть, подходит ли он. Это позволит OTMe
нить ввод текста и заменить шрифт. Добавьте следующее свойство в класс TextDialog
после кода, приведенноrо выше.
// Установить шрифт поля редактирования
public: property Sуstеm::Drаwiпg::Fопt Л TextFont
{
void sеt(Sуstеm::Drаwiпg::Fопt Л font) { textBoxl>Font
font; }
Это свойство только для записи, которое вы можете использовать для установки
шрифта объекта поля редактирования textBoxl.
Для помощи в создании текстовых элементов необходимы еще коекакие ДO
полнительные переменныечлены в классе Forrnl. Добавьте в файл Forrnl. h дирек"
тиву #include для файла заrоловка TextDialog. h, а затем член textDialog типа
ТехtDiаlоg Л . Инициализируйте ero вызовом gcnew TextDialog () в списке инициали
зации конструктора класса Forrnl.
Отображение диалоrовоrо окна и создание TeKCToBoro элемента
Процесс создания TeKcToBoro элемента отличается от процесса создания reoMe..
трических элементов, и поэтому для понимания последовательности событий в коде
сначала посмотрим, как этот процесс выrлядит в интерактивном режиме. Режим соз..
дания TeKcToBoro элемента вступает в силу при выборе пункта меню Text или щелчка
на соответствующей кнопке панели инструментов. Для создания элемента в форме в
том месте, rде должен размещаться левый верхниЙ уrол строки текста, осуществляется
щелчок мышью. Это приводит К отображению диалоrовоrо окна TextDialog, в кото..
ром В текстовом поле вводится любой желаемый текст, после чеrо нажимается клавиша
<Enter> для закрытия диалоrовоrо окна. Обработчик события MouseMove не принимает
в этом процесс е вообще никакоrо участия. Таким образом, получается, что весь про..
цесс начинается со щелчка кнопкой мыши для указания позиции размещения элемен
та. Это означает, что отображение диалоrовоrо окна и создание TeKcToBoro элемента
должно осуществляться в обработчике событий MouseDown. Необходимый для этоrо
код приведен ниже.
private: System::Void FоrmlМоusеDоwп(Sуstеm::ОЬjесtЛ sender,
Sуstеm::Wiпdоws::Fоrms::МоusеЕvепtАrgs Л е)
.......-
rлава 18. Работа с диалоrовыми окнами и элементами управления 1127
if(e>Button System::Windows::Forms::MouseButtons::Left)
{
firstPoint = e>Location;
if(mode == Mode: :Normal)
{
if (elementType == ElementType: : ТЕХТ)
{
// Сброс соки eXCOBoro поля
textDialog>TextString = L"";
// Усановиь шриф для поля редахирования
textDialog>TextFont = textFont;
if(textDialog>ShowDialog() ==
System: :Windows: : Forms: : DialogResul t: : ОК)
{
tempElement = gcnew TextElement(color, firstPoint,
textDialog>TextString, textFont);
sketch += tempElement;
// Облась eXCOBoro элемена
Invalidate(tempElement>bound);
tempElement = nullptr;
Update();
}
drawing = false;
}
Else
{
drawing = true;
}
}
}
в этом коде сначала указывается, что текстовые элеенты должны создаваться толь
ко при условии, если член elernentType формы имеет значение ElernentType: : ТЕХТ, а
mode значение Mode: : Norrnal; второе условие необходимо во избежание отображе
ния диалоrовоrо окна, коrда оно находится в режиме перемещения. Коrда условие i f
истинно, первым делом осуществляется сброс значения свойства Text для элемента
управления TextBoxl в пустую строку за счет установки последней в качестве значения
для свойства TextString диалоrовоrо окна. Далее, после вызова в выражении условия
if функции ShowDialog, отображается диалоrовое окно. Если функция ShowDialog ()
возвращает значение DialogResul t: : ОК, то осуществляется извлечение строки из диа
лоrовоrо окна, создание объекта TextElernent и ero добавление в эскиз. После этоrо
область, занимаемая новым элементом, становится недействительной и отображает..
ся заново за счет вызова функции Update () . Значение указателя ternpElernent по за
вершении Bcero этоrо тоже сбрасывается в nullptr. И наконец, переменная drawing
устанавливается в состояние false для предотвращения попыток создать элемент об
работчиком MouseMove.
Если скомпилировать проrрамму Sketcher теперь, то должна появиться возмож"
ность создавать в ней текстовые элементы с использованием предпочитаемоrо шриф
та, а также возм?жность перемещать и удалять их. На рис. 18.24 показано окно про
rpaMMbI Sketcher с несколькими созданными текстовыми элементами.
1128 Visual с++ 2010. Полный курс
(
Iт в;,.,. с;;, l 'l
1. J J. .,j / DCJ:I\, т F J.,. · .
I I
r1
: font
! )1 ааsfш{tJ30.,- 1;:' ," "' " : "" Siz: l
! I ".щ ;h* 16- r О!( I "
: ' . --;l r :l! --: I (- I
............ !lucida San 1 !Im.Lio БoL i j I C__,___J
... ....' I I 1 22
;.",- 1lucida Sans Unic: 1 1 ( ; ;?'
.,- [ 1 . ! '2. I v!
,.' jМarandra GD , 1 j 126
/ I l!f!!,_:J L ... I !-_.:'" J
,
; Вfed$
fA ..dc:t ,- StnkeotA А ,.. "й'. \/ у. '7 '2/
\ 1;, : и...v07 '-lf
, I
",
"\ Sapt:
.,\ Weвtem 1" ,1
".
....,.... "....... .".-'
.I,
.,,'"
__J
Рис. 18.24. Создание текстовыlx элементов в версии CLR про--
zраммыl Sketcher
Резюме
в этой rлаве было показано несколько разных диалоrовых окон, в которых исполь-
зовались разные элементы управления. Хотя создание диалоrовых окон с применением
нескольких различных элементов управления одновременно здесь не демонстрирова-
лось, механизм будет точно таким же, поскольку каждый элемент управления может
функционировать независимо от всех остальных.
Диалоrовые окна это фундаментальный инс!румент пользовательскоrо ввода в
приложении. Они предоставляют способ самостоятельно орrанизовать ввод разноо-
бразных данных. Вы леrко можете осуществить проверку, чтобы приложение получа-
ло только достоверные данные. Разумный выбор элементов управления в диалоrовом
окне может заставить пользователя выбрать значения лишь из предоставленноrо на-
бора возможностей. Вы можете также проверять данные после их ввода в диалоrовом
окне :и снова запросить данные у пользователя, если они недопустимы.
Упражнения
Исходные коды упражнений и их решения можно заrрузить с весайта www . wrox. сот.
1. Реализуйте в версии MFC приложения Sketcher диалоrовое окно масштабирова-
ния с использованием пере ключа тел ей.
2. Реализуйте в версии MFC приложения Sketcher диалоrовое окно установки ши-
рины пера с использованием окна списка.
3. Реализуйте в версии MFC приложения Sketcher диалоrовое окно установки ши.
рины пера в виде комбинированноrо окна с раскрывающимся списком типов,
выбираемым на вкладке Styles (Стили) окна свойств (этот раскрывающийся спи-
сок должен позволять пользователю выбирать вариант из фиксированноrо рас-
крывающеrося списка, но не помещать в Hero альтернативные вхождения).
rлава 18. Работа с диалоrовыми окнами и элементами управления 1129
Что вы узнали в этой rлаве
о Дuал020вое О1(,и0. Любое диалоrовое окно состоит из двух компонентов: ресурса,
определяющеrо само диалоrовое окно и входящие в ero состав элементы управ
ления, а также класса, который используется для отображения этоrо диалоrово
ro окна и работы с ним.
О Извлече1luе U1lформа1J,UU uз содержащuхся в дuал020вом О1(,ие элеме1lтов уnравле1lUЯ. Mo
жет осуществляться с помощью механизма DDX, а верификация данных с по
мощью механизма DDV. ДЛЯ применения DDXjDDV необходимо Bcero лишь
воспользоваться предлаrаемой в интерактивном мастере Add Member Variable
Wizard возможностью Add the Control VariabIe (Добавление переменной элемен
та управления) и с ее помощью определять те переменные в классе диалоrовоrо
окна, которые должны ассоциироваться с ero элементами управления.
О МодалЪ1l0е дuал020вое О1(,и0. Подразумевает удерживание фокуса в приложении до
тех пор, пока окно этоrо диалоrовоrо окна не будет закрыто. Друrими словами,
до тех пор, пока будет отображаться модальное диалоrовое окно, все остальные
окна в приложении будyr неактивными.
О Не.м,одалЪ1l0е дuал020вое О1(,и0. Позволяет переключать фокус с диалоrовоrо окна на
дрyrие окна в приложении, и наоборот. По необходимости немодальное диалоrо
вое окно может отображаться до тех пор, пока выполняется приложение.
О Общuе элеме1lты' уnравле1lUЯ. Представляют собой набор стандартных элементов
управления Windows, которые поддерживаются MFC и средствами редактирова
ния ресурсов Developer Studio.
О Добавле1luе элем,е1lт06 уnравле1lUЯ. Несмотря на то что элементы управления обыч
но ассоциируются с диалоrовым окном, в принципе они MOryт добавляться в лю
бое окно.
о Добавuтъ форму в nрuложе1luе Windows Forтs. Можно с ПОМОIЦЬЮ окна Solutions
Explorer (Проводник решений), щелкнув в нем правой кнопкой мыши на
имени нужноrо проекта и выбрав в контекстном меню пункт Add New Item
(Добавить Новый элемент).
о Превратuтъ форму в модалЪ1l0е дuал020вое О1(,и0. Можно за счет замены значения свой
ства ForrnBorderStyle значением FixedDialog и установки значений свойств
MaxirnizeBox, MinirnizeBox и ControlBox в состояние false.
О 3аnОЛ1lЯтъ дuал020вое О1(,и0 элем,е1lта.мu уnравле1lUЯ. Следует перетащить их из окна
Toolbox (Панель инструментов) в диалоrовое окно, отображаемое в области KOH
структора.
О Ото6разuтъ дuал020вое О1(,и0. Вызвать для Hero функцию ShowDialog ( ) .
о 31lа:че1luе свойства DialogResul t для 'Ки0n'Ки. Содержится в диалоrовом окне кноп
ки И определяет то, какое значение будет возвращаться функцией ShowDialog ( )
при использовании этой кнопки для закрытия диалоrовоrо окна.
О ста1lдарт1lыle дuал020выle О1(,иа. В окне Toolbox доступно несколько rOToBbIx CTaH
дартных диалоrовых окон, включая диалоrовое окно для выбора шрифта.
о Рисоватъ стрО1(,У в форме. Можно с ПОМ0ЩЬЮ функции DrawString () объекта
Graphics, который инкапсулирует всю поверхность рисования формы. Переда
вая этой функции в качестве apryмeHTa объект Font, можно задавать rарнитуру,
стиль и размер прорисовываемых символов, а передавая объект Brush их цвет.
Сохранение и печать
документов
в этой rЛАВЕ...
> Что такое сериализация
> Как сделать объекты класса сериализуемыми
> Роль объекта CArchi ve в сериализации
> Как реализовать сериализацию в собственных классах
> Как реализовать сериализацию в приложении Sketcher
> Печать в библиотеке MFC
> Как использовать функции класса представления для поддержки печати
> Что содержит объект cPrintinfo и как он используется в процессе
печати
> Как реализовать мноrостраничную печать в приложении Sketcher
Возможности, реализованные на настоящий момент в проrраl\fме Sketcher, поз ВО"
ляют создавать достаточно развитые документы с представлениями в разных масшта
бах, но информация этих документов временна, поскольку нет средств их сохранения.
В этой rлаве речь пойдет о том, как сохранять документы на диске. Кроме Toro, вы
узнаете, как выводить документ на принтер.
rлава 19. Сохранение и печать документов 1131
Что такое сериализация
Документ проrраммы на базе библиотеки MFC не простая сущность, это объект
класса, который может оказаться очень сложным. Обычно он содержит Iпирокое раз
нообразие объектов, каждый из которых может содержать друrие объекты, а те, в свою
очередь, MOryт содержать в себе еlце объекты... и такая структура может распростра
няться на множество уровней.
Вам нужно иметь возможность сохранять документ в файле, но запись объекта клас..
са в файл представляет собой некоторую проблему, поскольку это не то же самое, что
базовый элемент данных вроде целоrо числа или символьной строки. Базовый элемент
данных состоит из известноrо количества байт, поэтому, чтобы записать ero в файл, He
обходимо записать это определенное количество байт. И наоборот, если вы знаете, что
в файл было записано значение типа int, то, чтобы получить ero обратно, необходимо
просто прочесть соответствующее количество байт.
С записью объекта дела обстоят иначе. Даже если вы запишете все переменные
члены объекта, этоrо недостаточно для Toro, чтобы получить обратно исходный
объект. Объекты класса содержат функциичлены наряду с переменнымичленами,
и все члены как данные, так и функции имеют спецификаторы доступа. Таким об
разом, чтобы сохранить объекты во внешнем файле, информация, записываемая в этот
файл, должна содержать полные спецификации всех участвующих структур классов.
Процесс чтения также должен быть достаточно интеллектуальным, чтобы полностью
синтезировать исходные объекты на основе данных файла. Библиотека MFC подд;ержи
вает механизм, называемый серuалuза'Цuей, который помоrает реализовать ввод с диска
и вывод на диск объектов классов с минимальными затратами времени и усилий.
Основная идея, лежащая в основе сериализации, зак,"'Iючается в том, что любой ce
риализуемый класс сам заботится о сохранении и восстановлении себя. Таким образом,
чтобы классы приложения Sketcher стали сериализуемыми (в данном случае речь идет
о классе СЕ 1 етеп t и унаследованных от Hero классах фиryр), они должны уметь записы
вать себя в файл. Но чтобы класс был сериализуемым, все типы данных, используемые
в ero объявлении, также должны быть сериализуемыми.
Сериализация документа
Все это звучит довольно замысловато, но основная возможность сериализации дo
кумента изначально встраивается в приложение мастером Application Wizard. Обработ"
чики для пунктов меню FileSave (ФайлСохранить), FileSave As (ФайлСо?,ранить
как) и FileOpen (ФайлОткрыть) предполаrают, что будет реализована сериализация
документов, и уже содержат код, необходимый для ее поддержки. Взrляните на части
определения и реализации класса CSketcherDoc, относящиеся к созданию документов
с использованием сериализации.
Сериализация в определении класса документа
Код для определения класса CSketcherDoc, обеспечиваЮIЦИЙ сериализацию объек"
та документа, в следующем фраrменте кода выделен полужирным шрифтом.
class CSketcherDoc : public CDocument
{
protected: // Создается ТОЛЬRО сериализацией
CSketcherDoc();
DECLAREDYNCREATE(CSketcherDoc)
1132 Visual С++ 2010. Полный курс
// Остальная часть класса...
// Пере определения
public:
virtual BOOL OnNewDocument();
virtual void Serialize(CArchive& ar);
// Остальная часть класса...
} ;
Здесь представлены три элемента, имеющие отношение к сериализации объекта дo
кумента.
1. Макрос DECLARE DYNCREATE ( ) .
2. Функция..член Serialize ().
3. Стандартный конструктор класса.
Макрос DECLARE DYNCREATE () позволяет инфраструктуре приложения динамиче
ски создавать объекты класса CSketcherDoc во время сериализации процесс ввода.
В реализации класса ero дополняет друrой макрос IMPLEMENT DYNCREATE ( ) . Этот
макрос применяется только к классам, унаследованным от класса CObj ect, но, как вы
вскоре увидите, это не единственная пара макросов, которые MOryт использоваться в
этом контексте. Для любоrо класса, который вы хотите сериализовать, класс CObj ect
должен быть прямым или непрямым базовым классом, поскольку он добавляет возмож
ности, позволяющие сериализации работать. Вот почему класс CElement унаследован
от класса CObj ect. Почти все классы библиотеки MFC унаследованы от класса CObj ect
и как таковые являются сериализуемыми.
@ . i .... ........., ... ............. . ............... , .: .' """"""'" ................... , .......A . . . , ........
tl Примечание. В иерархической диаrрамме в справочнике Мiсrоsоft Foundation Class
" Reference для среды разработки Visual С++ 201 О показаны те классы, которые не унаследо
11 ваны от класса CObj ect. Обратите внимание на то, что класс CArchi ve входит в этот список.
Определение класса таюке включает объявление виртуальной функции Serialize ().
Каждый сериализуемый класс должен содержать эту функцию. Она вызывается дЛЯ BЫ
полнения операций сериализации как ввода, так и вывода с переменнымичленами
класса. Объект типа CArchi ve, передаваемый в качестве apryмeHTa этой функции, опре
деляет, какая операция выполняется ввода или вывода. Вы узнаете об этом подроб
нее, коrда будете рассматривать реализацию сериализации класса документа.
Обратите внимание на то, что класс явно определяет стандартный конструктор.
Это также существенно для правильной работы сериализации, поскольку стандартный
конструктор используется инфраструктурой для синтеза объекта при чтении из файла,
и синтезируемый объект затем заполняется данными из файла, устанавливая значения
переменныхчленов объекта.
Сериализация в реализации класса документа
в файле SketcherDoc. срр, содержащем реализацию класса CSketcherDoc, Ha
ходятся две части, относящиеся к сериализации. Первая это макрос IMPLEMENT
DYNCRETE ( ) , дополняющий макрос DECLARE DYNCREATE ( ) .
rлава 19. Сохранение и печать документов 1133.
// SketcherDoc.cpp : реализация класса CSketcherDoc
//
#include "stdafx.h"
// Макрос SHAREDНANDLERS может быть определен в проекте ATL, реализующем
// предварительньм просмотр, эскизы и фильтры поиска, позволяя совместно
// использовать код документа в этом проекте.
#ifndef SHARED НANDLERS
#include "Sketcher.h"
#include "PenDialog.h"
#endif
#include "SketcherDoc.h"
#include <propkey.h>
#ifdef DEBUG
#define new DEBUG NEW
#endif
// CSketcherDoc
IМPLEМENTDYNCREATE(CSketcherDoc, CDocument)
// Карты сообщений и остальная часть файла...
Данная макрокоманда определяет для класса CSketcherDoc базовый класс CDocu
rnent. Это требуется для правильноrо динамическоrо создания объекта CSketcherDoc,
включая члены, унаследованные от базовоrо класса.
Функция Serialize ()
Реализация класса также включает определение функции Serialize ().
void CSketcherDoc::Serialize(CArchive& ar)
{
if (ar.IsStoring())
{
// TODO: добавить сюда код сохранения
}
else
// TODO: добавить сюда код заrрузки
}
Функция сериализует переменныечлены класса. ApryMeHT, передаваемый функ
ции, это ссылка на объект ar класса CArchive. Функциячлен IsStoring () объекта
этоrо класса возвращает значение TRUE, если нужно выполнить операцию сохранения
переменныхчленов в файле, и значение FALSE если необходимо читать переменные"
члены из ранее coxpaHeHHoro документа.
Поскольку мастер Application Wizard понятия не имеет о том, какие данные coдep
жит документ, процесс записи и чтения этой информации находится в вашей компетен..
ции, что указано в комментариях. Чтобы понять, как это делается, рассмотрим внима
тельнее класс CArchi ve.
1134 Visual С++ 2010. Полный курс
Класс CArchi ve
Класс CArchi ve это механизм, управляющий сериализацией. Он предоставляет
эквивалент библиотеки MFC потоковых операций С++, которые вы использовали ДЛЯ
чтения с клавиатуры и записи на экран в примерах консольных проrрамм. Объект клас..
са CArchi ve библиотеки MFC предоставляет механизм для направления ваших объек"
тов в файл либо записи их из входноrо потока, автоматически реконструируя объекты
этоrо класса в процессе.
Объект класса CArchive включает в себя связанный с ним объект CFile, обеспечи
вающий возможности BBoдaBЫBoдa для двоичных файлов, а также действительное под"
ключение к конкретному физическому файлу. Во время сериализации объект CFile за
ботится обо всех деталях операций файловоrо ввода"вывода, а объект класса CArchi ve
имеет дело с лоrикой структурирования данных объекта, подлежащих записи, или ре..
конструкции объетов на основе прочитанной информации. Вам придется заботиться
о деталях связанноrо объекта CFile только в том случае, если вы хотите сконструи
ровать свой собственный объект CArchive. Для документов в приложении Sketcher
инфраструктура обеспечит все необходимое и передаст объект ar типа CArchi ve, ко..
торый будет им создан, функции Serialize () из класса CSketcherDoc. Вы сможете ис..
пользовать один и тот же объект в каждой функции Serialize (), которые добавите в
классы фиryр при реализации их сериализации.
Класс CArchive переrружает операторы вставки и извлечения (» и «), необхо
димые для ввода и вывода объектов классов, унаследованных от класса CObj ect, плюс
диапазон базовых типов данных. Эти переrруженные операторы работают с перечис
ленными в табл. 19.1 объектными и базовыми типами.
Таблица 19.1. Типы, с которыми работают операторы вставки и извлечения, переrру-
женные в классе CArchive
Тип
Определение
bool
float
double
БУТЕ
char
wchar t
short
CObject*
Лоrические значения true или false
Стандартный тип с плавающей запятой одинарной точности
Стандартный тип с плавающей запятой двойной точности
8битовое целое без знака
8битовый символ
16битовый символ
16битовое целое со знаком
32битовое целое со знаком
64битовое целое со знаком
64..битовое целое без знака
16..битовое целое без знака
32битовое целое без знака
Объект CString, определяющий строку
Объект, определяющий размер как пару сх, су
Объект, определяющий точку как пару сх, су
Объект, определяющий прямоуrольник по ero левому верхнему и правому
нижнему уrлам
Указатель на объект класса CObj ect
LONG и long
LONGLONG
ULONGLONG
WORD
DWORD и unsigned int
CString
SIZE и CSize
POINT и CPoint
RECT и CRect
rлава 19. Сохранение и печать документов 1135
Для базовых типов данных в ваших объектах используются операторы вставки и
извлечения для сериализации данных. Чтобы прочесть или записать объект сериа
лизуемоrо класса, производный от класса CObj ect, можно либо вызвать функцию
Serialize () для объекта, либо воспользоваться операторами извлечения или вставки.
Какой бы пyrь вы ни выбрали, следует обеспечить соrласованность между вводом и вы..
водом, поэтому вы не должны выводить. объект с использованием оператора вставки,
а затем читать ero обратно функцией Serialize () , или наоборот.
Коrда тип объекта при чтении не известен, как, например, в случае указателей в
списке фиryр документа, необходимо использовать только функцию Serialize (). Это
вводит в действие механизм виртуальных функций, поэтому каждый раз вызывается
функция Serialize () , соответствующая типу объекта, с которым она вызвана, что
определяется во время выполнения.
Объект CArchi ve конструируется либо ДЛЯ сохранения объектов, либо для их извле--
чения. Функция IsStoring () класса CArchive возвраIIает значение TRUE, если объект
предназначен для вывода, и значение FALSE если для ввода. Вы видели это в операто
ре if функции Serialize () класса CSketcherDoc.
Существует множество дрyrих Функцийчленов класса CArchi ve, относящихся к под"
робностям механизмов процсса сериализации, но обычно для применения сериализа..
ции в своих проrраммах знать их не обязательно.
Возможности классов на базе класса CObject
Существуют три уровня возможностей, доступных вашим классам, если они проис
ходят от класса CObj ect библиотеки MFC. Конкретный уровень, который вы получаете
в классе, определяется тем, какой из трех макросов применяется в определении класса
(табл. 19.2).
Таблица 19.2. Макросы, определяющие уровень возможностей
Макрос
DECLAREDYNAМIC()
DECLAREDYNCREATE()
DECLARE SERIAL ( )
Возможность
Подцержка информации о классе во время выполнения
Подцержка информации о классе во время выполнения и динамическое
создание объектов
Поддержка информации о классе во время выполнения, динамическое
создание объектов и сериализация объектов
Каждый из этих макросов требует дополняющеrо макроса, именованноrо с префик"
сом IMPLEMENT вместо DECLARE и помещенноrо в файл, который содержит реализа
цию класса. Как следует из табл. 19.2, эти макросы обеспечивают нарастающий объем
возможностей, поэтому сосредоточим внимание на третьем из них DECLARE SERIAL ( ) ,
поскольку он обеспечивает все, что преДIпествующие e и даже более. Этот макрос сле
дует применять, чтобы обеспечить возможность сериализации ваших классов. Он требу
ет добавления макроса IMPLEMENT SERIAL () к файлу, содержащему реализацию класса.
Возможно, вас удивит, почему класс документа использует макрос DECLARE
DYNCREATE ( ) , а не DECLARE SERIAL ( ) ? Макрос DECLARE SERIAL () обеспечивает воз
можность сериализации класса, а также динамическое создание объектов класса, таким
образом, он включает в себя возможности макроса DECLARE DYNCREATE ( ) . Ваш класс
документа не нуждается в сериализации, так как инфраструктура должна только син
тезировать объект документа, а затем восстанавливать значения переменныхчленов;
1136 Visual С++ 2010. Полный курс
однако переменныечлены документа долж'J-lыl быть сериализуемыми, поскольку этот
процесс применяется для их сохранения и извлечения.
Макрос, обеспечивающий сериализацию класса
Наличие макроса DECLARE SERIAL () в.определении класса, производноrо от класса
CObj ect, обеспечивает доступ к поддержке сериализации, предоставляемой классом
CObject. Это включает специальные операторы new и delete, отвечающие за обнару
жение утечек памяти в отладочном режиме. Для их использования вам не нужно делать
ничеrо особеННОI:О, поскольку все работает автоматически. '
Макрос требует указания имени класса в качестве apryмeHTa, поэтому для сериализа
ции класса CElement понадQбится добавить следующую строку к определению класса.
DECLARE SERIAL(CElement)
Примечание. Здесь точка с запятой не требуется, поскольку это вызов макроса, а не
оператор С++.
IIеважно, куда именно вы поместите макрос в пределах определения класса, но
если вы будете помещать ero в первой строке, то всеrда сможете убедиться в ero при
сутствии, даже коrда определение класса состоит из множества строк кода.
Макрос IMPLEМENT SERIAL ( ) , который вы помещаете в файл реализации класса, по
лучает три apryмeHTa. Первый apryмeHT имя класса, второй имя непосредственноrо
базовоrо класса, третий apryMeHT беззнаковое 32битовое целое, идентифицирующее
номер схемыl, или номер версии, вашей проrраммы. Номер схемы позволяет защитить
процесс сериализации от проблем, которые MOryт возникнуть, если вы пишете объек
ты в одной версии проrраммы, а читаете в дрyrой, в которой классы MOryт отличаться.
Например, можете добавить в реализацию класса CElement такую строку кода.
IMPLEМENTSERIAL(CElement, CObject, 1)
Если вы последовате-'!ьно модифицируете определение класса, то должны при этом
изменять номер схемы на чтото друrое, например на 2. Если проrрамма попытается
прочесть данные, которые были записаны с номером схемы, отличным от номера Teкy
щей активной проrраммы, создается исключение. Лучше Bcero поместить этот макрос
в первой строке, следующей за директивами #include и всеми начальными KOMMeHTa
риями в файле . срр.
Коrда класс CObj ect является непрямым базовым классом, как, например, в случае
класса CLine, для корректной работы сериализации каждый класс в иерархии должен
иметь добавленный макрос сериализации в классе BepxHero уровня. Чтобы работала
сериализация класса CLine, макрос также должен быть добавлен к классу CElement.
Процесс сериализации
Общий процесс сериализации документа в упрощенном; виде показан на рис. 19.1.
Функция Serialize () в объекте документа вызывает функцию Serialize () (или
использует переrруженный оператор вставки) для каждой из переменных"членов. Kor
да переменнаячлен является объектом класса, функция Serialize () для этоrо объекта
сериализирует каждую из переменныхчленов по очереди, до тех пор пока, в конечном
счете все базовые типы данных не будут записаны в файл. Поскольку большинство клас..
сов библиотеки MFC унаследованы от класса CObj ect, они содержат поддержку сериа
лизации, так что вы можете почти всеrда сеРИ(l!Iизовать объекты классов MFC
rлава 19. Сохранение и печать документов 1137
ВЫВОД документа с применением сериализации
Объект
документа
Внутренний
объект
Файл
Рис. 19.1. Общий про'Цесс сериализа'Ции документа
.Данные, с которыми вы имеете дело в Функцияхчленах Serialize () ваших классов
и в объекте документа приложения, во всех случаях являются переменными..членами.
О структуре вовлеченных в этот процесс классов, а также о любых дryrих данных, не..
обходимых для воспроизведения первоначальных объектов, автоматически заботится
объект класса CArchi ve.
Коrда вы наследуете класс CObj ect через множество уровней, функция Ser iali ze ( )
вашеrо класса и объект документа приложения в каждом случае должны вызывать член
Serialize () ero непосредственноrо предка. Так как сериализация не поддерживает
множественное наследование, должен существовать только один базовый класс для
каждоrо класса, определенноrо в иерархии.
Как реализовать сериализацию для класса
На основе предыдущей дискуссии можно подытожить действия, которые необходи"
мо предпринять для добавления в класс возможностей сериализации.
1. Удостоверьтесь, что класс прямо или косвенно унаследован от класса CObj ect.
2. Добавьте макрос DECLARE SERIAL () в определение класса (и в непосредствен
ный базовый класс, если это не класс CObject).
3. Объявите функцию Serialize () как член вашеrо класса.
4. Добавьте макрос IMPLEMENT SERIAL () в файл, содержащий реализацию класса.
5. Реализуйте в своем классе функцию Serialize () .
Теперь посмотрим, как можно реализовать сериализацию документов в проrрамме
Sketcher.
1138 Visual С++ 2010. Полный курс
Применение сериализации
Чтобы реализовать сериализацию в приложении Sketcher, необходимо реали
зовать функцию Serialize () в классе CSketcherDoc, чтобы она обрабатывала все
переменные--члены класса. Вам понадобится добавить сериализацию к каждому классу,
который задает объекты, включаемые в документ. Прежде чем вы начнете добавлять
сериализацию к классам приложения, нужно будет внести небольшие изменения в про
rpaMМY, чтобы зафиксировать факт внесения изменений в документэскиз. Это не явля
ется абсолютно необходимым, но весьма желательно, поскольку позволяет проrрамме
предотвращать закрытие документа без сохранения изменений.
Запись изменений в документе
YHac уже есть механизм для фиксации факта изменения документа, он использует
унаследованный член класса CSketcherDoc по имени SetModifiedFlag () . Вызывая эту
функцию соrласованно при каждом изменении документа, вы фиксируете факт TaKoro
изменения в переменнойчлене объекта класса документа. Это вызывает автоматический
запрос при попытке выхода из приложения без сохранения модифицированноrо дoкy
мента. ApryмeHT функции SetModifiedFlag () имеет тип BOOL и значение по умолчанию
TRUE. Если вы имеете возможность отметить, что документ не был изменен, то можете
вызвать эту функцию с apryмeHToM FALSE, хотя такая необходимость возникает редко.
Существуют только три случая, коrда требуется изменить объект документа.
о При вызове члена AddElernent () класса CSketcherDoc для добавления HOBoro
элемента.
о При вызове члена DeleteElernent () класса CSketcherDoc для удаления элемента.
О При перемещении элемента.
Вы леrко можете справиться с этими тремя ситуациями. Все, что необходимо cдe
лать, добавить вызов функции SetModifiedFlag () в каждую функцию, задействован
ную в этих операциях. Определение функции AddElernent () расположено в определе
нии класса CSketcherDoc. Вы можете расширить ero следующим образом.
void AddElement(CElement* pElement) // Добавить элемент в список
{
mElementList.AddTail(pElement);
SetмodifiedFlag(); // Установить фла модификации
Вы можете получить определение функции DeleteElernent () в классе CSketcherDoc,
щелкнув на имени функции в панели Class View (Представление классов). к нему потре-
буется добавить одну строку, как показано ниже.
void CSketcherDoc::DeleteElement(CElement* pElement)
{
if(pElement)
{
mElementList.remove(pElement); // Удалить указатель из списка
delete pElement; // Удалить элемент из распределяемой памяти
SetмodifiedFlag(); // Установить фла модификации
rлава 19. Сохранение и печать документов 1139
Необходимо установить флаr, если указатель pElernent не пуст, поэтому вы не може..
те просто поместить вызов функции, rде заблаrорассудится.
В объекте представления перемещение элемента происходит в функции..члене
MoveElernent (), вызанной обработчиком сооБIIения WMMOUSEMOVE, но вы изме
няете документ только тоrда, коrда нажата левая кнопка мыши. Если нажата правая
кнопка, элемент возвращается в свою исходную позицию, поэтому вызов функции
SetModifiedFlag () документа нужно добавить только в функцию OnLButtonDown () .
void CSketcherView::OnLButtonDown(UINT nFlags, CPoint point)
{
CClientDC aDC(this);
OnPrepareDC(&aDC);
aDC.DPtoLP(&point);
if(mMoveMode)
{
// Создать ROHTeRcT устройства
// Получить исправленное начало Rоординат
// Преобразовать ТОЧRУ в лоrические Rоординаты
// в режиме перемещения, поэтому сбросить элемент
mMoveMode false; // Отключить режим перемещения
mpSelected nullptr; // Отменить выделение элемента
GetDocument()>UpdateAllViews(O); // Перерисовать все
// представления
GetDocument()>SetмodifiedFlag(); // Установить флаr модификации
}
// Остальная часть фУНRЦИИ RaR раньше...
Вы вызываете унаследованную функцию"нлен GetDocurnent () класса представления,
чтобы получить доступ к указателю на объект документа, а затем используете этот указа
тель для вызова функции SetModifiedFlag () . Таким образом, покрываются все места,
rде документ может изменяться.
Если вы соберете и запустите проект Sketcher, а затем модифицируете документ или
станете добавлять к нему элементы, то перед выходом из проrраммы получите приrла
шение на сохранение документа. Конечно, пункт меню FileSave (ФайлСохранить)
пока ничеrо не делает, за исключением очистки флаrа модификации и сохранения пу"
cToro файла на диске. Необходимо реализовать сериализацию для правильной записи
документа на диск, и это будет следующим шаrом.
Сериализация документа
Первый этап предусматривает реализацию функции S е r i а 1 i z е () в классе
CSketcherDoc. В этой функции необходимо добавить код сериализации переменных
членов класса CSketcherDoc. Переменнычлены, объявленные в классе, приведены ниже.
protected:
int m PenWidth; / / Текущая ширина пера
CSize mDocSize; // Размер документа
ElementType mElement; // Тип текущеrо элемента
std::list<CElement*> mElementList; // Список элементов эскиза
К сериализации относятся два участка файла SketcherDoc. срр, содержащеrо реа..
лизацию класса CSketcherDoc. Первый это макрокоманда IMPLEMENT DYNCREATE () ,
которая дополняет макрокоманду DECLARE DYNCREATE ( ) .
Чтобы объект CSketcherDoc Mor- быть десериализирован, ero переменные
члены должны сериализироваться. Следовательно, необходимо добавить операторы
1140 Visual С++ 2010. Полный курс
сохранения и возвращения пяти переменныхчленов в функциичлене Serialize ()
класса. Но есть небольшая проблема. Объект list<CElement*> не сериализируем, по
тому что ero шаблон не происходит от класса CObj ect. Фактически не сериализируем
1-lU одU1-l из контейнеров STL, поэтому об их сериализации вы всеrда должны заботиться
самостоятельно.
Однако не все потеряно. Если вы можете сериализировать объекты, на которые
указывают указатели в контейнере, вы сможете восстановить и контейнер, коrда воз
никнет необходимость прочитать ero.
Примечание. В действительности библиотека MFC определяет контейнерные классы,
такие как CList, которые вполне сериализируемы. Но если бы вы использовали их в при
ложении Sketcher, то не узнали бы, как можно сделать класс сериализуемым.
Реализуйте сериализацию объекта документа следующим образом.
void CSketcherDoc::Serialize(CArchive& ar)
{
if (ar.IsStoring())
{
ar « m Color // Сохранить текУЩИЙ eT
« staticcast<int>(mElement) // текУЩИЙ тип элемента
« m PenWidth / / текущую ширину пера
« mDocSize; // и текУЩИЙ размер документа
ar« mElementList.size();
// Хранит количество элементов
/ / в списке
/ / Теперь сохранить элементы из списка
for(auto iter = mElementList.begin() ;
iter != mElementList.end() ; ++iter)
ar « *iter; // Сохранить элемент
}
else
int elementType(O);
ar » m Color
» elementType
» m PenWidth
» mDocSize;
// Хранит тип элемента
// Получить текУЩИЙ eT
// текУЩИЙ тип элемента
// текущую ширину пера
// и текУЩИЙ размер документа
mElement = staticcast<ElementType>(elementType);
sizet elementCount(O); // Хранит количество элементов
CElement* pElement(nullptr); // Указатель на элемент
ar » elementCount; // количество элементов
// Теперь получить все элементы и сохранить в списке
for(sizet i = О ; i < elementCount ; ++i)
{
ar » pElement;
mElementList.pushback(pElement);
}
}
--
rлава 19. Сохранение и печать документов 1141
Для четырех из переменныхчленов вы просто используете операторы извлечения
и вставки, которые в классе CArchive переrружены. Это работает с членом mColor,
даже несмотря на то, что ero типом является COLORREF, поскольку тип COLORREF это
то же, что тип long. Переменнаячлен m Element имеет тип ElementType, и процесс c
риализации не будет обрабатывать ее непосредственно. Но тип епиm целочисленный,
поэтому для сериализации можете привести ero к целому числу, а затем, десериализи
ровав ero как целое число, снова привести к типу ElementType.
Для списка элементов m ElementList сначала сохраните в архиве количество эле
ментов списка, поскольку впоследствии оно понадобится при чтении элементов. Затем
в цикле for запишите элементы из списка в архив. Для записи в архив элементов * iter,
являющихся указателями на тип CElement *, используйте оператор «о Механизм сериа
лизации понимает, что элемент * i te r это указатель, и заботится о записи в архив
элемента, на который он указывает.
Раздел else оператора if несет ответственность за чтение объекта документа из
архива. Оператор извлечения используется для возвращения из архива первых четы
рех членов в той же последовательности, в которой они были записаны. Тип элемента
читается в локальную целочисленную переменную elementType, а затем сохраняется в
переменной m Element как корректный тип.
Прочитайте количество элементов, записанных в архиве, и сохраните ero в локаль
ной переменной elementCount. И наконец, используйте переменную elementCount
для управления циклом for, который читает элементы из архива и сохраняет их в спи
ске. Обратите внимание на то, что вы не должны делать ничеrо особенноrо, с учетом
Toro факта, что первоначально элементы были созданы в распределяемой памяти. Ме..
ханизм сериализации заботится о восстановлении элементов в распределяемой памяти
автоматически.
Если вас интересует вопрос, откуда берется объект list<CElement*>, коrда проис
ходит десериализация, то он оздается процессом сериализации с использованием CTaH
дартноrо конструктора класса CSketcherDoc. Именно так создается объект OCHoBHoro
документа и ero неинициализированные переменныечлены. Волшебство, не так ли?
Это и все, что необходимо для сериализации переменныхчленов класса документа,
но сериализация элементов списка требует вызова функции Serialize () для классов
элементов, чтобы сохранять и возвращать сами элементы. Таким образом, вам также
нужно реализовать сериализацию для этих классов.
Сериализация классов элементов
Все классы фиryр сериализуемые, поскольку они унаследованы от базовоrо класса
CElement, который, в свою очередь, унаследован от класса CObject. Причина выбора
класса CObj ect в качестве базовоrо для класса CElement связана исключительно с He
обходимостью получить под"цержку сериализации. Удостоверьтесь, что для каждоrо из
классов формы определен стандартный конструктор. Процесс десериализации требу"
ет, чтобы этот конструктор был определен.
Теперь можете добавить под"цержку сериализации в каждый из классов фиryp, доба
вив код сериализации переменныхчленов в функциючлен Serialize (). Начать мож
но с базовоrо класса CElement, определение KOToporo потребуется модифицировать
следующим образом.
class CElement: public CObject
{
DECLARESERIAL(CElement)
1142 Visual С++ 2010. Полный курс
protected:
int m Реп;
COLORREF m Color;
CRect mEnclosingRect;
public:
virtual CElement() {} // Виртуальный деструктор
// Виртуальная операция рисования
virtual void Draw(CDC* рОС, CElement* pElementO){}
virtual void Move(const.CSize& aSize) {} // Перемещение элемента
CRect GetBoundRect(); // Получить описьrnающий элемент прямоуrольник
virtual void Serialize(CArchive& ar); // Функция сериализации класса
protected:
CElement(void);
// Ширина пера
// Цвет элемента
// Прямоуrольник, описывающий элемент
// Чтобы предотвратить вызов
} ;
Здесь добавлен макрос DECLARE SERIAL ( ) , а также объявление виртуальной функ
ции Serialize () .
у вас уже есть стандартный конструктор, созданный мастером Application Wizard.
Вы изменяете ero на protected, хотя ero спецификатор доступа не имеет значения
до тех пор, пока он не указан явно в определении класса. Он может быть public,
protected или private, и сериализация все равно будет работать. Но если вы забудете
включить стандартный конструктор, то получите сообщение об ошибке при компиля..
ции макроса IMPLEMENT SERIAL ( ) .
Макрос DECLARE SERIAL () необходимо добавить к каждому из производных клас
сов CLine, CRectangle, CCircle, CCurve и CText с соответствующими имена..
ми классов в качестве apryMeHTa. Вы также должны добавить объявление функции
Serialize () как открытоrо члена класса.
В начало файла Elements . срр следует добавить макрос
IМPLEМENТSERIAL(CElement, CObject, VERSIONNUМВER)
Определите константу VERSIONNUMBER в файле SketcherConstants. h, добавив
следующие строки.
// Номер версии прораI для использования при сериализации
const UINT VERSION NUМEER 1;
Затем ту же константу можно использовать при добавлении макроса для каждоrо из
классов фиryp. Например, в класс CLine потребуется добавить строку
IMPLEМENTSERIAL(CLine, CElement, VERSIONNUМEER)
Для классов друrих фиryp все аналоrично. Коrда вы модифицируете любые классы,
относящиеся к документу, то все, что нужно сделать, это изменить определение кон..
станты VERSION NUMBER в файле SketcherConstants . h и указать новый номер версии.
По желанию можете поместить все операторы IMPLEMENT SERIAL () в начало файла.
Ниже показан их полный набор.
IMPLEМENTSERIAL(CElement, CObject, VERSIONNUМEER)
IMPLEМENTSERIAL(CLine, CElement, VERSIONNUМEER)
IMPLEМENTSERIAL(CRectangle, CElement, VERSIONNUМEER)
IMPLEМENTSERIAL(CCircle, CElement, VERSIONNUМEER)
IMPLEМENTSERIAL(CCurve, CElement, VERSIONNUМEER)
IMPLEМENTSERIAL(CText, CElement, VERSIONNUМEER)
rлава 19. Сохранение и печать документов 1143
Функции Serialize () классов фиrур
Теперь можно реализовать функциючлен Serialize () для каждоrо из классов фи..
ryp. Начнем с класса CElement.
void CElement::Serialize(CArchive& ar)
{
CObject::Serialize(ar);
if (ar.IsStoring())
{
// Вызвать ФУНКЦИЮ базовоrо класса
ar « m PenWidth // ширину пера,
« m Color // Сохранить цвет
« mEnclosingRect; // и описьrnающий прямоуrольник
}
else
ar » m PenWidth // ширина пера
» m Color // цвет
» mEnclosingRect; // и описывающий прямоуrольник
}
}
Эта функция имеет ту же форму, что и ранее добавленная в класс CSketcherDoc. Все
переменнычлены, определенные в классе CElement, поддерживаются переrруженны
ми операторами ВСТd.вки и извлечения, поскольку все необходимое делается этими оп
раторами. Чтобы rарантировать сериализацию унаследованных переменныхчленов,
необходимо вызвать функциючлен Serialize () для класса CObject.
Для класса CLine эту функцию можно записать следующим образом.
void CLine::Serialize(CArchive& ar)
{
CElement::Serialize(ar); // Вызов функции базовоrо класса
if (ar.IsStoring())
{
ar « m StartPoint
« m EndPoint;
// Сохранить начальную точку линии
// и ее конечную точку
}
else
ar » m StartPoint
» m EndPoint;
// Извлечь начальную точку линии
// и ее конечную точку
}
}
Опятьтаки все переменные"члены поддерживаются операторами извлечения и
вставки объекта ar класса CArchi ve. Вы вызываете Функциючлен Serialize () базово
ro класса CElement для сериализации ero переменныхчленов, и это приводит К вызову
Функциичлена Serialize () класса CObject. Вы можете видеть, как процесс сериали
зации проходит по иерархии классов.
Функциячлен Serialize () класса CRestangle проста.
void CRectangle::Serialize(CArchive& ar)
{
CElement::Serialize(ar);
// Вызов Функции базовоrо класса
}
1144 Visual с++ 2010. Полный курс
Здесь только вызывается функция Serialize () базовоrо класса, поскольку данный
класс не имеет дополнительных переменныхчленов.
Класс CCircle также не имеет дополнительных переменныхчленов, помимо уна..
следованных от класса CElement, поэтому функция Serialize () также только вызы"
вает функцию базовоrо класса.
void CCircle::Serialize(CArchive& ar)
{
CElement::Serialize(ar);
// Вызов функции базовоrо класса
}
.
Для класса CCurve также понадобится удивительно HeMHoro работы. Класс CCurve
использует контейнер vector<CPoint> для хранения определенных точек, а поскольку
они не сериализуемы непосредственно, вы должны позаботиться об этом самостоятель..
но. При сериализируемом документе это не будет чрезмерно трудным. Ero функцию
Serialize () можно записать следующим образом.
void CCurve::Serialize(CArchive& ar)
{
CElement::Serialize(ar); // Вызов функции базовоrо класса
// Сериализовать вектор точек
if (ar.IsStoring())
{
ar « m Points. ize (),,: / / Сохранить количество точек
// Теперь сохранить точки J
for(sizet i О ; i< mPoints.size() ; ++i)
ar « m Points[i];
}
else
size t nPoints(O);
ar » nPoints;
// Теперь получить
CPoint point;
for(sizet i О ;
{
// Хранит количество точек
// Получить количество точек
точки
i < nPoints ; ++i)
ar » point;
mPoints.pushback(point);
}
}
}
Сначала вы вызываете функцию Serialize () базовоrо класса, чтобы обеспечить
сериализацию унаследованных членов класса. Сохранение содержимоrо вектора ocy
ществляется тем же способом, который мы использовали для сериализации документа.
Сначала в архив записывается количество элементов, а затем сами элементы. Класс
CPoint сериализируем, таким образом, он сам позаботится о себе. Чтение точек тоже
не сложно. Достаточно сохранить в цикле for каждый прочитанный объект из архи
ва в вектор mPoints. Чтобы создать простой объект класса, процесс сериализации
использует конструктор без параметров класса CCurve, таким образом, член класса
vector<CPoint> создается в этом процессе.
Последний класс, для KOToporo потребуется добавить реализацию функции
Serialize () в файл Elements . срр, класс CText.
rлава 19. Сохранение и печать документов 1145
void CText::Serialize(CArchive& ar)
{
I
CElement::Serialize(ar); // Вызов функции базовоrо класса
if (ar.IsStoring())
{
ar « mString;
// Сохранить текстовую строку
}
else
ar » mString;
// Получить текстовую строку
}
После вызова функции базовоrо класса вы сериализуете переменнуючлен m String,
используя операторы вставки и извлечения объекта ar. Класс CString, хотя и не YHa
следован от класса CObj ect, все же полностью поддерживается классом CArchi ve за
счет переrруженных операторов.
Испытание сериализации
Это и все, что нужно для реализации сохранения и извлечения документов в про
rpaMMe Sketcher! Параметры файловоrо меню для сохранения и восстановления те..
перь полностью функциональны без добавления какоrолибо дополнительноrо кода.
Если вы соберете и запустите проrрамму Sketcher после включения описанных выше
изменений, то сможете сохранять и восстанавливать файлы, и будете получать авто..
матический запрос на сохранение модифицированноrо документа, коrда попытаетесь
закрыть проrрамму или выйти из нее (рис. 19.2).
': 1
' '
I::
- , r
Sketcher - Sketch1
1 FiJe Edit View
L с.з ....J !
,I , .;п
Element Реп
Window Help
, ;Y'JIi! >
/ о ? '\.ТJ
-ъ-х.
,
@. .:::..
....
f L
'
I
I
I
I
1
I
1
I
1
I
I
I
I
I
I
I
I
I
I
I
I
I
I
I
I
I
1.
I -t
! View Scafe 1
I
I
I! Ready
l'7Iiil
( .&< 1
1 I .
I : '
1 1 ' 1
I ! Sa\re changes to Sketch1? I
! I
I
1 I
I Уе. I No. I Cancd 1 1
L
Ап interesting wooden model
'"t
! I
.E-!! C:!':!,,j J
Рис. 19.2. Автоматический запрос на сохранение .flrtодИфИ1.j,ированноzо aaкY.flrteHma
1146 Visual с++ 2010. Полный курс
Запрос работает блаrодаря добавленным вами вызовам функции SetModifiedFlag ()
везде, rде вы обновляете документ. Если вы щелкнете на кнопке Yes (Да) на экране,
показанном на рис. 19.2, откроется диалоrовое окно FileSave As (ФайлСохранить
как), показанное на рис. 19.3.
х<
5a\re A
.. libr.aries .. Oo-cuments .. Sketch Fites
...,.. 7 j
р;
"" "........"" ""........ ..... ""'" ....... .....,
Organize ..
New fokfer
1
: ..
А
DOClJments Hbrary
Я::etсh Fifes
Arrange Ь.у: Fctder
Favorites
1.
Recent PI.ces I
;.
Desktop
Downloads
Name
Date mcclifiec!
Туре Se
No tterr! n1.atch 'iu,Jf search.
Desktop
libraries
Ivor
AppData
Contacts
Desktop
Downloads
F a-vorites
.. .
..................... .............
J
.
Fr(e пате Sketch1ske
Save jtS type [ Sketcher Files C".,ske}
..
. ]
! '
[:.:Е-.:] I Cancel ] J
...................... ..... ... .............. ...".............. ..... ......,..............
.. Hide Folders
Рис. 19.3. Диалоzовое О1(НО Save As
Это стандартное диалоrовое окно Windows для упомянутоrо пункта меню. Оно пол..
ностью функционально и под"церживается кодом, предоставленным инфраструктурой.
Имя файла документа создается по присвоенному при первом открытии документа, а
расширение файла автоматически определяется как. ske. Теперь приложение облада
ет полной поддержкой файловых операций с документом.
Печать документа
Теперь обратимся к печати документа. У вас уже есть базовые возможности печати,
реализованные в проrрамме Sketcher, блаrодаря инфраструктуре и предусмотритель"
ности мастера Application Wizard. Пункты меню FilePrint (ФайлПечать), FilePrint
Setup (ФайлПараметры страницы) и FilePrint Preview (ФайлПредварительный
просмотр) уже работают. Выбор пункта FilePrint Preview отображает окно, показы
вающее текущий документ приложения Sketcher на странице (рис. 19.4).
Все, что есть в текущем документе, помещается на один лист бумаrи в текущем мас..
штабе представления. Если размеры документа выходят за пределы бумажноrо листа,
то часть документа, выходящая за rраницы листа, не печатается. Если вы щелкнете на
кнопке Print (Печать), данная страница будет отправлена на принтер.
В качестве базовой возможности, которую вы получаете бесплатно, это достаточ
но впечатляет, но не подходит для большинства случаев. Типичный документ нашей
....
rлава 19. Сохранение и печать документов 1147
проrраммы не будет помещаться на одну страницу, поэтому необходимо либо изменить
масштаб, чтобы все уместилось, либо печатать документ на нескольких страницах. Вы
можете добавить собственный код обработки печати, чтобы расширить возможности,
предоставленные инфраструктурой, но сначала необходимо разобраться, как печать
реализована в библиотеке MFC.
d Wooden mode[.ske
-;;..
..............,
) Print
и
Close
.. f'Vi. ScaJe . 1
Рис. 19.4. Окно предварителъноzо просмотра документа
Процесс печати
Печать документа управляется текущим представлением. Этот процесс неизбежно
несколько запyrан, поскольку печать по природе своей запyrанный процесс, который
потенциально вовлекает вас в реализацию собственных версий множества унаследо
ванных функций класса представления. На рис. 19.5 показана лоrика процесса и уча
ствующие в нем функции.
На рис. 19.5 видно, как последовательность событий управляется инфраструктурой
и как печать документа включает вызов пяти унаследованных методов класса представ
ления, которые должны быть переопределены. Функции..члены класса CDC, показанные
в левой части диаrраммы, взаимодействуют с драйвером устройства печати и вызыва
ются инфраструктурой автоматически.
Типичная роль каждой функции в текущем представлении в процессе операции
печати указана в примечаниях, расположенных рядом. Последовательность их вызо"
вов указана числами на стрелках. На практике не обязательно реализовывать все эти
функции, а только те, которые нужны для обеспечения конкретных требований к пе
чати. Обычно понадобится реализовать как минимум собственные версии функций
OnPreparePrinting () , OnPrepareDC () и OnPrint () . Далее в этой rлаве вы увидите
пример реализации этих функций в контексте проrраммы Sketcher.
Вывод данных на принтер выполняется таким же образом, как и на дисплей, че..
рез контекст устройства. Вызовы GDI, которые вы используете для вывода текста или
rрафики, независимы от устройства, поэтому они работают одинаково хорошо как с
принтером, так и с дисплеем. Единственное отличие в применяемом объекте CDC.
Функции CDC на рис. 19.5 взаимодействуют с драйвером принтера. Если документ,
подлежащий печати, требует для cBoero вывода более одной страницы, процесс
1148 Visual С++ 2010. Полный курс
иклически повторяет вызовы функции OnPrepareDC () для каждой следующей новой
страницы, как определено функцией EndPage ( ) .
Члены представления
1
OnPreparePrintingO
· Вычисление количества страниц
· Вызов DoPreparePrintingO
2 OnBeginPrintingO · Выделение ресурсов GDI
сос: :StartDocO 3
Цикл, пока остаются страницы со · Смена начала координат области
а. 4 OnPrepareDCO отображения
.....
· Установка атрибутов ос
>-
а.
.....
u
со
CDC::StartPageO 5 а.
-е- 6 1
:::I: · Печать нижних и верхних
:s:
OnPrintO колонтитулов
· Печать текущей страницы
CDC::EndPageO 7
CDC::EndDocO 8
9
OnEndPrintingO
· Возврат выделенных ресурсов GDI
Рис. 19.5. Лоzи'Ка npo1j,ecca печати
Все функции в вашем классе представления, участвующие в процессе печати, по
лучают в качестве apryMeHTa указатель на объект типа CPrintInfo. Этот объект обе..
спечивает связь между всеми функциями, управляющими процесс ом печати, поэтому
. раССМ9ТРИМ класс CPrintInfo более подробно.
Класс CPrintIn:fo
Объект CPrintInfo имеет основополаrающее значение в процессе печати, посколь
ку сохраняет информацию о задании печати и подробности о ero состоянии в любой
момент времени. Также он обеспечивает функции доступа и манипуляции данными.
Этот объект средство передачи информации от одной функции представления к дpy
rой во время печати, а также между инфраструктурой и вашими функциями представ
ления.
Объект класса CPrintInfo создается всякий раз, коrда вы выбираете пункты меню
File Print и File Print Preview. После использования каждой из функций текущеrо
представления, которая участвует в процессе печати, он автоматически удаляется
по окончании операции печати. Все переменныечлены класса CPrintInfo открыты
(public). Их перечень приведен в табл. 19.3.
rлава 19. Сохранение и печать документов 1149
Таблица 19.3. Переменные..члены класса CPrintInfo
Член Использование
тpPO Указатель на объект CPrintDialog, отображающий диалоrовое окно пе
чати
m bDirect Устанавливается инфраструктурой в значение TRUE при печати, чтобы
пропустить диалоrовое окно печати; в противном случае равно FALSE
m bPreview Член типа BOOL, имеющий значение TRUE при выборе пункта меню
FileqPrint Preview; в противном случае равно FALSE
m bContinuePrinting Значение типа BOOL. Если установлено в значение TRUE, инфраструкту
ра продолжает цикл печати, показанный на диаrрамме. При установке в
значение FALSE цикл печати завершается. Необходимо установить эту
переменную, только если не вы передаете счетчик страниц для операции
печати объекту CPrintlnfo (используя функциючлен SetMaxPage () ).
в этом случае вы отвечаете за своевременное извещение об окончании
печати установкой этой переменной в значение FALSE
m nCurpage Значение типа UINT, хранящее номер текущей страницы. Обычно страни
цы нумеруются, начиная с 1
m nNumpreviewpages Значение типа UINT, задающее количество страниц, отображенных в окне
предварительноrо просмотра печати. Может быть 1 или 2
m lpUserData Значение типа LPVOID, хранящее указатель на объект, который вы создае
те. Это позволяет создать объект для хранения дополнительной инфор
мации об операциях печати и ассоциировать ero с объектом CPrintlnfo
m rectDraw Объект CRec, определяющий используемую область страницы в лоrиче
ских координатах
m strPageDesc Объект CString, содержащий форматную строку, используемую инфра
структурой для отображения номеров страниц во время предварительно
ro просмотра печати
Объект CPrintInfo имеет открытые Функциичлены, перечисленные в табл. 19.4.
Таблица 19.4. Функции..члены класса CPrintInfo
Функция
SetMinPage(UINT
nMinPage)
SetMaxPage(UINT
nMaxPage)
GetMinPage() const
GetMaxPage() const
GetFromPage() const
GetToPage() const
Описание
AprYMeHT задает номер первой страницы документа. Типа возвращаемоrо
значения нет
AprYMeHT задает номер последней страницы документа. Типа возвращае..
Moro значения нет
Возвращает номер первой страницы документа, как тип UINT
Возвращает номер последней страницы документа, как тип UINT
Возвращает номер первой страницы документа, подлежащей печати, как
тип UINT. ЭТО значение устанавливается в диалоrовом окне печати
Возвращает номер последней страницы документа, подлежащей печати,
как тип UINT. ЭТО значение устанавливается в диалоrовом окне печати
При печати документа, состоящеrо из нескольких страниц, необходимо определить,
сколько печатных страниц займет документ, и сохранить эту информацию в объекте
CPrintInfo, чтобы сделать ее доступной инфраструктуре. Это можно сделать в вашей
версии Функциичлена текущеrо представления OnPreparePrinting () .
1150 Visual С++ 2010. Полный курс
Для установки номера первой страницы документа потребуется вызвать функцию
SetMinPage () в объекте CPrintInfo, которая получает номер страницы как apryмeHT
типа UINT. Возвращаемое значение отсутствует. Чтобы установить номер последней
страницы документа, вызовите функцию SetMaxPage ( ) , которая также получает номер
страницы как apryMeHT типа UINT и не возвращает значения. Если позднее захотите
извлечь эти значения, вызовите функции GetMinPage () и GetMaxPage () с объектом
CPrintInfo.
Задаваемые вами номера страниц сохраняются в объекте CPrintDialog, на который
указывает член m pPD в объекте CPrintInfo, и отображаются в диалоrовом окне, KOTO
рое открывается после выбора пункта меню FilePrint. Затем пользователь получает
возможность задать номера первой и последней печатаемых страниц, которые вы мо"
жете получить вызовом функций GetFromPage () и GetToPage () объекта CPrintInfo.
В каждом случае возвраJцается значение типа UINT. Диалоrовое окно автоматически Be
рифицирует вхождение номеров первой и последней печатаемых страниц в диапазон,
указанный минимальной и максимальной страницами документа.
Теперь вам известно, какие функции можно реализовать в классе представления для
управления печатью, оставив большую часть работы инфраструктуре. Вы также знаете,
какая информация доступна в объекте CPrintInfo, передаваемом функциям, занятым в
процессе печати. Вы получите rораздо более ясное понимание подробностей механиз..
ма печати, если реализуете базовую возможность мноrостраничной печати документов
Sketcher.
Реализация мноrостраничной печати
Вы используете режим отображения мм LOENGLISH в проrрамме Sketcher для уста..
новки всех параметров, а затем переключаетесь в режим мм ANISOTROPIC. ЭТО означа..
ет, что фиryры и размеры представления измеряются в сотых долях дюйма. Конечно, с
фиксированными физическими единицами измерения в идеале вы захотите печатать
объекты с их действительными размерами.
При размере документа, заданном как зооохзооо единиц, можете создавать дoкyмeH
ты площадью до 30 квадратных дюймов, которые заполнят несколько листов бумаrи,
если заполнить всю их область. Вычисление количества страниц, необходимых для пе..
чати эскиза, требует HeMHoro больше усилий, нежели при печати типичноrо TeKCTOBO
ro документа, поскольку в большинстве случаев для печати полноrо документаэскиза
понадобится двумерный массив страниц.
Чтобы избежать чрезмерноrо усложнения, предположим, что вы печатаете на HOp
мальном листе бумаrи (формата А4 или размером 8х11 дюймов (210х297 мм)) в пор
третной ориентации (т.е. большая сторона располаrается по вертикали). При любом
размере листа вы печатаете в центральной ero части размером 7,5хl0 дюймов. При
этих предположениях вы не должны заботиться о реальном размере бумаrи; вам нужно
только поделить документ на куски размером 750хl000 единиц. Документ, занимающий
более одной страницы, придется разбить, как показано в примере на рис. 19.6.
Как видите, страницы будут нумероваться построчно, поэтому в данном случае CTpa
ницы от l..й до 4й находятся в первой строке, а страницы с 5ю по 8ю во второй.
Эскиз, зани.мающий максимальный размер. зохзо дюймов, будет напечатан на 12 CTpa
ницах.
rлава 19. Сохранение и печать документов 1151
Страница 1
Количество по ширине = 4
Страница 2 Страница 3
..
..
Страница 4
N
tI
Ф
....
о
C.J
:о
(D
о
с:
о
m
Ь
Ф
:т
s
D
Wrox
Press
. с::::1
i
т
о
:Е
JS
2
r:[
о
"1""""
Страница 5
Страница 6
Страница 7
Страница 8
1
.............7.5 ДЮ Й МО В..............
Рис. 19.6. Печатъ М/flоzостраuи'Чuоzо докумеита
Получение полноrо размера документа
Чтобы узнать, сколько страниц занимает конкретный документ, вам нужно знать,
насколько велик эскиз, а для этоrо потребуется вычислить прямоуrольник, который
включает в себя весь документ. Это леrко сделать, добавив функцию GetDocExtent ()
в класс документа CSketcherDoc. Добавьте следующее объявление к открытому интер
фейсу класса CSketcherDoc.
CRect GetDocExtent(); // Получить оrраничивающий прямоуrольник для
// Bcero документа
Реализация также не составит большой проблемы.
// Получить прямоуrольник, оrраничивающий весь документ
CRect CSketcherDoc::GetDocExtent()
{
CRect DocExtent(O,O,l,l); // Начальный размер документа
for(auto iter mElementList.begin() ; iter ! m ElementList.end()
++iter)
docExtent.UnionRect(docExtent, (*iter)>GetBoundRect());
docExtent.NormalizeRect();
return docExtent;
}
Вы можете добавить определение этой функции в файл SketcherDoc. срр или про
сто добавить ее код, выбрав в контекстном меню представления классов пункт AddAdd
Function (ДобавитьДобавить функцию). Процесс перебирает в цикле все элементы в
документе, получая и накапливая оrраничиваЮlцие прямоуrольники каждоrо элемента
в переменной docExtent. Функция UnionRect () , член класса CRect, вычисляет мини
мальный прямоyrольник, содержащий два друrих прямоуrольника, которые переданы
1152 Visual с++ 2010. Полный курс
ей в качестве apryмeHToB, и помещает ero значение в объект CRect, для KOToporo вызва
на данная функция. Таким образом, значение переменной docExten t продолжает увели
чиваться, пока все элементы не поместятся в Hero. Обратите внимание, что вы инициа
лизируете переменную docExtent размером (О, 0,1,1), поскольку функция UnionRect ()
не работает правильно с прямоyrольниками нулевой длины или ширины
Сохранение данных печати
Функция OnPreparePrinting () в классе представления вызывается инфраструкту
рой приложения, чтобы позволить инициализировать процесс печати 'документа. Tpe
буемая базовая инициализация предназначена для Toro, чтобы предоставить информа
цию о том, сколько страниц документа будет отображаться в диалоrовом окне печати.
Необходимо сохранить информацию о страницах, требуемых вашим документом, кото-
рую можно будет использовать позже в дрyrих функциях представления, участвующих в
процессе печати. Создайте ее в Функциичлене Onprepareprinting () класса представ
ления, сохраните в объекте вашеrо собственноrо класса, который определите специ
ально для этой цели, и сохраните указатель на объект в объекте CPrintlnfo, доступ к
которому обеспечивает инфраструктура. Такой подход предназначен преимущественно
для демонстрации Toro, как работает данный механизм; в большинстве случаев вы об
наружите, что проще сохранить данные в вашем объекте представления в основном
потому, что это обеспечит более простой способ обращения к данным.
Необходимо сохранить количество страниц, приходящихся на ширину ДOКYMeH
та, mnWidths, и количество строк страниц,'приходящихся на длину документа, m
nLengths. Сохраните также левый верхний yrол прямоyrольника, включающеrо данные
документа, как объект CPoint по имени m DocRefPoint, поскольку он будет использо
ваться при нахождении позиции страницы, подлежащей печати, по ее номеру. Можете
сохранять имя документа в объекте CString по имени mDocTitle, чтобы иметь B03
можность добавить ero в качестве заrоловка каждой страницы. Определение класса,
подходящеrо для этоrо, может выrлядеть так, как показано ниже.
#pragma опсе
class CPrintData
public:
UINT printWidth; //
UINT printLength; //
UINT m nWidths;
UINT mnLengths;
CPoint m DocRefPoint;
CString mDocTitle;
Печатаемая ширина страницрr единицы 0,01 дюйма
Печатаемая длина страницы единицы 0,01 дюйма
// Количество страниц на ширину документа
// Количество страниц на длину документа
// Верхний левый уrол содержимоrо документа
// Имя документа
// Конструктор
CPrintData () :
printWidth (750)
, printLength(1000)
{ }
// 7,5 дюйма
// 10 дюймов
} ;
Добавьте к проекту новый файл заrоловка PrintData. h, щелкнув правой кнопкой
мышки на папке Header Files (Файлы заrоловка) в панели Solution Explorer (Проводник
решения) и выбрав в контекстном меню пункт AddqNew Item (ДобавитьqJ-Iовый эле
мент). После этоrо введите определение класса в новый файл.
rлава 19. Сохранение и печать документов 1153
Вам не понадобится отдельный файл реализации этоrо Масса. Стандартный KOH
структор (создаваемый автоматически) здесь вполне адекватен. Поскольку объект это
ro класса существует и используется кратковременно, вам не нужно применять класс
CObj ect в качестве базовоrо класса, как не нужны и дрyrие усложнения.
Процесс печати начинается с вызова функции..члена класса представления
OnPreparePrinting () , так что давайте разберем, как ее реализовать.
Подrотовка к печати
Мастер Application Wizard добавляет версии функций OnPreparePrinting () ,
OnBeginPrinting () и OnEndPrinting () в клас CSketcherView с caMoro начала.
Базовый код функции Onprepareprinting () вызывает функцию DoPrepare
Printing () в операторе return, как показано в следующем коде.
BOOL CSketcherView::OnPreparePrinting(CPrintInfo* pInfo)
{
// Подrотовка по умолчанию
return DoPreparePrinting(pInfo);
Функция DoPreparePrinting () отображает диалоrовое окно Print, используя ин..
формацию о количестве страниц, подлежащих печати, которая определена в объек"
те CPrintlnfo. Всеrда, коrда это возможно, необходимо вычислять количество стра"
ниц, подлежащих печати, и сохранять ero в объекте CPrintlnfo перед этим вызовом.
Конечно, во мноrих ситуациях может понадобиться информация для принтера из
контекста устройства прежде, чем вы сможете сделать это, например, коrда печа
тается документ, в котором количество страниц .зависит от размера используемоrо
шрифта. В таких случаях может оказаться невозможным получить счетчик страниц в
функциичлене OnBeginPrinting () , который получает указатель на контекст устрой
ства в качестве apryMeHTa. Эта функция вызывается инфраструктурой после функции
OnPreparePrinting () , поэтому информация, введенная в диалоrово окне Print (Пе
чать), уже доступна. Это означает, что вы можете также получить размер листа, BЫ
бранный пользователем в диалоrовом окне Print.
Предположим, размер листа достаточен, чтобы уместить область 7,5хl0 дюймов
для отображения данных документа, поэтому можете вычислить количество страниц в
функции OnPreparePrinting () . Ниже приведен необходимый для этоrо код.
BOOL CSketcherView::OnPreparePrinting(CPrintInfo* pInfo)
{
CPrintData*p(new CPrintData); // Создаь объек печааеиых даниых
CSketcherDoc* pDoc = GetDocument(); // Получиь указаель на ДOKyмeH
// Получиь всю облась ДOKyмeHa
CRect docExtent = pDoc>GetDocExtent();
// Сохрани очку ссылки на весь ДOKyмeH
p>mDocRefPoint = CPoint(docExtent.left, docExtent.top);
// Получиь имя файла ДOKyмeHa и сохрани ero
p>mDocTitle = pDoc>GetTitle();
// Вычислиь, сколько размеров нanечаанных страниц необходимо,
// чобы COOBeCOBaь ширине ДOKyмeHa
p>mnWidths = staticcast<UINТ>(ceil(
1154 Visual С++ 2010. Полный курс
staticcast<double>(docExtent.Width(»/p>printWidth»;
// Вычислиь, сколько размеров нanечаанных страниц необходимо,
// чобы COOBeCOBaь длине ДOKyмeHa
p>mnLengths = staticcast<UINT>(
ceil(staticcast<double>(docExtent.Height(»/p>printLength»;
// Усановить номер первой страницы равным 1, а номер
// последней страницы равным обму количесву страниц
pInfo>SetмinPage(l);
pInfo>SetмaxPage(p>mnWidths*p>mnLengths);
pInfo>mlpUserData = р; // Сохраниь адрес объека PrintData
return DoPreparePrinting(pInfo);
Первоначально вы создаете объект CPrintData в распределяемой памяти и co
храняете ero адрес в локальном указателе. После получения указателя на документ вы
получаете прямоуrольник, включающиЙ все элементы документа, вызывая функцию
GetDocExtent () , которую вы добавили в класс документа ранее в этой rлаве. Затем вы
сохраняете yrол этоrо прямоуrольника в члене m DocRefPoint объекта CPrintData и
помещаете имя файла, содержащеrо документ, в переменную m DocT i tle.
Следующие две строки кода вычисляют количество страниц, приходящееся на ши..
рину документа, а также количество страниц, приходящихся на ero длину. Количество
страниц, покрывающее ширину, вычисляется делением ширины документа на шири..
ну печатной области страницы с окруrлением до следующеrо большеrо целоrо с помо
щью библиотечной функции cell (), которая определена в заrоловке cmath. Дирек
тива #include для этоrо файла также должна быть добавлена в файл SketcherView.
срр. Например, вызов функции cell (2.1) возвращает значение з. о, вызов функции
cell (2.9) также возвращает значение з. о, а вызов функции cell (2 .1) возвращает
значение 2. о. Подобные вычисления выполняются и для определения количества
страниц по длине документа. Про изведение этих двух значений дает общее количество
страниц, подлежащих печати, и именно это значение применяется в качестве макси..
мальноrо номера страницы. Последний этап подразумевает сохранение адреса объекта
CPrintData в переменной"члене m lpUserData объекта plnfo.
Очистка после печати
Поскольку вы создали объект CPrintData в распределяемой памяти, необходимо уда..
лить ero o завершении работы с ним. Для этоrо добавим в функцию OnEndPrinting ()
следующий код.
void CSketcherView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* pInfo)
{
// Удалиь объек печа
delete staticcast<CPrintData*>(pInfo>mlpUserData);
Это все, что необходимо добавить в эту функцию в проrрамме Sketcher, но в He
которых случаях понадобится сделать несколько больше. Вся окончательная очистка
должна быть помещена сюда. Не забудьте удалить знаки комментария (/* * /), окружа..
ющие имя BToporo параметра; в противном случае функция не скомпилируется. В реа..
лизации по умолчанию имена параметров закомментированы, поскольку может и не
rлава 19. Сохранение и печать документов 1155
понадобится обращаться к ним в коде. Поскольку вы использовали параметр pInfo, то
должны убрать комментарий с Hero; в противном случае компилятор сообщит, что он
не определен.
Вам ничеrо не нужно добавлять к функции OnBeginPrinting () в проrрамме
Sketcher, но необходимо добавить код для выделения ресурсов GDI, таких как перья,
если они понадобятся в процессе печати. Затем необходимо удалить все это в процесс е
очистки в функции OnEndPrinting () .
Подrотовка контекста устройства
Пока что проrрамма Sketcher вызывает функцию OnPrepareDC () , которая YCTaHaB
ливает режим отображения мм ANISOTROPIC, чтобы учесть коэффициент масштаби..
рования. Потребуется внести некоторые дополнительные изменения, чтобы контекст
устройства был правильно подrотовлен для печати.
void CSketcherView::OnPrepareDC(CDC* pDC, CPrintInfo* pInfo)
{
int scale = mScale; // Сохранить масштаб локально
if(pDC>IsPrinting(»
scale = 1; // Если выполняется печать, установить масштаб 1
CScrollView::OnPrepareDC(pDC, pInfo);
CSketcherDoc* pDoc GetDocument();
рDС>SеtМарМоdе(ММANISОТRОРIС);
CSize DocSize pDoc>GetDocSize();
pDC>SetWindowExt(DocSize);
// Установить режим отображения
// Получить размер документа
// Установить размер окна
// Получить количество пикселей на дюйм для х и у
int xLogPixels pDC>GetDeviceCaps(LOGPIXELSX);
int yLogPixels pDC>GetDeviceCaps(LOGPIXELSY);
// ВЬNИСЛИТЬ размер области отображения по х и у
int xExtent = (DocSize.cx*scale*xLogPixels)/100;
int yExtent = (DocSize.cy*scale*yLogPixels)/100;
pDC>SetViewportExt(xExtent, yExtent); // Установить размер
// области отображения
Функция вызывается инфраструктуроЙ для вывода как на принтер, так и на экран.
Необходимо убедиться, что для преобразования из лоrических координат в координа..
ты устройства при выполнении печати используется масштаб ]. Если оставить все как
есть, то вывод пойдет в текущем масштабе представления, однако следует учитывать
масштаб при вычислении необходимоrо количества страниц, а также то, как устанав..
ливается начало каждой страницы.
Определить, имеете ли вы дело с контекстом устройства печати, можно вызовом
функциичлена IsPrinting () объекта CDC, которая возвращает значение TRUE, если
выполняется печать. Все, что необходимо сделать, коrда вы имеете дело с контекстом
устройства принтера, установить масштаб в 1. Разумеется, необходимо изменить
операторы, использующие значение масштаба, чтобы они использовали локальную
переменную scale вместо члена представления rn Scale. Значения, возвращенные BЫ
зовами функции GetDeviceCaps () с арryментами LOGPIXELSX и LOGPIXELSY, означа
ют количество лоrических точек на дюйм в направлениях х и у для принтера в случае
1156 Visual С++ 2010. Полный курс
печати либо эквивалентные значения для дисплея в случае рисовании на экране. Пото..
му размер автоматически адаптируется к области отображения для соответствующеrо
устройства, коrда вы посылаете на Hero вывод.
Печать документа
Записывать данные в контекст устройства принтера можно в функции OnPrint () .
Она вызывается один раз для каждой печатаемой страницы. Необходимо добавить
переопределение этой функции в класс CSketcherView, используя окно свойств клас
са. Выберите функцию OnPrint () из списка переопределений и щелкните на пункте
<Add> OnPrint в правой колонке.
Вы можете получить номер текущей страницы из переменнойчлена rn nCurpage
объекта CPrintlnfo и использовать это значение для нахождения координат поло..
жения документа, соответствующих левому верхнему yrлу текущей страницы. Как это
сделать, лучше Bcero пояснить на примере, поэтому предположим, что выполняется
печать страницы под номером семь в восьмистраничном документе (рис. 19.7).
DocRefPoint
printWidth = 750
printLength = 1000
mnCurPage = 7
r
printLength*((mnCurpage .. 1 )/mnWidths)
1 000*((7 1 )/4) = 1
1000*(6/4) =
1 000*1 = 1 000
......f
Страница 5 Страница 6
mnWidths: Количество страниц по ширине = 4 .
T' t
Страница 1 Страница 2 Страница 3 Страница 4 I
Страница 7
N
11
Ф
:I:
:s:
Е::;
Е:::[
о
t:
=r
:s:
сра-;;;Ц8i I
=r о
:s: m
:I: ....
:s:
о Е::;
о о
о
ll
xOrg, yOrg
xOrg = DocRefPoint.x + 1500
yOrg = DocRef Poi nt. у + 1000
750 единиц+!
printWidth*((mnCurpage 1 )%mnWidths)
750*(60/04) = 750*2 =1500
Рис. 19. 7. Печатъ страuu'ЦЪt под 'Номером семъ в восМtuстраuu'Чuом докумеите
Получить индекс rоризонтальной позиции страницы можно, уменьшив номер CTpa
ницы на 1 и взяв остаток от деления на количество ширин страниц, приходящихся
на ширину печатаемой области документа. Умножение результата на значение пер
менной printWidth дает координату х BepxHero левоrо уrла страницы относительно
BepxHero левоrо уrла прямоуrольника, описывающеrо все элементы документа. Ан ал о..
rично можете определить индекс вертикальной позиции в документе, разделив номер
текущей страницы, уменьшенный на 1, на ширину страниц, приходящихся на длину
Bcero документа. Умножив остаток на значение переменной printLength, вы получите
относительную координату у BepxHero левоrо уrла страницы. Эти два оператора можно
представить следующим образом.
--
rлава 19. Сохранение и печать документов 1157
CPrintData* p(staticcast<CPrintData*>(pInfo>m lpUserData));
int xOrg p>mDocRefPoint.x + p>printWidth*
((pInfo>mnCurPage l)%(p>mnWidths));
int yorg p>mDocRefPoint.y + p>printLength*
((pInfo>mnCurPage l)/(p>mnWidths));
Было бы неплохо напечатать имя файла документа вверху каждой страницы, но вы
хотите иметь rарантию, что данные документа не напечатаются поверх имени файла.
Вы также хотите отцентрировать на странице печатную область. Это можно сделать,
переместив начало системы координат в контексте устройства печати после mozo как
будет напечатано имя файла. Все это продемонстрировано на рис. 19.8.
Начало
координат
страницы
m rectDraw. right
111( )1
Начало страницы в документе
отображается сюда
r;riИ;h ;t, задаННЫ/1 т т
I
Начало координат I
I
документа : xOrg,yOrg
DocRefPoint :
I
I I
Это расстояние yOffset, заданный
(mrectDraw.bottom600)/2
Е
.9
(5
...Q
з:
со
(5
1:5
Q)
L..
I
Е
Печатаемая страница
Документ
Страница 7
'
Рис. 19.8. Соответствие печат1tой области и стра1tи1J,ыl
На рис. 19.8 показано соответствие между печатной областью в контексте устрой
ства и печатаемой страницей в рамке данных документа. Помните, что все это представ"
лен о в лоrических координатах эквивалент мм LOENGLISH в приложении Sketcher,
поэтому координата у имеет возрастающее отрицательное значение в направлении
сверху вниз. Страница показывает выражения смещений от начала координат страни
цы для области, определенной значениями переменых printWidth и printLength, в
которой мы собираемся печатать страницу. Поскольку вы хотите печатать информа..
цию документа в заштрихованную область, показанную на странице, отобразите точку
xOrg, yOrg в документе на позицию, показанную на печатаемой странице, которая ото..
бражается со смещением xOffset и yOffset от начала координат страницы.
1158 Visual ++ 2010. Полный курс
По умолчанию начало системы координат, которая используется для определе
ния элементов документа, отображается на начало системы координат контекста
устройства, но это можно изменить. В объекте CDC для этоrо предусмотрена функция
SetWindowOrg ( ) . Это позволяет определить точку в системе лоrических координат, ко..
торую вы хотите поставить в соответствие началу координат контекста устройства. Важ
но сохранить исходную начальную точку, возвращенную функцией SetWindowOrg () ,
как объект CPoint. Необходимо восстановить старую начальную точку по завершении
рисования текущей страницы; в противном случае, коrда вы приступите к печати сле
дующей страницы, значение переменнойчлена m rectDraw объекта CPrintlnfo будет
установлено неправильно.
Точка документа, которую необходимо отобразить на начало координат страницы,
имеет координаты xOrgxOffset, yOrgyOffset. Это может быть нелеrко визуализи
ровать, но вспомните, что установкой начальной точки окна вы определяете точку, OTO
бражающую начало координат представления. Если вы подумаете об этом, то увидите,
что точка xOrg, yOrg в документе это та точка, которая нужна на странице.
Ниже показан полный код печати страницы документа.
// Напечааь страницу ДOKyмeHa
void CSketcherView::OnPrint(CDC* pDC, CPrintInfo* pInfo)
{
CPrintData* p(staticcast<CPrintData*>(pInfo>mlpUserData»;
// Bывec имя файла ДOKyмeHa
pDC>SetTextAlign(TACENTER); // Центрироваь следУЮЩИЙ eKC
pDC>TextOut(pInfo>mrectDraw.right/2, 20, p>mDocTitle);
CString str;
str.Format(T("Page %u") , pInfo>mnCurPage);
pDC>TextOut(pInfo>mrectDraw.right/2, pInfo>mrectDraw.bottom20,
str);
pDC>SetTextAlign(TALEFT);
// Выравниваь eKC влево
// Вычислиь начальную очку екй страницы
int xOrg = p>mDocRefPoint.x +
p>printWidth*«pInfo>mnCurPage l)%(p>mnWidths»;
int yOrg = p>mDocRefPoint.y +
p>printLength*«pInfo>mnCurPage l)/(p>mnWidths»;
// Вычислить смещения центра облас рисования как положиельные
// значения
int xOffset = (pInfo>mrectDraw.right p>printWidth)/2;
int yOffset = (pInfo>mrectDraw.bottom p>printLength)/2;
// Смениь начало окна для соовесвия екущей странице
// и сохраниь capoe значение
CPoint 01dOrg = pDC>SetWindowOrg(xOrgxOffset, yOrgyOffset);
// Оnpеделиь осекающий npямоуrольник по размеру печааемой обласи
pDC>IntersectClipRect(xOrg, yOrg, xOrg+p>printWidth,
yOrg+p>printLength);
OnDraw(pDC); // Нарисоваь весь ДOKyмeH
pDC>SelectClipRgn(nullptr); // Удалиь осекающий npямоуrольник
pDC>SetWindowOrg(OldOrg); // Воссановиь capoe начало окна
....
rлава 19. Сохранение и печать документов 1159
Первый этап подразумевает инициализацию локальноrо указателя р адресом объек"
та CPrintData, который хранится в переменнойчлене mlpUserData объекта на ко..
торый указывает указатель plnfo. Затем выводится имя файла, записанное в объекте
CPrintData. Функциячлен SetTextAlign () объекта класса CDC позволяет определить
выравнивание последующеrо TeKcToBoro вывода по отношению к точке привязки тек..
стовой строки в функции TextOut () . Выравнивание определяется константой, пере
даваемой в apryMeHTe функции. Доступны три варианта указания выравнивания текста,
перечисленные в табл. 19.5.
Таблица 19.5. Выравнивание текста по rоризонтали
Константа
ТА LEFT
Выравнивание
Точка находится слева от оrраничивающеrо текст прямоуrольника, так что текст pac
полаrается справа от указанной точки. Это выравнивание по умолчанию
Точка находится справа от оrраничивающеrо текст прямоуrольника, так что текст
располаrаетсяслеваотуказаннойточки
Точка находится в центре оrраничивающеrо текст прямоуrольника
ТА RIGHT
ТА CENTER
Вы определяете координату х имени файла на странице как половину ее ширины, а
координату у как 20 единиц, что составляет 0,2 дюйма от вершины страницы.
После вывода имени файла документа как центрированноrо текста выведите номер
страни;цы внизу страницы. Чтобы отформатировать номер страницы, хранящийся в
переменнойчлене m nCurpage объекта класса CPrintlnfo, используется функция..член
Format () класса CString. Он располаrается в 20 единицах от основания страницы.
Затем выравнивание текста возвращается к стандартному значению TA LEFT.
Функция SetTextAlign () также позволяет изменить позицию текста по вертикали,
комбинируя с помощью оператора ИЛИ второй флаr с флаrом выравнивания. Второй
флаr может быть любым из приведенных в табл. 19.6.
Таблица 19.6. Выравнивание текста по вертикали
Константа Выравнивание
ТА ТОР Выровнять вершину прямоуrольника, оrраничивающеrо текст, по точке, определяю
щей положение текста. Установлено по умолчанию
ТА ВаТТОМ Выровнять низ прямоуrольника, оrраничивающеrо текст, по точке, определяющей
положение текста
ТА BASELINE Выровнять базовую линию шрифта, используемоrо для текста по точке, определяю
щей позицию текста
Следующее действие в функции OnPrint () использует метод, который обсуждался
ранее, для отображения области документа на текущую страницу. Вы получаете дoкy
мент, нарисованный на странице, вызывая фУНКЦИIQ OnDraw () , которая применяется
для отображения документа в представлении. Это потенциально нарисует весь ДOKY
мент, но вы можете оrраничить то, что появляется на странице, определив отсекающий
nрям,ОУZОЛЪ1tи'К (сliр rectangle). Вывод вне это!'о прямоyrольника подавляется. Также для
этоrо можно определить и области неправильной формы.
Начальная область отсечения по умолчанию определена в контексте устройства пе
чати по rраницам страницы. Вы определяете отсекающий прямоyrольник, соответству"
ющий области, определяемой значениями переменных printWidth и printLength,
1160 Visual с++ 2010. Полный курс
в центре страницы. Это rарантирует, что будет нарисована только эта область, а имя
файла не будет перекрыто.
После рисования текущей страницы функцией OnDraw () вызовите функцию
SetClipRgn () с apryмeHToM NULL для удаления отсекающеrо прямоyrольника. Если это..
ro не сделать, то вывод заrоловка документа будет подавлен на всех страницах, следую
щих за первой, поскольку он лежит вне этоrо прямоуrольника, который останется в
силе на протяжении Bcero процесса печати до тех пор, пока не произойдет следующий
вызов функции IntersectClipRect () .
Последнее действие состоит в еще одном вызове функции SetWindowOrg ( ) , необхо
димом для восстановления начальной точки в ее исходном положении, как было сказа
но ранее в настоящей rлаве.
Получение печатноrо вывода документа
Чтобы получить первый печатный документ приложения Sketcher, нужно только
собрать проект и запустить проrрамму (исправив все возможные опечатки). Выбор пун
кта меню FileqPrint Preview (ФайлqПредварительный просмотр) должен привести к
отображению окна, подобноrо показанному на рис. 19.9.
"]
00.
Sketcer Sket:ch
FHe Edit v.e-w EJeme-nt Реп Window Hetp
JJtW
.) / [] O T
. ъ.х.
..J Sketch1
::1 АЕ'
t.tol Pritlt
.J1
OOSf"
...
\
\
,.
ryVf
1::'
...
'1
.
"'-'
!e 1/ Scafe 1
Ready
САР NUM . .:r
Рис. 19. 9. Окцо предварителъиоzо просмотра печатиоzо докумеита
Итак, совершенно бесплатно вы получаете полную возможность предварительне
ro просмотра печати. Инфраструктура использует ваш код для нормальной операЦИJ
мноrостраничной печати, чтобы создать образы страниц в окне предварительноrо пр<
смотра. То, что вы видите в окне предварительноrо просмотра печати, должно полн(
стью соответствовать тому, что вы получите на печатной странице.
rлава 19. Сохранение и печать документов 1161
Сериализация и печать в версии
CLR nporpaMMbI Sketcher
Серuалuзау,ueU (serialization) называется процесс записи объектов в поток, а дecepu
алuзау,ueU (deserialization), соответственно, обратный процесс, т.е. процесс воссозда..
ния объектов из потока. В инфраструктуре .NET Framework предлаrается несколько
различных способов для сериализации и десериализации объектов классов С++ jCLI.
Серuалuзау,uя XlWL предполаrает преобразование объектов в поток XML, соответствую
щий определенной схеме XML. Объекты также MOryт преобразовываться и в потоки
XML, соответствующие спецификации SOAP (Simple Object Access Protocol простой
протокол доступа 'к об'ОеКта.м), и такой подход называется сepuалuзау,ueU SOAP. PaCCMOTpe
ние спецификаций XML и SOAP выходит за рамки настоящей книrи. Здесь будет по
казан лишь третий и, пожалуй, наиболее простой из предлаrаемых в инфраструктуре
.NET Framework способов сериализации, называемый двОU'Ч1l0Й серuалuзау,ueU (binary
serialization).
Вы также увидите, как выполнять печать эскизов из версии CLR проrраммы
Sketcher. Блаrодаря возможностям уже известноrо средства Form Designer (KOHCTPYК
тор форм) этот процесс будет выrлядеть очень просто.
Двоичная сериализация
Прежде чем yrлубляться в детали сериализации эскиза, сначала вкратце ознакомим
ся с тем, что подразумевает процесс двоичной сериализации объектов классов. ЧтобI
объекты классов моrли подверrаться двоичной сериализации, в первую очередь нужно
сделать классы сериализуемыми. Чтобы сделать ссылочный класс или класс значений
сериализуемым, нужно пометить ero атрибутом Serializable, например, так, как по--
казано ниже.
[Serializable]
public ref class MyClass
{
// Определение класса...
} ;
Конечно, на практике это может потребовать больших усилий. Например, чтобы
сериализация работала в случае приведенноrо класса MyClass, необходимо, чтобы все
поля этоrо класса тоже были сериализуемыми. Но очень часто встречаются поля, KOTO
рые не являются сериализуемыми по умолчанию, или поля, сериализировать которые
не имеет смысла, поскольку сохраняемые в них данные после их десериализации боль
ше не будут действительными. Кроме Toro, существуют поля, которые просто нельзя се..
риализировать из соображений безопасности. В таких ситуациях требуется принимать
специальные меры для обработки всех не сериализуемых членов класса.
Обработка полей, которые не должны подверrаться сериализации
При наличии в классе членов, которые не должны подверrаться сериализации,
можно помечать их атрибутом NonSerialized для предотвращения прохождения ими
этоrо процесса.
[Serializable]
public ref class MyClass
{
1162 Visual С++ 2010. Полный курс
public:
[NonSerialized]
int value;
//Остальная часть определения класса...
} ;
Здесь процесс сериализаци не будет пытаться сериализовать член; value класса
MyClass. Чтобы этот код можно было скомпилировать, необходимо добавить объявле-
ние using для пространства имен System: : Runtime: : Serialization.
Хотя добавление к переменной"члену атрибута NonSerialized исключает вероя
ность возникновения во время сериализации проблем с теми типами данных, которые
нельзя сериализировать, он не решает проблемы сериализации объектов, если только
несериализуемые данные не MOryT без проблем пропускаться при достижении этапа
воссоздания объекта во время ero десериализации. В отношении полей с атрибутом
NonSerialized обычно будут предприниматься какие--то специальные действия.
Атрибут OnSerializing позволяет помечать ту функцию класса, которая должна
вызываться вначале процесса сериализации объекта, и, следовательно, предоставляет
возможность делать что--то с теми полями, которые не нужно подверrать сериализации.
Соответствующий пример приведен ниже.
[Serializable]
public ref class мyclass
{
public:
[NonSerialized]
int value;
[OnSerializinq]
void FixNonSerializedData(StreamingContext context)
{
// Код для выполнеfmя необходимых действий...
}
// Остальная часть определения класса...
} ;
При сериализации объекта MyClass функция FixNonSerializedData () будет вызва..
на прежде, чем объект сериализируется, и код столкнется с проблемой не сериализи
pyeMoro объекта anObj ect, возможно, при разрешении сериализации некоторых дpy
rих данных, что позволит восстановить объект anObj ect при десериализации объекта
MyClass. Функция PostSerialization () будет вызвана после Toro, как сериализирует
ся объект MyClass. Фукция, отмеченная атрибутом OnSerializing или OnSerialized,
должна иметь тип возвращаемоrо значения void и параметр типа StreamingContext.
Передаваемый функции объект StreamingContext является структурой, содержащей
информацию об источнике и назначении, но в версии CLR проrраммы Sketcher ero
использовать не придется.
Еще можно сделать так, чтобы какая"нибудь функциячлен вызывалась и при десериа..
лизации объекта. Атрибуты OnDeserializing и OnDeserialized идентифицируют функ
ции, которые должны быть вызваны до или после десериализации соответственно.
[Serializable]
public ref .class MyClass
{
rлава 19. Сохранение и печать документов 1163
public:
[NonSerialized]
ProblemType anObject;
[OnSerializing]
void FixNonSerializedData(StreamingContext context)
{
// Код действий для объекта anObject перед сериализацией...
[OnSerialized]
void PostSerialization(StreamingContext context)
{
// Код действий после сериализации...
[OnDeserializinq]
void PreSerialization(StreaminqContext context)
{
// Код действий перед десериализацией...
}
[OnDeserialized]
void ReconstructObject(StreaminqContext context)
{
// Код восстановления объекта anObject ...
}
// Остальная часть определения класса...
} ;
Функция PreSerialization () будет вызвана непосредственно перед началом десе"
риализации объекта MyClass. Функция ReconstructObj ect () начнет выполняться по
сле Toro, как процесс десериализации объекта закончится, таким образом, эта функция
несет ответственность за корректную YCTaHoBky объекта anObj ect. Функция, помечае..
мая атрибутом OnSerialized, тоже обязательно должна иметь возвращаемый тип void
и параметр типа StreamingContext.
В общем, получается, что для подrотовки объекта класса к сериализации, потребу"
ется выполнять следующие действия.
1. Пометить подлежащий сериализации класс атрибутом Serializable.
2. Определить все переменныечлены, которые нельзя или не следует подверrать
сериализации, и пометить их атрибутом NonSerialized.
3. Добавить открытую функцию с возвращаемым типом void и однимединственным
параметром типа StreamingContext для обработки не сериализуемых полей при
сериализации объекта и пометить ее атрибутом OnSerializing.
4. Добавить открытую (public) функцию с возвращаемым типом void и одним
единственным параметром типа StreamingContext для обработки Hece
риализуемых полей при десериализации объекта и пометить ее атрибутом
OnSerialized.
5. Добавить объявление using для пространства имен System: : Runtime: : Seriali
zation в файл заrоловка, в котором содержится класс.
1164 Visual с++ 2010. Полный курс
Сериализацияобъекта
Сериализация объекта означает ero запись в поток, поэтому первым делом, конеч..
но, нужно определять поток, который должен выступать в роли пункта назначения
для данных, определяющих объект. Каждый поток представляется с помощью класса
System: : 10: : Stream, который относится к типу абстрактных ссылочных классов. По
током может быть любой источник или пункт назначения для данных, имеющих вид
последовательности байтов, например, файл, сокет ТСР /IP или канал, позволяющий
данным передаваться между двумя процессами.
Чаще Bcero предпочтение отдается сериализации объектов в файл. Все статиче
ские функции для создания объектов, инкапсулирующих файлы, содержатся в классе
System: : 10: : File. Для создания HOBoro файла или открытия существующеrо файла
для чтения и/или записи применяется функция File: : Open () . Эта функция возвраща
ет ссылку типа FilеStrеаm Л , указывающую на объект, который инкапсулирует файл.
Поскольку тип FileStream является производным от типа Stream, возвращаемая функ
цией Open () ссылка может сохраняться в переменной типа Strеаm Л . Функция Open ()
имеет три переrруженные версии; та версия, которую будете использовать вы, имеет
следующий вид.
FilеStrеаm Л Open( Striпg Л path, FileMode mode)
Параметр path позволяет указывать путь к файлу, который требуется открыть, и MO
жет содержать как полный путь, так и только имя файла. В случае указания только име
ни файла предполаrается, что файл находится в текущем каталоrе.
Параметр mode позвuляет указывать, должен ли создаваться новый файл, если yкa
занноrо файла еlце нет, и MorYT ли данные перезаписываться, если указанный файл
существует. Он может принимать любое из значений, доступных в перечислении
FileMode. Все эти значения приведены в табл. 19.7.
Таблица 19.7. Значения, доступные в перечислении FileМode
Значение Описание
CreateNew Создать новый файл, путь к которому задан параметром path. Если такой файл
уже существует, создается исключение System: : 10: : 10Exception. Применяется
при записи HOBoro файла
Truncate Открыть файл, путь к которому задан параметром path. Ero содержимое удаляет..
ся в результате усечения размера файла до нуля байтов. Применяется при записи
существующеrо файла
Crea te Указывает, что в случае отсутствия указанноrо файла должен быть создан новый
файл, а в случае ero существования файл должен быть перезаписан. Применяется
при записи файла
Open Открыть файл, путь к которому задан параметром ра th. Если TaKoro файла нет,
передается исключение типа System: : 10: : FileNotFoundException. Применя..
ется при чтении файла
OpenOrCreate Открыть файл, путь к которому задан параметром path, если таковой существует,
и создать новый, если нет. Может применяться как при чтении, так и при записи
файла, в заВИСИМ9СТИ от Toro, какое значение указывается в aprYMeHTe access
Append Открыть файл, путь к которому задан параметром ра th, если таковой существует,
а затем переместить ero позицию записи в самый конец, или же создать новый
файл, если нйти файл с таким именем не удается. Применяется либо для добав"
ления данных в уже существующий файл, либо для записи HOBoro файла
rлава 19. Сохранение и печать документов 1165
Таким образом, получается, что создать приrодный для записи поток, инкапсули
рующий файл в текущем каталоrе, можно с помощью оператора
Strеаm Л stream == File::Open(L"sketch.dat", FileMode::Create);
Соrласно этому оператору, в случае, если файла sketch. dat не существует, он будет
создаваться в текущем каталоrе, а в случае, если он уже существует, ero содержимое
будет перезаписываться.
Статическая функция OpenWri te () в классе File будет открывать существующий
файл, указанный в строковом apryMeHTe с доступом для записи, и возвращать ссылку
FilеStrеаm Л на используемый поток для записи файла.
Для сериализации объекта в файл, который был инкапсулирован в созданном объек
те FileStream, при меняется объект типа System: : Runtime: : Serialization: : Format
ters: :Binary: : BinaryFormatter, который создается следующим образом.
ВiпаrуFоrmаttеr Л formatter == gcnew BinaryFormatter();
Для успешной компиляции этоrо оператора понадобится объявление using для про..
странства имен System: : Runtime: : Serialization: : Formatters: : Binary. У объекта
formatter имеется функциячлен Serialize () , которая служит для сериализации объ
екта в поток. В качестве первоrо apryмeHTa этой функции передается ссылка на поток,
который выступает в роли пункта назначения для данных, а в качестве BToporo ссыл
ка на объект, который подлежит сериализации в этот поток. Таким образом, получает
ся, что записать объект sketch в поток stream можно с помощью оператора
formatter>Serialize(stream, sketch);
Чтение объекта из потока осуществляется вызовом для объекта BinaryFormatter
функции Deserialize () .
Skеtсh Л sketch == safe саst<SkеtсhЛ>(fоrmаttеr>Dеsеriаlizе(strеаm));
Функция Deserialize () в качестве apryмeHTa получает ссылку на читаемый поток и
возвращает прочитанный объект как тип Obj ect Л, который должен быть обязательно
приведен к соответствующему типу.
Сериализация класса Sketch
Чтобы обеспечить сериализацию эскизов в версии CLR приложения Sketcher, не..
обходимо превратить класс Sketch в сериализуемый класс и добавить код, позволяю
щий пунктам меню File и кнопкам, панели инструментов поддерживать сохранение и
извлечение эскизов.
Превращение класса Sketch в сериализуемый класс
Чтобы указать возможность сериализации класса Sketch, добавим перед ero опреде"
лением атрибут Serializable.
[Serializable]
public ref class Sketch
{
// Определение класса такое же, как и раньше...
} ;
Но одноrо лишь указания, что класс является сериализуемым, недостаточно. Про
цесс сериализации все равно Не будет проходить изза Toro, что классы контейнеров
1166 Visual С++ 2010. Полный курс
STL/CLR не являются сериализуемыми по умолчанию. Поэтому далее потребуется ука"
зать, что член класса elements не является сериализуемым, как показано ниже.
[Serializable]
public ref class Sketch
{
private:
[NonSerialized]
list<ЕlеmепtЛ>Л elements;
// Остальная часть определения класса такая же, как и раньше...
.
Теперь класс Sketch действительно станет сериализуемым, но не особенно полез..
ным, поскольку ни один из элементов эскиза не будет записываться в файл. Чтобы
исправить это, нужно предоставить для элементов контейнера list<Еlеmеnt Л > аль
тернативный сериализуемый депозитарий, позволяющий записывать элементы эски"
за в файл. К счастью, обычный массив С++ /CLI является сериализуемым, и, что еще
приятнее, у контейнера списка имеется функция to array () , которая возвращает все
содержимое контейнера в виде массива. Это означает, что в класс Sketch можно до"
бавит открытую функцию с атрибутом OnSerializing, которая будет копировать co
держи мое контейнера элементов в массив, и сделать так, чтобы она вызывалась перед
началом процесса сериализации. Кроме Toro, в Hero можно еще добавить открытую
функцию с атрибутом OnSerialized, которая будет воссоздавать контейнер elements
при десериализации массива, содержащеrо элементы. Ниже приведены все изменения,
которые потребуется внести в класс Sketch.
[Serializable]
public ref class Sketch
{
private:
[NonSerialized]
list<ЕlеmепtЛ>Л elements;
аrrау<ЕlemеntЛ>Л elementArray;
public:
Sketch(): elementArray(nullptr)
{
elements gcnew list<Еlеmепt Л >();
[OnSerializinq]
void ListToArray(StreaminqContext context)
{
elementArray = elements>toarray();
}
[OnDeserialized]
void ArrayТoList(StreaminqContext context)
{
elements = qcnew list<ЕlemеntЛ>(еlemеntArrау) .
elementArray = nullptr;
}
// Остальная часть определения класса такая же, как и раньше...
} ;
rлава 19. Сохранение и печать документов 1167
Теперь имеется новая закрытая переменнаячлен elementArray, которая будет со..
держать все элементы эскиза, коrда тот будет сериализироваться в файл. Она инициа
лизируется в конструкторе значением nullptr. При сериализации объекта сначала
будет вызываться функция ListToArray () , которая перед началом непосредственно ca
Moro процесса сериализации объекта будет передавать содержимое контейнера списка
в массив elementArray. Далее объект Sketch, содержащий объект elementArray, будет
записываться в файл. При обратном считывании эскиза из файла будет осуществляться
воссоздание объекта Sketch вместе с содержащимся в нем членом elementArray, сразу
после чеrо будет вызваться функция ArrayToList () для восстановления содержимоrо
caMoro контейнера elements из массива. Больше этот массив будет не нужен, поэтому в
функции для Hero устанавливается значение nullptr.
Пока что все вроде бы достаточно хорошо, но всетаки это еще не совсем конец.
Для Toro чтобы эскиз был сериализуемым, все элементы в эскизе тоже должны быть се--
риализуемыми, а с классом Curve связана одна небольшая проблема, посколы\.' одним
из ero членов является не сериализуемый контейнер STLjCLR. Объекты Реп и Brush
не сериализируемы, поэтому предстоит сделать коечто еще, прежде чем эскиз будет
сериализироваться.
Первым делом все равно нужно добавить атрибут Serializable к классу Element
и всем ero подклассам. Переменнуючлен реп класса Element также можно отметить
атрибутом NonSerialized.
Решение проблемы сериализации объектов Реп и Brush
Вы не сможете сериализировать и десериализировать объекты Реп и Brush, но если
вы будете сериализировать атрибуты пера или кисти, то сможете восстановить их. Добавь..
те некоторые члены к базовому классу Element, чтобы хранить необходимые атрибуты.
[Serializable]
public ref class Element abstract
{
protected:
Point position;
Color color;
System::Drawing::Rectangle boundRect;
Color highlightColor;
float penWidth;
DashStyle dashStyle;
[NonSerialized]
РепЛ реп;
// Остальные классы, как прежде...
} ;
Все добавления, необходимые для сериализации, выделены полужирным шрифтом.
Здесь, две дополнительные защищенные переменные"члены хранят значения ширины
пера и свойства DashStyle элемента.
Объект Реп не используется классом TextElement, поэтому в классах, которые
определяют rеометрические формы, вы должны восстановить только член реп класса
Element. Добавьте следующие открытые функции в классы Line, Rectangle и Circle.
[OnSerializing]
void SavePenAttributes(StreamingContext context)
1168 Visual с++ 2010. Полный курс
penWidth pen>Width;
dashStyle pen>DashStyle;
[OnDeserialized]
void CreatePen(StreamingContext context)
{
реп gcnew Pen(color, penwidth);
pen>DashStyle dashStyle;
Функция SavePenAttributes () будет вызвана непосредственно перед сериализаци
ей объекта, чтобы заполнить переменныечлены penWidth и dashStyle, rOTOBbIe для
сериализации. Функция CreatePen () будет вызвана после десериализации объекта и
воссоздаст объект реп из атрибутов, сохраненных процессом сериализации.
В классе TextElement вы должны отметить переменнуючлен brush атрибутом
NonSerialized. Затем необходимо предусмотреть восстановление этоrо объекта по
сле десериализации объекта TextElement.
В определение этоrо класса внесены следующие изменения.
[Serializable]
public ref class TextElement : Element
{
protected:
Striпg Л text; Fопt Л font;
[NonSerialized]
SоlidВrush Л brush;
public:
[OnDeserialized]
void CreateBrush(StreamingContext context)
{
brush = qcnew SolidВrush(color);
}
// Остальные классы, как прежде...
} ;
Функция Crea teBrush () используется для восстановления переменной..члена
brush после десериализации объекта. При восстановлении объекта SolidBrush ника
кая дополнительная информация не обязательна, поскольку он должен иметь только
переменнуючлен color, который сериализируется в любом случае.
Сериализация класса Curve
Класс Curve должен позаботиться об унаследованной переменнойчлене реп и о BeK
торном контейнере, который не сериализируем. Вы можете применить к контейнеру в
классе Curve тот же трюк, что и к контейнеру в классе Sketch; это потребует внесения
следующих изменений в определение класса.
[Serializable]
public ref class Curve : Element
{
private:
rлава 19. Сохранение и печать документов 1169
[NonSerialized]
vесtоr<Роiпt>Л points;
аrrау<Роint>Л pointsArray;
public:
Curve(Color color, Point рl, Point р2, float penWidth) :
pointsArray(nullptr)
// Код конструктора, как прежде...
[OnSerializinq]
void SavePenAndVectorData(StreaminqContext context)
{
penWidth = pen>Width;
dashStyle = pen>DashStyle;
pointsArray = points>toarray();
}
[OnDeserialized]
void RestorePenAndVectorData(StreaminqContext context)
{
реп = qcnew Pen(color, penWidth);
pen>DashStyle = dashStyle;
points = qcnew vector<Point>(pointsArray);
pointsArray = nullptr;
}
// Остальная часть определения класса такая же, как и раньше...
} ;
Работа этоI;'о кода вполне очевидна. Переменнаячлен points теперь не сериализуе
ма, но у вас есть новая переменнаячлен pointsArray, которая будет хранить точки в
сериализуемый форме.
Функция SavePenAndVectorDa ta ( ) , которая вызывается непосредственно перед ce
риализацией объекта, устанавливает значения переменныхчленов penWidth, dashStyle
и pointsArray, rOTOBbIX для сериализации. Функция RestorePenAndVectorData () BOC
станавливает перо и векторный контейнер после десериализации объекта Curve. Конеч
но же, нужно помнить о необходимости добавления как в Sketch. h, так и в Elements . h
объявления using для пространства имен System: : Runtime: : Serialization.
Реализация файловых операций
Все необходимые для выполнения файловых операций пункты меню и кнопки панели
инструментов уже присyrствуют в версии CLR приложения Sketcher. Двойной щелчок на
свойстве события Click в окне свойств пункта меню FileSave, FileSave As и FileOpen
позволит добавить для всех них необходимые обработчики событий. Далее нужно бу
дет выбрать подходящие обработчики событий из раскрывающеrося списка доступных
для события Click значений для каждой из кнопок панели инструментов. После этоrо
останется лишь добавить код для выполнения этими обработчиками нужных действий.
Создание диалоrовых окон для файловых операций
В окне Toolbox (Пнель инструментов) имеются стандартные диалоrовые окна для
открытия и сохранения файла, которыми и следует воспользоваться Перетащите из
окна Toolbox элементы OpenFileDialog и SaveFileDialog в окно конструктора формы
1170 Visual С++ 2010. Полный курс
Forml. По желанию можете изменить значение в их свойствах пате на saveFileDialog
и openFileDialog соответственно. Далее измените значения в их свойствах Ti tle на
то, что должно отображаться в строке заrоловка. Можете также изменить для диало
rOBoro окна openFileDialog значение свойства FileName на Sketch: это свойство от..
вечает за имя файла, которое будет отображаться по умолчанию при первом исполь
зовании диалоrовоrо окна. Еще полезно определить папку для хранения всех эскизов,
поэтому создайте такую папку сейчас (С: \CLR Sketches вполне подойдет). Можно
также указать для обоих диалоrовых окон в свойстве Ini tialDirectory каталоr по
умолчанию. У обоихдиалоrовых окон имеется свойство Filter, позволяющее задавать
фильтры для отображаемоrо в этих окнах списка файлов. Можете установить значение
этоrо свойства как "CLR Sketches I * . ske I All files I * . *". Устанавливаемое по умол..
чанию для свойства FilterIndex значение 2. Удостоверьтесь в том, что для свойств
Valida teNames и Overwr i tePrompt диалоrовоrо окна saveFi leDialog установлено ис
пользуемое по умолчанию значение true, чтобы при попытке перезаписать существую
щий эскиз пользователь всеrда получал соответствующий запрос.
Запись состояния сохранения эскиза
Прежде чем переходить непосредственно к самому процессу реализации обработ"
чика событий для пункта меню FileSave, рассмотрим, как в общем будет выrлядеть
лоrика. То, что должно происходить после щелчка на пункте меню FileSave, зависит
Toro, сохранялся ли текущий эскиз ранее.
Если эскиз никоrда не сохранялся, нужно, чтобы отображалось диалоrовое окно со..
хранения файла, а если он уже сохранялся, чтобы эскиз просто сохранялся в файл без
отображения TaKoro окна. Чтобы добиться TaKoro эффекта, необходимо найти какой..
то способ фиксации информации о том, сохранялся эскиз или нет. Один из возмож"
ных вариантов добавить в класс Sketch открытый член типа bool по имени saved.
Можно сделать так, чтобы в конструкторе класса Sketch для Hero в качестве исходноrо
значения устанавливалось значение false, а при первом сохранении эскиза значение
true. Обработчик Forml MouseUp () добавляет в эскиз новые элементы, поэтому ero
следует изменить.
private: System::Void Forml МоusеUр(Sуstеm::ОЬjесt Л sender,
Sуstеm::Wiпdоws::Fоrms::МоusеЕvепtАrgs Л е)
if ( ! drawing)
{
mode Mode::Normal;
return;
if(tempElement)
{
sketch + tempElement;
sketch>saved = false; // Эскиз изменен, снять метку сохранения
tempElement nullptr;
Invalidate () ;
}
drawing false;
Обработчик Forml MouseDown () добавляет в эскиз объект TextElement, поэтому
переменная saved должна быть установлена в значение false.
rлава 19. Сохранение и печать документов 1171
private: System::Void FоrmlМоusеDоwп(Sуstеm::ОЬjесtЛ sender,
Sуstеm::Wiпdоws::Fоrms::МоusеЕvепtАrgs Л е)
{
if(e>Button System::Windows::Forms::MouseButtons::Left)
{
firstPoint e>Location;
if(mode Mode::Normal)
{
if(elementType ElementType::TEXT)
{
// Сбросить строку TeKcToBoro поля
textDialog>TextString L"";
// Установить шрифт для поля редактирования
textDialog>TextFont textFont;
if(textDialog>ShowDialog()
System::Windows::Forms::DialogResult::OK)
{
tempElement gcnew TextElement(color, firstpoint,
textDialog>TextString, textFont);
sketch + tempElement;
// Эскиз изменен, снять метку сохранения
sketch>saved false;
// Область TeKCToBoro элемента
Invalidate(tempElement>bound);
tempElement nullptr;
Upda te () ;
}
drawing false;
}
else
{
drawing true;
Функция Forml MouseMove () изменяет эскиз, поэтому здесь также необходимо уста..
новить переменную saved в состояние false.
private: System::Void FоrmlМоusеМоvе(Sуstеm::ОЬjесtЛ sender,
Sуstеm::Wiпdоws::Fоrms::МоusеЕvепtАrgs Л е)
{
if(drawing)
{
// Код, как прежде...
}
else if(mode Mode::Normal)
{
// Код, как прежде...
}
else if(mode Mode::Move &&
e>Button System::Windows::Forms::MouseButtons::Left)
{ // Переместить выделенньм элемент
if(highlightedElement)
1172 Visual С++ 2010. Полный курс
{
sketch>saved = false; // Пометить эскиз как не сохраненный
// Область до перемещения
Invalidate(highlightedElernent>bound);
highlightedElernent>Move(e>X firstPoint.X,
e>y firstPoint.y);
firstPoint e>Location;
// Область после перемещения
Invalidate(highlightedElernent>bound);
Upda te () ;
Обработчик пункта Delete KOHTeKcTHoro меню изменяет эскиз, поэтому ero необхо
димо изменить.
private: Systern::Void dеlеtеСопtехtМепuItеrnСliСk(Sуstеrn::ОЬjесtЛ sender
Sуstеrn::ЕvепtАrgs Л е)
{
if(highlightedElernent)
{
sketch>saved = false; // Пометить эскиз как не сохраненный
sketch highlightedElernent; // Удалить выделенньм элемент
Invalidate(highlightedElernent>bound);
highlightedElernent 4 nullptr;
Upda te () ;
-
и наконец, должен быть изменен обработчик sendToBack ( ) .
private: Systern::Void sendToBackContextMenuIternClick(
Sуstеrn::ОЬjесt Л sender, Sуstеrn::ЕvепtАrgs Л е)
{
if(highlightedElernent)
{
sketch>saved = false; // Пометить эскиз как не сохраненный
// Остальной код, как прежде...
Таково состояние сохранения эскиза. Теперь можно реализовать сам процесс coxpa
нения.
Сохранение эскиза
Для сохранения эскиза необходим объект BinaryFormatter, наилучшим местом для
размещения KOToporo является, конечно же, класс Forml. Добавьте объявление using
для пространства имен System: : Runtime: : Serialization: : Formatters: : Binary
в файл Forml. h и новый закрытый член formatter типа ВiпаrуFоrmаttеr Л в класс
Forml. В списке инициализации конструктора класса Forml инициализируйте ero в опе
раторе gcnew BinaryFormatter () .
Прежде чем реализовать обработчик событий Click, добавьте в файл заrоловка
Forml. h объявление using для пространства имен System. 10. Затем добавьте в класс
rлава 19. Сохранение и печать документов 1173
Forml закрытый член sketchFilepath типа Striпg Л для сохранения пути к файлу эски
за и в конструкторе Forml установите для Hero в качестве исходноrо значения nullptr.
Далее, чтобы реализовать обработчик событий Click для операций сохранения, дo
бавьте следующий код.
private: Systern::Void saveToolStripMenuIternClick{
Sуstеrn::ОЬ]есt Л sender, Sуstеrn::ЕvепtАrgs Л е)
SaveSketch();
Здесь просто вызывается вспомоrательная функция, которую вы вскоре добавите.
Вот как можно реализовать обработчик для пункта меню Save As... (Сохранения
как...), используя друryю вспомоrательную функцию.
private: Systern::Void saveAsToolStripMenuIternClick{
Sуstеrn::ОЬjесt Л sender, Sуstеrn::ЕvепtАrgs Л е)
SaveSketchAs();
.
Причина использования вспомоrательных функций в том, что обработчики пунктов
меню Save и Save As... MOryт использовать одинаковые средства.
Добавьте функцию SaveSketch () как закрытый член класса Forml и реализуйте ero
так, как показано ниже.
void SaveSketch{void)
{
if(sketch>saved)
return; // Ничеrо не делать, поскольку эскиз не был изменен
if(sketchFilePath == nullptr)
{
// Файл еще не сохранялся, отобразить диалоrовое окно сохранения
SaveSketchAs();
,
}
else
{
// Файл уже сохранялся, сохранить ero с текущим именем
Strеam Л stream = File::OpenWrite(sketchFilePath);
formatter>Serialize(stream, sketch);
stream>Close();
}
Есть два направления действий, в зависимости от Toro, был ли эскиз сохранен pa
нее. Если эскиз не изменялся, ничеrо делать не нужно, поэтому функция завершается.
Если переменная saved указывает, что эскиз должен быть сохранен, то есть две даль
нейшие возможности.
....
1. Если переменная sketchFilePath содержит значение nullptr, значит, эскиз
ранее не сохранялся. В таком случае вызовите функцию SaveSketchAs ( ) , чтобы
использовать диалоrовое окно сохранения для сохранения эскиза.
2. Если эскиз был сохранен прежде, переменная sketchFilePath содержит дo
пустимый путь. В данном случае сохраните файл, используя ero существующее
полное имя.
1174 Visual С++ 2010. Полный курс
Чтобы сохранить эскиз, коrда он был сохранен ранее, вы получаете ссылку на
объект FileStream, который можно использовать для сериализации эскиза при вызове
статической функции OpenWri te () , которая опрееляется в классе File. ApryMeHToM
функции OpenWri te () является полное имя файла эскиза. Затем вы сериализуете эскиз,
вызвав функцию Serialize () для объекта BinaryFormatter. И наконец, вызываете
для потока функцию Close () , чтобы закрыть поток и освободить ресурсы.
Добавьте в класс Forml закрытую функцию SaveSketchAs ( ) , реализованную так,
как показано ниже.
// Сохранить текУЩИЙ эскиз
void SaveSketchAs(void)
{
if(saveFileDialog>ShowDialog()
System::Windows::Forms::DialogResult::OK)
{
Strеam Л stream = File::Open(saveFileDialog>FileName,
FileNode::Create);
if(stream != nullptr)
{
formatter>Serialize(stream, sketch);
stream>Close();
sketchFilePath = saveFileDialog>FileName;
sketch>saved = true;
}
else
{
MessageBox::Show(L"Failed to create sketch file stream!");
}
}
Откройте диалоrовое окно сохранения файла, вызвав ero функцию ShowDialog ()
в условии оператора if. Если диалоrовое окно закрывает кнопка ОК, значит, пользова
тель хочет закончить операцию сохранения. Поэтому вызовите статическую функцию
Open () , чтобы создать для файла объект FileStream, используя имя файла, предостав..
ленное свойством FileName объекту диалоrовоrо окна. Сериализируйте эскиз в файл
при вызове функции Serialize () для объекта BinaryFormatter и закройте поток. Co
храните полное имя файла, полученное в диалоrовом окне, для последующеrо исполь..
зования и установите для переменнойчлена saved объекта Sketch значение true.
Если вызов функции Open () класса File будет неудачным, в переменной stream co
храняется значение nullptr. В данном случае для отображения сообщения использует
ся класс System: : Windows: : Forms : : MessageBox. Функция Show () класса MessageBox
позволяет отобразить широкий диапазон окон сообщений. Далее в rлаве вы увидите
еще больше возможностей.
Извлечение эскиза из файла
Восстановление эскиза из файла HeMHoro сложней, поскольку необходимо пред"
усмотреть возможность перезаписи текущеrо эскиза. Если текущий эскиз не был
сохранен, вы должны предоставить пользователю возможность сохранить ero пре..
жде, чем читать новый эскиз из файла. Обработчик событий Click пункта меню
FileOpen имеет дело со считыванием эскиза из файла. Ero можно реализовать сле..
дующим образом.
rлава 19. Сохранение и печать документов 1175
private: Systern::Void openToolStripMenulternClick(
Sуstеrn::ОЬjесt Л sender, Sуstеrn::ЕvеntАrgs Л е)
Strеam Л stream;
if(!sketch>saved)
{
String Л message =
L"The current sketch is not saved.\nSave the current sketch?";
String Л caption = L"Sketch Not Saved";
MessageBoxВuttons buttons = MessageBoxВuttons::YesNo;
// Отобразить окно сообщений, чтобы предупредить о
// несохраненнон эскизе
if (MessageBox::Show(this, message, caption, buttons)
System::Windows::Forms::DialogResult::Yes)
{
SaveSketch();
}
}
// Теперь открыть новый эскиз
if(openFileDialog>ShowDialog()
System::Windows::Forms::DialogResult::OK)
{
stream = openFileDialog>OpenFile();
if(stream != nullptr)
{
sketch = sаfесаst<SkеtСhЛ>(fоrmattеr>Dеsеriаlizе(strеam»;
stream>Close();
sketch>saved = true;
sketchFilePath = openFileDialog>FileName;
Invalidate();
}
}
,
Если переменнаячлен saved текущеrо объекта Sketch содержит значение false,
то эскиз не был сохранен. Отобразите окно сообщения, предлаrающее пользователю
сохранить текущий эскиз. Эта версия функции Show () в классе MessageBox получает
четыре apryмeHTa: ссылку на текущее окно, отображаемое сообщение, заrоловок окна
сообщений и значение MessageBoxButtons, определяющее кнопки, которые будут
включены в окно сообщений. Перечисление MessageBoxButtons включает такие чле..
ны: ОК, OKCancel, AbortRetryIgnore, YesNoCancel, YesNo и RetryCancel. Если зна..
чение, возвращенное функцией Show () класса MessageBox, соответствует щелчку на
кнопке Yes, вызовите функцию SaveSketch ( ) , чтобы сохранить эскиз.
Здесь класс OpenFileDialog предоставляет функцию OpenFile () , которая возвра"
щает ссылку на поток, инкапсулирующий выбранный в диалоrовом окне файл. Далее
происходит десериализация эскиза из файла с помощью вызова для объекта f orma t ter
функции Deserialize () , после чеrо поток закрывается. Очевидно, что эскиз остается
в файле, поэтому член Saved класса Sketch устанавливается в состояние true. Coдep
жащееся в переменной sketchFilepath имя файла сохраняется для применения в по
следующих операциях сохранения, а уже после этоrо вызывается метод Invalidate ()
для выполнения повторной прорисовки формы И отображения в ней заrруженноrо
только что эскиза.
1176 Visual с++ 2010. Полный курс
Реализация операции Fi/e New
Пункт меню FileNew должен создать новый пустой эскиз, но только после провер
ки сохранения текущеrо эскиза. Добавьте для пункта меню обработчик события Click
и реализуйте ero так, как показано ниже.
private: System::Void newToolStripMenultemClick(
Sуstеm::ОЬjесt Л sender, Sуstеm::ЕvепtАrgs Л е)
{
SaveSketchCheck();
sketch = 9cnew Sketch();
sketchFilePath = nullptr;
Invalidate();
Update();
Реализация очень проста. Чтобы проверить, нужно ли сохранить текущий эскиз и
позволить пользователю сделать это, вызовите функцию SaveSketchCheck ( ) . Затем
создайте новый объект Sketch, обновите переменнуючлен sketchFilePath и перери
суйте клиентскую область, вызвав функции Invalidate () и Update () .
Добавьте функцию SaveSketchCheck () как закрытый член класса Forml и реализуй
те ее так, как показано ниже.
void SaveSketchCheck()
{
if(!sketch>saved)
{
Strin9Л message =
L"The current sketch is not saved.\nSave the current sketch?";
Strin9Л caption = L"Sketch Not Saved";
MessageBoxВuttons buttons = MessageBoxВuttons::YesNo;
// Отобразить окно сообщений, чтобы предупредить о
// несохраненном эскизе
if ( MessageBox:: Show (this, message, caption, buttons)
System::Windows::Forms::Dialo9Result::Yes)
.
{
SaveSketch();
}
}
Закрытие формы
В настоящее время можете закрыть приложение, щелкнув на пиктоrрамме закрытия
в верхнем правом уrлу формы. Приложение будет закрыто, а текущий эскиз, если он
есть, будет удален. Необходима возможность сохранения эскиза прежде, чем приложе
ние закроется. Добавьте ДЛЯ формы обработчик события FormClosing, отобразив окно
свойств формы и дважды щелкнув на событии FormClosing. Реализуйте этот обработ"
чик событий так, как показано ниже.
private: System::Void FоrmlFоrmСlоsiпg(Sуstеm::ОЬjесtЛ sender,
Sуstеm::Wiпdоws::Fоrms::FоrmСlОSlпgЕvепtАrgs Л е)
SaveSketchCheck();
rлава 19. Сохранение и печать документов 1177
Вы просто вызываете функцию SaveSketchCheck () , чтобы проверить эскиз и со..
хранить ro при необходимости. Вот и все.
По.цдержка кнопок панели
Кнопки панели для сохранения, открытия файлов и создания HOBoro эскиза в HaCTO
ящее время ничеrо не делают. Но сделать их рабочими несложно. Все, что вы должны
сделать, выбрать обработчик события Click для пункта меню File, соответствующеrо
кнопке, и указать ero как обработчик события кнопки.
Теперь ваша версия CLR приложения Sketcher способна сохранять и восстанавли
вать эскизы.
Печать эскиза
Что касается печати эскиза, то тут сразу налицо преимущества, поскольку в окне
Toolbox доступно пять компонентов, поддерживающих операции печати, в том числе
и диалоrовые окна для настройки параметров страницы, печати и предварительноrо
просмотра. Для Toro чтобы иметь возможность напечатать эскиз, необходимо создать
экземпляр компонента PrintDocument, реализовать для Hero обработчик событий
printpage и вызвать для объекта PrintDocument функцию Print () , чтобы фактически
распечатать эскиз. Конечно, еще потребуется создать обработчики событий Click для
принимающих участие в этом процесс е пунктов меню и обеспечить отображение еще
нескольких диалоrовых окон, но начнем с компонента PrintDocument.
Использование компонента Prin tDocumen t
Перетащите компонент PrintDocument из окна Toolbox на форму в окне KOHCTPYK
тора, чтобы добавить в класс Forml член PrintDocument. Отобразите окно свойств
объекта PrintDocument и измените значение ero свойства name на printDocument.
Щелкните на кнопке Events, а затем дважды щелкните на событии printpage, что
бы создать для Hero обработчик. У параметра РriпtРаgеЕvепtАrgs Л есть свойство
Graphics, предоставляющее объект Graphics, который можно использовать для про
рисовки rOToBoro к печати эскиза следующим образом.
private: System::Void рriпtDосumепtРriпtРаgе{Sуstеm::ОЬjесtЛ sender,
Sуstеm::Drаwiпg::Рriпtiпg::РriпtРаgеЕvепtАrgs Л е)
{
sketch>Draw(e>Graphics);
Проще и быть не может, не так ли?
Реализация операции Print
Для предоставления пользователю возможности выбирать принтер и запускать
процесс печати необходимо диалоrовое окно печати, поэтому перетащите на форму
из окна Toolbox компонент PrintDialog и измените значение ero свойства name на
printDialog. Чтобы сопоставить с этим диалоrовым окном объект printDocument,
выберите из раскрывающеrося списка в столбце значений для свойства Document зна
чение printDocument. Далее добавьте для пункта меню FilePrint обработчик событий
Click и установите ero также в качестве обработчика для кнопки печати в панели ин
струментов. После этоrо останется лишь добавить в этот обработчик код, отображаю..
щий диалоrовое окно печати, как показано ниже.
1178 Visual с++ 2010. Полный курс
private: System::Void printToolStripMenultemClick(
Sуstеm::ОЬjесt Л sender, Sуstеm::ЕvепtАrgs Л е)
if(printDialog>ShowDialog() ==
System::Windows::Forms::DialogResult::OK)
printDocument>Print();
Здесь сначала отображается диалоrовое окно, а затем, если функция ShowDialog ( )
возвращает значение DialogResul t: : ОК, для объекта printDocument вызывается функ
ция Print (), которая и выполняет печать эскиза. Вот и все! Вы добавили две строки
кода здесь плюс одну в обработчике событий printpage, и получилась rотовая стан..
дартная возможность для выполнения печати эскизов.
Отображение диалоrовоrо окна печати позволяет пользователю выбирать прин
тер и изменять предпочтения для задания на печать перед началом непосредственно
caMoro процесс а печати. Разумеется, отображать такое диалоrовое окно для печати
эскиза вовсе не обязательно. Если необходимо, чтобы печать эскиза выполнялась He
медленно, без отображения диалоrовоrо окна, вызовите функцию Print () для объекта
PrintDocument.
Резюме
В настоящей rлаве вы узнали, как преобразовать сохраняемый на диске документ
в форму, позволяющую считывать ero и воссоздавать составляющие ero объекты с по..
мощью процессов сериализации, которые поддерживаются библиотеками MFC и CLR.
Была также реализована возможность печати эскизов в версиях приложения Sketcher
на базовых языках С++ и С++ jCLI. Если вы уяснили, как работает сериализация и пе
чать в двух версиях приложения Sketcher, у вас не должно быть проблем с реализаци..
ей сериализации и печати в любом приложении MFC или Windows Forms.
Упражнения
Исходные коды упражнений и их решения можно заrрузить с ве6-сайта www.wrox.com.
1. Добавьте в функцию OnPrint () код, делающий так, чтобы внизу каждой страни..
цы документа печатался номер страницы в виде "Page п of т", rде п номер
текущей страницы; т общее количество страниц.
2. В качестве дальнейшеrо усовершенствования класса CText измените ero реализа
цию так, чтобы в нем должным образом работало масштабирование. (ПодС'КаЗ'Ка:
обратитесь к описанию функции CreatePointFont () в оперативной справочной
системе.)
3. Измените версию CLR проrраммы Sketcher так, чтобы пользователь Mor вы..
бирать специальный цвет элементов. (Подс'Каз'Ка: это подразумевает добавление
HOBoro пункта меню и кнопки панели. Помощь по выбору специальноrо цвета
можно найти в справке по панели инструментов Toolbox.)
4. Добавьте в версию CLR приложения Sketcher возможность ввода шаблона сти
ля линии, состоящеrо из двух штрихов, затем двух точек, потом одноrо штриха
и одной точки.
rлава 19. Сохранение и печать документов 1179
Что вы узнали в этой rлаве
о Серuалuза1J,UЯ. Процесс передачи объектов в файл. Десериализация восстанавли
вает объект из данных в файле.
О Серuалuза1J,UЯ мрс. Для сериализации объекта в приложении MFC необходимо
пометить класс как сериализуемый. Для этоrо используют в определении класса
макрокоманду DECLARE SERIALI ZABLE ( ) , а в файле реализации класса MaKpOKO
MaHдyIMPLEMENTSERIALIZABLE().
О СерuалuзуеМЪtе 'КЛассъt в прuложе1lUU мрс. Чтобы класс был сериализуемым в при..
ложении MFC, он должен происходить от класса CObj ect, реализовать KOHCTPYK
тор без параметров и функцию Serialize () как член Класса.
О серuалuза1J,UЯ С++ /CLL Чтобы объявить класс С++ /CLI сериализуемым, ero следу"
ет отметить атрибутом Serialiable.
О серuалuза1J,UЯ 'КЛассов С++ /CLL Чтобы класс при.hожения С++ /CLI был сериализуе
мым, необходимо следующее.
. Все поля, которые не MOryт или не должны сериализироваться, следует OTMe
тить атрибутом NonSerialized.
. Иметь открытую функциючлен, работающую с не сериализуемыми полями
при сериализации объекта, имеющую тип возвращаемоrо значения void, один
параметр типа StreamingContext и атрибут OnSerializing.
. Иметь открытую функциючлен, работающую с не сериализуемыми полями
при десериализации объекта, имеющую тип возвращаемоrо значения void,
один параметр типа StreamingContext и атрибут OnSerializing.
. Иметь в каждом файле заrоловка, содержащем сериализуемые классы, объяв
ление using для пространства имен System: : Runtime: : Serialization.
О Печатъ в МFC. ДЛЯ обеспечения возможности напечатать документ в приложении
MFC необходимо реализовать в классе представления документа собственные
версии функций OnPreparePrinting () , OnBeginPrinting () , OnPrepareDC () ,
OnPrint () и OnEndPrinting () .
о Обоект CPrintInfo. Создается инфраструктурой MFC дЛЯ хранения информации
о процессе печати. Вы можете хранить адрес собственноrо объекта класса, KOTO
рый содержит информацию о печати, в указателе на объект CPrintInfo.
О Печатъ в прuложе1lUU С++ /CLL Чтобы реализовать печать в приложении Windows
Forms, добавьте в форму компонент PrintDocument и реализуйте обработчик co
бытия PrintPage, чтобы напечатать форму.
О Реалuза1J,UЯ полъзоваmелъскоzо U1lrперфеиса печатu в прUЛОЖe1lUU С++ /CLL Чтобы pea
лизовать пользовательский интерфейс печати в приложении Windows Forms, до..
бавьте в форму компонент PrintDialog и отобразите ero в обработчике события
Click для пункта меню и кнопки панели инструментов, инициализирующеrо пе
чать. Если диалоrовое окно печати закрывается с помощью кнопки ОК, вызовите
функцию Print () для объекта PrintDocument, чтобы напечатать фор
Написание собственных
библиотек DLL
в этой rЛАВЕ...
). Библиотеки DLL
). Коrда следует рассматривать возможность реализации библиотеки DLL
). Какие существуют вариации библиотек DLL и для чеrо они используются
). Как можно расширить библиотеку MFC с помощью библиотеки DLL
). Как обращаться к содержимому библиотеки DLL из разрабатываемых
проrрамм
в rлаве 9 упоминалось о том, что библиотеки классов С++ /CLI хранятся в файле
. dll. Дuиа.мu'Ч,ескu nод'ключае.мыle библиотеки (Dynamic Link Library DLL) также интен
сивно используются приложениями на "родном" С++. Полное описание библиотек DLL
" " с
в приложениях на родном ++ выходит за рамки книrи для начинающих, тем не менее
вам предлаrается rлава ознакомительноrо характера, посвященная этим библиотекам.
Что такое библиотека DLL
Почти все языки проrраммирования поддерживают библиотеки или стандартные
модули кода, включающие часто используемые функции. В "родном" С++ вы исполь
зуете множество функций, хранящихся в стандартных библиотеках. Например, функ
ция cell () , которую вы применяли в предыдущей rлаве, объявлена в файле заrоловка
cmath. Код этой функции сохраняется в библиотечном файле с расширением .lib, и
коrда создается исполняемый модуль проrраммы Sketcher, компоновщик извлекает
rлава 20. Написание собственных библиотек DLL 1181
код этой стандартной ФУНКЦИИ из библиотечноrо файла и интеrрирует ero копию в
файл. ехе проrраммы Sketcher. Если вы пишете друryю проrрамму и используете ту
же фУ:fIКЦИЮ, она также получает собственную копию ФУНКЦИИ cell () Функция cell ()
является статически СВЯЗЪtваемой с каждым приложением и представляет собой состав..
ную часть каждоrо исполняемоrо модуля (рис. 20.1).
Статическая библиотека
Копия добавляется в каждую проrрамму во время компоновки
Библиотечная
функция
ProgramA.exe ProgramB.exe ProgramC.exe
Библиотечная
функция
L....+ Библиотечная
функция
........... Библиотечная
функция
Рис. 20.1. Статическая комnоиовка
Хотя это очень удобный способ применения стандартной ФУНКЦИИ при минимальных
усилиях с вашей стороны, с ним связаны и недостатки, поскольку он ведет к TO что He
сколько параллельно выполняющихся проrрамм используют несколько копий одной и
той же ФУНКЦИИ в среде Windows. Статически связываемые стандартные фУНКЦИИ, при
меняемые более чем одной проrраммой, дублируются в памяти для каждой использую
щей их проrраммы. Это может показаться и несущественным дЛЯ ФУНКЦИИ cell (), но
некоторые фУНКЦИИ, например ввода и вывода, неизбежно являются общими для боль"
шинства проrрамм, и весьма вероятно, что они займут значительные объемы памяти.
Статическая компоновка таких функций была бы чрезвычайно неэффективной.
Дрyrой apryMeHT против статической компоновки заключается в том, что CTaHдapT
ная функция из статической библиотеки может быть скомпонована в сотни проrрамм
1182 Visual С++ 2010. Полный курс
в вашей системе, поэтому идентичные копии их кода будут занимать дисковое про..
странство в файле . ехе каждой проrраммы. По этой причине операционная система
Windows поддерживает дополнительную возможность работы с библиотеками CTaH
дартных функций. Они называются диuамичес1(,И nод'КЛ'Ю'Чае.мъt.Ми или диuамичес1(,И 1(,o.мn
uуе.мъt.Ми библиотеками (dynamic link library DLL). Это позволяет совместно использо
вать одну копию функции несколькими параллельно выполняющимися проrраммами,
избеrая необходимости помещения копии кода библиотечных функций в исполняемый
модуль использующих их проrрамм.
Как работают библиотеки DLL
Динамически подключаемая библиотека это файл, содержащий коллекцию MOДY
лей, которые MOryт при меняться любым количеством различных проrрамм. Обычно
такой файл имеет расширение. dll, хотя это и не обязательно. Присваивая имя би':'
блиотеке DLL, можете назначить ей любое расширение, которое вам нравится, однако
это повлияет на то, как она будет обрабатываться операционной системой Windows.
Система Windows автоматически заrружает динамически подключаемые библиотеки,
файлы которых имеют расширение. dll. Если они имеют KaKoeTO дрyrое расширение,
то придется заrружать их явно, добавляя для этоrо специальный код в проrрамму. В ca
мой операционной системе Windows некоторые системные библиотеки DLL имеют
расширение . ехе. Наверняка вы встречали файлы с расширениями . vbx (Visual Basic
Extension расширение Visual Basic) и . осх (OLE Custom Extension специальное pac
ширение OLE), которые применяются к библиотекам DLL, содержащим специальные
типы элементов управления.
Может показаться, что у вас есть выбор применять в своих проrраммах динами"
ческие библиотеки или нет, но на самом деле это не так. Проrраммный интерфейс АР!
Win32 используется любой проrраммой Windows, и этот интерфейс АР! реализован в
.
виде набора библиотек DLL. Библиотеки DLL фундаментальная часть проrрамми"
рования для Windows. Подключение функции из библиотеки DLL к проrрамме обе..
спечивается иначе, чем при подключении статически связываемых библиотек, rде код
помещается в проrрамму раз и навсеrда во время компоновки исполняемоrо модуля.
Функция в библиотеке DLL подключается к проrрамме только во время выполнения,
и это делается при каждом ее запуске, как показано на рис. 20.2.
На рис. 20.2 представлена последовательность событий, происходящих, коrда три
проrраммы, использующие функцию из библиотеки DLL, запускаются последователь"
но, а затем выполняются параллельно. Никакой код из библиотеки DLL не включается
в исполняемые модули ни одной из проrрамм. Коrда выполняется одна из проrрамм,
она заrружается в память, и если используемая ею библиотека DLL еще не находится в
памяти, она заrружается отдельно. Затем устанавливаются соответствующие связи меж
ду проrраммой и библиотекой DLL. Если при заrрузке проrраммы библиотека DLL уже
заrружена, то все, что потребуется сделать, это связать проrрамму с затребованной
функцией из библиотеки DLL.
В частности, обратите внимание на то, что коrда ваша проrрамма вызывает функ..
цию из библиотеки DLL, операционная система Windows автоматически заrружает
библиотеку DLL в память. Любая проrрамма, впоследствии заrруженная в память и
работающая с той же библиотекой DLL, может использовать любые средства, предо"
ставляемые той же 'Копией библиотеки DLL, поскольку операционная система Windows
распознает, что код библиотеки уже находится в памяти, и просто устанавливает связи
между ним и проrраммой. Операционная система Windows отслеживает количество
rлава 20. Написание собственных библиотек DLL 1183
проrрамм, которые пользуются библиотекой DLL, находящейся в памяти, так что би
блиотека остается там до тех пор, пока хотя бы одна проrрамма все еще работает с ней.
Коrда библиотека DLL уже не используется ни одной выполняющейся проrраммой
операционная система Windows автоматически удаляет ее из памяти.
4. Проrрамма В заrружена
2. Библиотека .dll заrружена
Library.dll
1. Проrрамма А заrружена
6. Проrрамма С заrружена
Проrрамма А
Проrрамма В
Проrрамма С
Library.dll
5. Связывание с ункцией из DLL
З. Связывание с функцией из DLL
Функция
7. Связывание с функцией из DLL
Память компьютера
Рис. 20.2. Раииее связыlаииеe
Библиотека MFC поставляется в форме множества библиотек DLL, к которым ваша
проrрам.ма может подключаться динамически, наряду с библиотеками, которые MOryт
компоноваться в проrрамму статически. По умолчанию мастер Application Wizard соз
дает проrраммы, динамически связываемые с библиотекой DLL из состава библиотеки
MFC.
Наличие функций, хранящихся в библиотеке DLL, позволяет изменять эти функ
ции, не затраrивая использующих их проrрамм. До тех пор, пока интерфейс функции
в библиотеке DLL остается неизменным, проrрамма может работать с новой версией
функции вполне успешно, без необходимости какой"либо перекомпиляции или пере
компоновки. К сожалению, это также имеет и свою отрицательную сторону: очень
1184 Visual с++ 2010. Полный курс
леrко в конечном итоrе применить с проrраммой неправильную версию библиотеки
DLL. Это может представить собой проблему для проrрамм, которые устанавливают
библиотеку DLL в системную (System) папку операционной системы Windows. HeKO
торые коммерческие приложения произвольным образом пишут библиотеки DLL, свя
занные с проrраммой, в эту папку, несмотря на вероятность Toro, что MOryт перезапи
сать существующие там одноименные библиотеки DLL. Это может неrативно сказаться
на друrих приложениях, которые инсталлированы ранее, и в худшем случае нарушить
их работоспособность.
Примечание. Библиотеки DLL не единственное средство cOBMecTHoro использова
ния классов или функций несколькими приложениями. Объектная модель компонентов
(Component Object Model СОМ) предоставляет друrой, более мощный способ созда
ния nporpaMMHbIX компонентов MHoroKpaTHoro использования. Использование модели
СОМ может быть весьма сложным, но библиотека активных шаблонов (Active Template
Library ATL), представляющая собой библиотеку шаблонов классов, может существенно
упростить работу с ней. Использование модели СОМ и библиотеки ATL весьма обширная
и интересная тема, но она не рассматривается в этой книrе.
Динамическое связывание во время выполнения
Библиотеки DLL, которые вы будете создавать в этой rлаве, автоматически заrружа
ются в память, коrда в нее для выполнения попадает использующая их проrрамма. Это
известно как дuиамu'Чес'Кое связыlаиuеe во время заzруз'Кu, или раииее связыlанuе,, поскольку
связи с используемыми функциями устанавливаются, как только проrрамма и библиоте
ка DLL заrружаются в память. Операция TaKoro рода проиллюстрирована на рис. 20.2;
но это не единственно возможный вариант. Возможна также заrрузка бибJlиотеки DLL
после начала выполнения проrраммы. Это называется дuиа.мu'Чес'Ким связыlаиuемM во время
выlолнеиuя,, или nоздиuм связыlаиuем.. Последовательность операций, осуществляемых
при этом, показана на рис. 20.3.
Динамическое связывание во время выполнения позволяет проrрамме отложить
связывание с библиотекой DLL дО момента, коrда потребуется определенная функция
из этой библиотеки DLL. Это позволяет писать проrрамму, которая может самостоя
тельно определять, коrда ей необходимо заrружать одну или несколько библиотек DLL,
на основе полученноrо ею ввода, так что в память будyr заrружены только те функции,
которые действительно нужны. В некоторых случаях это может радикально снизить
объем памяти, необходимой для выполнения проrраммы.
Проrрамма, реализованная для использования динамическоrо связывания во Bpe
мя выполнения, вызывает библиотечную функцию операционной системы Windows
по имени LoadLibrary () , чтобы заrрузИть библиотеку DLL в случае необходимо
сти. Адрес функции в библиотеке DLL может быть получен с помощью функции
GetProcAddress (). Коrда проrрамме уже не нужна библиотека DLL, она может OT
ключиться от нее вызовом функции FreeLibrary ( ) . Если никакая друrая проrрамма
не использует ту же самую библиотеку DLL, она будет удалена из памяти. Подробности
работы этоrо механизма в настоящей книrе не рассматриваются.
.
rлава 20. Написание собственных библиотек DLL 1185
1. Проrрамма заrружается, но
библиотеки DLL не заrружаются.
Проrрамма может использовать
любую из трех DLL.
Library1.dll
Library2.dll
LiЬrаrуЗ.dll
Program.exe
З. Библиотека Library2.dll заrружается
по запросу проrраммы.
Проrрамма
Library2.dll
2. В этой точке проrрамма определяет,
что ей необходима Library2.dll,
и инициирует ее заrрузку.
Функция
4. Проrрамма получает адрес
функции из DLL и использует
ero для вызова функции.
Память компьютера
Рис. 20.3. Поздиее связыlапиеe
Содержимое библиотеки DLL
Динамически подключаемая библиотека не оrраничена хранением кода функций.
Вы можете также размеrцать в ней ресурсы, включая битовы е r.рафические изображе
ния и шрифты. Иrра Solitaire (Пасьянс), поставляемая вместе с операционной систе
мой Windows, использует динамическую библиотеку Cards . dll, содержащую все бито
вые изображения карт, наряду с функциями манипуляции ими. Если захотите написать
собственную карточную иrру, то можете с успехом использовать эту библиотеку DLL
в качестве базы и не заботиться о создании собственных rрафических изображений,
необходимых для представления карт на экране. Конечно, потребуется точно знать,
какие функции и ресурсы включены в библиотеку DLL.
Вы также можете определить в библиотеке DLL статические rлобальные перемен
ные, включая объекты классов С++, так что они станут доступными ИСПОЛЬЗУЮIЦИМ ее
1186 Visual С++ 2010. Полный курс
проrраммам. Конструкторы rлобальных статических объектов класса вызываются aBT<r
матичски при создании этих объектов. Следует заметить, что каждая проrрамма, пользу"
ющаяся библиотекой DLL, получает собственную копию любоrо статическоrо rлобально
ro объекта, определенноrо в библиотеке DLL, даже если он не обязательно применятся в
проrрамме. Для rлобальных объктов классов это включает накладные расходы на вызов
конструктора каждоrо из них. Поэтому необходимо избеrать включения таких объектов
в библиотеки DLL, если только это не является абсолютно необходимым.
Интерфейс библиотеки DLL
ВЫ не можете просто получить доступ ко всему, что содержится в библиотеке DLL.
Внешнему миру видимы только элементы, специально идентифицированные как Э'Кс
nортuруе-м-ыl.. Функции, классы, rлобальные статичекие переменные и ресурсы MOryт
экспортироваться из библиотеки DLL, и вместе они образуют ее uитерфейс. Все, что
не экспортировано, остается недоступным извне. Далее в этой rлаве вы увидите, как
можно экспортировать элементы из библиотеки DI..L.
Функция DllМain ( )
Даже несмотря на то, что библиотека DLL не является исполняемым модулем,
будучи независимой проrраммой, она содержит специальную разновидность функ..
ции main () по имени DllMain (). Эта функция вызывается операционной системой
\Vindows, коrда библиотека DLL впервые заrружается в память, чтобы позволить ей
выполнить всю необходимую инициализацию перед использованием ее содержимоrо.
Операционная система Windows также вызывает функцию DllMain () непосредственно
перед удалением библиотеки DLL из памяти, чтобы дать ей возможность выполнить
необходимую очистку за собой. Существуют и дрyrие ситуации, коrда вызывается Функ
ция DllMain () , но они выходят за рамки тем, рассматриваемых в настоящей книrе.
Вариации библиотек DLL
Существуют три разновидности библиотек DLL, которые вы можете собирать в сре..
де разработки Visual С++ 2010 с использованием библиотеки MFC: библиотека DLL рас..
ширения библиотеки MFC; обычная библиотека DLL, статически связанная с библио..
текой MFC; обычная библиотека DLL, динамически связанная с библиотекой MFC.
Библиотека DLL расширения библиотеки MFC
Сборка библиотеки DLL этоrо типа осуществляется, коrда в нее планируется вклю"
чить классы, унаследованные от библиотеки MFC. Ваши производные классы в библио..
теке DLL, по сути, расширяют библиотеку MFC. В среде, rде будет применяться ваша
библиотека DLL, должна присутствовать библиотека MFC, поэтому все классы библио..
теки MFC должны быть доступны вместе с вашими производными классами отсюда и
название "библиотека DLL расширения библиотеки MFC" (MFC Extension DLL). Однако
наследование ваших классов от библиотеки MFC не единственная причина использо..
вания библиотеки DLL TaKoro типа. Если вы пишете библиотеку DLL, которая содержит
функции, передающие указатели на объекты классов библиотеки MFC функциям про..
rpaMMbI, использующей ее, или же получающие такие параметры от функций проrрам
мы, то необходимо создавать ее как библиотеку DLL расширения библиотеки MFC.
Доступ к классам в библиотеке MFC через библиотеку DLL расширения всеrда осу"
ществляется динамически за счет СВЯdывания с совместно используемой версией библи..
отеки MFC, которая сама реализована в виде набора библиотек DLL. Библиотеки DLL
rлава 20. НаIJисание собственных библиотек DLL 1187
расширения создаются с применением совместно используемой версии библиотеки
MFC, поэтому, коrда вы применяете библиотеку DLL расширения, должна быть доступ
ной и совместно используемой версия библиотеки MFC. Библиотека DLL расширения
библиотеки MFC может использоваться обычным приложением, созданным мастером
Application Wizard. Это требует выбора параметра Use MFC as Shared 011 (Использо
вать библиотеку MFC как совместно используемую библиотеку DLL) в наборе свойств
General (Обlцие), который доступен после выбора пункта меню Projectr:::> Properties
(Проектr:::>Свойства). Это выбор по умолчанию для проrрамм, созданных мастером
Application \tVizard. Пu причине фундаментальной природы совместно используемой
версии библиотеки MJ?C в библиотеках DLL расширения библиотеки MFC такие би
блиотеки не MOryт использоваться проrраммами, которые статически связаны с библи..
отекой MFC.
Обычная библиотека DLL, статически связанная с библиотекой MFC
ЭТО библиотека DLL, которая использует классы библиотеки MFC, связанные стати
чески. Применение библиотеки DLL не требует доступности библиотеки MFC в среде
во время выполнения, поскольку код всех используемых классов включен в саму библи
отеку DLL. Это увеличивает размер библиотеки DLL, но обеспечивает значительное
преимущество подобные библиоrеки DLL MOryт использоваться любыми проrрамма
ми Win32, независимо от Toro, применяют ли они библиотеку MFC.
Обычная библиотека DLL, динамически связанная с библиотекой MFC
ЭТО библиотека DLL, использующая динамически связанные классы библиотеки
MFC, но не добавляющая собственных классов. С библиотеками DLL подобноrо рода
MOryT работать любые проrраммы Win32, независимо от Toro, применяют ли они сами
по себе библиотеку MFC, однако их использование требует доступности библиотеки
MFC в среде выполнения.
Для сБОрI<:И всех трех типов библиотек DI...L, ИСПОЛЬЗУЮIЦИХ библиотеку MFC, мож"
но при менять мастер Application Wizard. Вы также можете создать проеI<:Т библиотеки
DLL, который вообще независим от библиотеки MFC; дЛЯ этоrо создается проект типа
WiпЗ2 на основе шаблона WiпЗ2 Project (Проект Win32) с выбором библиотеки DLL в
настройках приложения проекта.
Что помещать в библиотеку DLL
Как решить, коrда необходима библиотека DLL? В большинстве случаев использо..
вани е библиотеки DLL представляет собой решение определенноrо рода проrрамм
ной проблемы, поэтому, если у вас есть такая проблема, библиотека DLL может OKa
заться подходящим ответом. Общим знаменателем часто является необходимость в
разделении кода между рядом проrрамм, однако встречаются и друrие ситуации, коrда
библиотека DLL обеспечивает преимущества. Ситуации, в которых помещение кода
или ресурсов в библиотеке DLL оказывается очень удобным и эффективным подходом,
кратко описаны ниже.
О у вас есть набор функций или ресурсов, которые вы хотите стандартизовать и
использовать в нескольких различных проrраммах. В частности, библиотека
DLL будет хорошим решением, коrда существует вероятность, что некоторые
из ваших проrраl\fМ, использующих стандартные средства, будут выполняться
параллельно.
1188 Visual С++ 2010. Полный курс
о У вас есть сложное приложение, включающее несколько проrрамм и оrромный
объем кода, но при этом часть функций или ресурсов MOryт быть совместно ис
пользованы некоторыми проrраммами приложения. Применение библиотеки
DLL дЛЯ хранения общих функций или ресурсов позволяет управлять и разраба..
TbIBarb их со значительной степенью независимости от проrраммных модулей,
использующих их, а также упрощает сопрово)кдение проrрамм.
О Вы разработали набор стандартных прикладных классов, унаследованных от
библиотеки MFC, которые собираетесь применять в нескольких проrраммах.
Упаковывая реализацию этих классов в библиотеке DLL расширения, вы може
те значительно упростить их использование в нескольких проrраммах, обеспе
чивая при этом возможность совершенствования этих классов, не затраrивая
конечных приложений.
О Вы разработали набор функций, предоставляющих леrкий в применении, но не--
вероятно мощный набор инструментов для прикладной области, который при
rодится мноrим разработчикам. Можете подrотовить пакет этих функций в виде
обычной библиотеки DLL и распространять ero в таком виде.
Бывают и друrие случаи, коrда вы можете предпочесть использовать библиотеку
DLL если хотите иметь возможность динамически заrружать и BbIrpY)KaTb библиоте
ки либо выбирать различные модули во время выполнения. Вы даже можете использо
вать их для облеrчения разработки и обновления cBoero приложения.
Чтобы лучше понять, как надо использовать библиотеку DLL, попробуем создать ее.
Написание библиотек DLL
Мы должны рассмотреть два аспекта написания библиотеки DLL: как собственно
писать библиотеки DLL и как определить, что именно будет доступно в библиотеке
DLL проrраммам, ее использующим. В качестве практическоrо примера создадим би
блиотеку DLL расширения, которая добавит набор классов в библиотеку MFC. Затем
раСIПИРИМ эту библиотеку DLL за счет добавления переменных, доступных использую
щим ее ПРОI'раммам.
Написание и использование библиотек DLL расширения
Можно создать библиотеку DLL расширения библиотеки MFC, которая будет co
держать классы фиryр для приложения Sketcher. Хотя это не предоставит особых
преимуществ данному приложению, все же позволит продемонстрировать способ на..
писания библиотеки DLL расширения без необходимости написания значительноrо
объема HOBoro кода.
Начальной точкой будет мастер Application Wizard, поэтому создайте новый про
ект, нажав комбинацию клавиш <Ctrl+Shift+N> и выбрав в качестве типа проект MFC,
а в качестве шаблона MFC DLL.
Такой выбор указывает на то, что вы создаете проект для основанной на библиотеке
MFC динамической библиотеки ExtDLLExample. Щелкните на кнопке ОК и выберите
Application Settings (Настройки приложения) в следующем окне (рис. 20.4).
Здесь находятся три переключателя, соответствующие трем типам библиотек DLL
на базе библиотеки MFC, о которых упоминалось ранее. Необходимо выбрать третий
переключатель, как показано на рисунке.
rлава 20. Написание собственных библиотек DLL 1189
.......... ""'1 ".,. h
. '\.1FC DLl"N Z6ro - t{J
:
,
Арр&саtlo" Settings
"
Overview
DLl type:
Regu/ar QI.1. tJSing !.hared f.1FC DLi.
BQu!ar D!.L ,,,,th МFC stat!cal!y lil"1Ked
f'o-f=С rens:on ОН.
Additюnal reatI.J(e$:
Aцt:omat!oo
App!icatlon SettlngS
t!
I
'
iпdo'iIIS soc"ts
,
1,
\
l
1.
'
< Prev-lO\.l$
FIf1IS,>t
canc.el
С]
Рис. 20.4. (}кио Application Settings
Два флажка ниже rруппы переключателей позволяют включить в библиотеку DLL
код поддержки автоматизации (Automation) и сокетов Windows (Windows sockets). Это
дополнительные возможности проrрамм Windows, которые здесь вам не понадобятся.
Авmомаmuзац,'UЯ обеспечивает возможность размещения объектов, созданных и управля
емых одним приложением, в друrом приложении. сlжеmыl Windows предоставляют клас
сы и функции, позволяющие проrрамме взаимодействовать с друrими проrраммами по
сети, но это выходит за рамки тематики книrи. Щелкните на кнопке Finish (fOTOBO) и
завершите создание проекта.
После Toro как мастер MFC DLL завершит работу, можете заrлянуть в созданный
им код. Если вы посмотрите на содержимое проекта в панели Solution Explorr (Про
водник решений), то увидите, что мастер MFC DLL создал несколько файлов, включая
файл. txt, содержащий описание остальных файлов. Можете все прочитать в этом
текстовом файле, однако два из созданных файлов реализации библиотеки DLL пред
ставляют непосредственный интерес (табл. 20.1).
Таблица 20. 1. Некоторые файлы, созданные мастером MFC DLL
Имя файла Содержимое
dllmain. срр Этот файл содержит функцию DIIMain () И является первичным исходным
файлом библиотеки DLL
ExtDLLExample . def Информация в этом файле используется во время компиляции. Он coдep
жит имя библиотеки DLL, и вы можете также добавить в Hero определения
элементов библиотеки DLL, которые должны быть доступны проrрамме, ис
пользующей эту библиотеку. В приведенном далее примере применяется
альтернативный и в некоторой степени более простой способ идентифика
циитакихэлементов
'Коrда ваша библиотека DLL будет заrружена в память, то первое, что про изойдет,
запуск функции DllMain () , потому рассмотрим сначала эту функцию.
1190 Visual С++ 2010. Полный курс
Функция DllМain ()
Если просмотреть содержимое файла dllmain. срр, то можно увидеть, что мастер
MFC DLL создал версию функции DIIMain (), которая приведена ниже.
extern "С" int APIENTRY
DIIMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
// Удалите это, если используется lpReserved
UNREFERENCEDPARAМETER(lpReserved);
if (dwReason DLLPROCESSATTACH)
{
TRACEO("EXTDLLEXAМPLE.DLL Initializing!\n");
// Однократная инициализация DLL расширения
if (!AfxInitExtensionModule(ExtDLLExampleDLL, hInstance))
return О;
// Вставьте эту DLL в цепочку ресурсов
// ПРИМЕЧАНИЕ: если DLL расширения неявно связана
// с обьной MFC DLL (такой как элемент управления ActiveX),
// а не с приложением MFC, вы можете удалить эту строку
// из DIIMain и поместить ее в отдельную функцию,
// экспортируемую из этой DLL расширения. Обьная DLL,
// которая использует эту DLL расширения, должна явно вызывать
// эту функцию для инициализации DLL расширения. В противном
// случае объект CDynLinkLibrary не будет присоединен к цепочке
// ресурсов обьной DLL, что приведет к возникновению серьезнь
// проблем.
new CDynLinkLibrary(ExtDLLExampleDLL);
}
else if (dwReason DLLPROCESSDETACH)
TRACEO("EXTDLLEXAМPLE.DLL Terminating!\n");
// Завершить библиотеку перед вызовами деструкторов
AfxTermExtensionModule(ExtDLLExampleDLL);
}
return 1; // Все в порядке
При вызове функции DIIMain () передаются три apryMeHTa. Первый apryMeHT
hlnstance это дескриптор, созданный операционной системой Windows для иден
тификации библиотеки DLL. Каждая задача под управлением операционной системы
Windows имеет дескриптор экземпляра, уникально идентифицирующий ее. Второй ap
ryMeHT dwReason указывает причину вызова функции DIIMain () . Леrко заметить,
что этот apryMeHT проверяется в операторе if. Первый оператор if проверяет ero на
равенство значению DLL PROCESS АТТАСН, указывающему, что проrрамма собирается
использовать данную динамическую библиотеку, а второй оператор i f на равенство
значению DLL PROCESS DETACH, указывающему, что проrрамма завершает работу с би
блиотекой DLL. Третий apryMeHT указатель, зарезервированный для использования
операционная системой Windows, поэтому ero можно иrнорировать.
rлава 20. Написание собственных библиотек DLL 1191
Коrда библиотека DLL используется проrраммой в первый раз, она заrружа
ется в память, и функция DIIMain () выполняется с apryMeHToM dwReason, YCTa
новленным в значение DLLPROCESSATTACH. ЭТО приводит к вызову функции
Afxlni tExtensionModule () , необходимой для инициализации библиотеки DLL и
создания в распределяемой памяти объекта класса CDynLinkLibrary. Операционная
система Windows использует объекты этоrо класса для управления библиотекой DLL
расширения. Если хотите преДУСl\10треть собственную инициализацию, добавьте ее в
конец этоrо блока. Любой код очистки, необходимый вашей библиотеке DLL, можно
добавить в блок следующеrо оператора if.
Добавление классов в библиотеку DLL расширения
Библиотека DLL используется для включения реализации классов форм прило..
жения Sketcher, поэтому переместите файлы Elements . h и Elements . срр из папки,
содержащей исходный код Sketcher, в папку, храНЯIЦУЮ библиотеку DLL. Убедитесь,
что вы перемещаете файлы, а не копируете их. Поскольку классы фиryp приложения
Sketcher должна под,держивать библиотека DLL, незачем оставлять их в исходных TeK
стах caMoro приложения.
Потребуется также удалить файл Elements . срр из проекта Sketcher. Чтобы cдe
лать это, откройте проект Sketcher, выделите файл Elements. срр в панели проводни
ка решения, затем нажмите клавишу <Delete>. Если вы не сделаете этоrо, компилятор
сообщит, что он не может найти файл при попытке компиляции проекта. С помощью
той же процедуры избавьтесь от файла Elements. h в папке Header Files (Файлы заrо
ловка) панели проводника решения.
Теперь потребуется добавить файлы Elements. h и Elements. срр в проект библи
отеки DLL раСIIlирения, поэтому откройте проект ExtDLLExample, выберите пункт
меню ProjectAdd Existing Item (ПроектДобавить супествующий элемент) и укажи..
те файлы Elements. h и Elements. срр в окне списка диалоrовоrо окна, как показано
на рис. 20.5.
d ь: :;+- lt........ t"\
се ExtOlLExamp.t . &tDllExц.mpt .
"
... ,.;- f
;...
О'qi3щzе ... N'" tdr
.,-
t
I1
I
Conta-cu
, DfiktOP
Oownloads
:t Favorit6
links
му Documnt5
. М! Mu:sic
..; му Pictur6
му Vidюs
s.avd Games
SEarches
\'irtual Machln6
, COn"lpиter
... Network
I MESH
PHENOM
" "t "!....n:] '}
Nзmе
Оме modifid
01 п.l/2.00913:41 I i '
7 I
P OO9 j I
res
dllm<!in.cpp
C E.l;m.t.pp
j ti1 Eternents.h
ExtDH.:Example.cpp
,А ExtDllExample.def .
ExtDllE:xвmple.rc
1:1 fxtDllExampte.vcxproj
,z1 ExtOLlExample.vcxproj.filters
L.. ExtDLlExampt.vcXP'°J.иsr
t..J RlIdMe.txt
Resource.h
stdah.cpp
","1 ,.+ J"
01/12/2009 B:I7
07112/'2009 13:4-1 i
O"'f12J'2f't'913;47
01 пl 2fJ0913:.t1
01111/2fJ0913:47
O/11!2009H47
01111 lOO9 13;47
07/1212009 13:017
07/11/100913:41
I
F.le пате: "Elements.cpp" "Elements h"
.
...1
... I А!' Files r ""\
Add 1 rCa
ж"-;;::;;;
,
r;;:;;",;7:,..;;;J1.:';1i!J ..,:.,."""";, ""l'l!!" ,.'
Рис. 20.5. Добавление файлов в проект библиотеки DLL расширения
1192 Visual С++ 2010. Полный курс
Можете добавить несколько файлов за один раз, удерживая нажатой клавишу <Ctr]>
во вреl\tIЯ выбора файлов в списке диалоrовоrо окна Add Existing Item. Затем вы должны
увидеть добавленные файлы в панели проводника решений, а все классы формы в па
нели представления классов проекта.
Классы формы используют константы VERSION NUMBER и SELECT COLOR, опреде
ленные в файле SketcherConstants . h, поэтому скопируйте их определения из файла
SketcherConstants . h в папке проекта Sketcher в файл Elements . срр в папке 'библио
теки DLL (можете поместить их непосредственно после директивы #include). Обрат"
те внимание на то, что константы VERSION NUМВER и.SЕLЕСТ COLOR используются исклю
чительно классами формы, поэтому можете удалить их из файла SketcherConstants . h,
используемоrо в проrрамме Sketcher, или только закомментировать их.
Экспорт классов из библиотеки DLL расширения
Коллекция объектов в библиотеке DLL, к которым может обратиться использующая
ее проrрамма, называется u.1lтерФейСОМ библиотеки DLL. Процесс создания объектной
части интерфейса библиотеки DLL называется экспортом объекта, а процесс доступа к
экспортируемому библиотеой DLL объекту импортом объекта.
lIMeHa определенных в библиотеке DLL классов, которые должны быть доступны
в использующей ее проrрамме, должны быть некоторым образом идентифицированы,
чтобы между проrраммой и библиотекой DLL моrли устанавливаться соответствую..
щие связи. !{ак было показано ранее, одним из способов сделать это является добав
ление информации к файлу. def для библиотеки DLL. Это подразумевает добавление
к библиотеке DLL Toro, что называется декорuрова1l1lыlu. име1lами (decorated пате), и
ассоциацию декорированноrо имени с уникальным идентифицирующим числовым
значением, называемым nорядковыlM (ordinal). Декорированное имя объекта это имя,
созданное компилятором, который добавляет дополнительную строку к имени, присво
eHHoro вами объекту. Эта дополнительная строка предоставляет информацию о типе
объекта, или в случае функции информацию о типах параметров функции. Помимо
прочеrо, это rарантирует уникальность идентификаторов и позволяет компоновщику
отличать переrруя(енные функции друr от друrа.
Получение декорированных имен и присвоение порядковых значений для экспорта
элементов из библиотеки DLL требует большоrо объема работы и является не лучшим
и не самым простым подходом в операционной системе Windows. HaMHoro леrче иден
тифицировать классы, которые вы хотите экспортировать из библиотеки DLL, моди
фицируя их определения в файле Elements. h, чтобы они включали ключевое слово
AFX ЕХТ CLASS для каждоrо имени класса, как показано в следующем фраrменте для
класса CLine.
// Класс, определяющий объект линии
class AFXEXTCLASS CLine: public CElement
{
// Подробности определения класса, как прежде...
} ;
Макрокоманда AFX ЕХТ CLASS указывает, что класс подлежит экспорту из библио
теки DLL. Это делает весь класс доступным любой проrрамме, использующей библио
теку DLL, и автоматически открывает доступ ко всем данным и функциям в открытом
интерфейсе класса.
Макрокоманда AFXEXTCLASS определена как declspec (dllexport), коrда
она присутствует в библиотеках DLL расширения, и как declspec (dllimport) в
rлава 20. Написание собственных библиотек DLL 1193
противном случае. Это означает, что вы можете использовать ту же макрокоманду как
для экспорта объектов из библиотеки DLL, так и для импорта объектов в приложение.
Но атрибуты dllexport и dllimport предоставляют более общий механизм для экс
порта и импорта объектов в библиотеку DLL, который вы можете использовать незави"
симо от Toro, является ли ваша библиотека DLL расширением библиотеки MFC. Таким
образом, я буду использовать в примере их, а не имя AFX ЕХТ CLASS, которое доступно
только в библиотеке MFC.
Ниже показано, как вы можете использовать макрокоманду declspec (dllexport)
для экспорта класса CLine из библиотеки DLL.
// макрокоманда делает код более читаемым
#define SКETCHERAPI declspec(dllexport)
// Класс, определяющий объект линии
class SКETCНERAPI CLine: public CElement
{
// Подробности определения класса, как прежде...
} ;
Символ SKETCHER АРI необходимо добавить ко всем друrим классам формы, вклю
чая базовый класс CElement, точно так же, как класс CLine. Почему необходимо экс
портировать класс CElement из библиотеки DLL? В конце концов, проrраммы создают
только объекты классов, производных от класса CElement, и не объекты caMoro класса
CElement. Одна из причин заключается в том, что вы объявили открытые члены класса
CElement, которые являются частью интерфейса для производных классов формы и
которые почти наверняка понадобятся проrраммам, использующим библиотеку DLL.
Если вы не экспортируете класс CElement, то такие функции, как GetBoundRect () , бу
дут недостvпны. Друrая причина в том. что в приложении Sketcher вы создаете пере
менные типа CElement или CElement *. Поэтому, чтобы код компилировался, определе
ние класса должно быть доступно.
Теперь сделано все необходимое для добавления классов фиryр в библиотеку DLL.
Остается лишь скомпилировать и выполнить сборку проекта, создав библиотеку DLL.
Сборка библиотеки DLL
Сборка библиотеки DLL выполняется точно таким же образом, как и любоrо дpy
roro проекта, с помощью пункта меню BuildBuild Solution (СборкаСобрать ре..
шение) или щелчка на соответствующей кнопке панели инструментов. Однако вывод,
который вы получите при этом, несколько отличается. Вы можете увидеть собранные
файлы во вложенной папке Debug папки проекта при отладОЧ1l0U (Debug) сборке или
в папке release при ОКО1l'Ц,атеЛЪ1l0U (Release) сборке. Исполняемый код библиотеки
DLL содержится в файле ExtDLLExample. dll. Этот файл должен быть доступен при
выполнении проrрамм, которые пользуются библиотекой DLL. Файл ExtDLLExample .
lib это импортируемый файл библиотеки, содержащий определения элементов, экс
портированных из библиотеки DLL, который должен быть доступен КОМПОНОВIЦИКУ
при сборке проrрамм, использующих библиотеку DLL.
<р ' ечание. c" обнаружите, что Сб ; а ' бЛИОТ И "D LL а вершается неудач е' из I
. за Toro, что файл Elernents. срр содержит директиву #include для файла Sketcher. h, 1
удалите ее. 8 моей системе мастер Class Wizard добавил эту директиву #include при соз-
дании кода ca CEl ement, хотя это и не обязательно. щ
1194 Visual С++ 2010. Полный курс
Импорт классов из библиотеки DLL
Теперь у вас нет никакой информации в проrрамме Sketcher о классах фиryр, по..
скольку вы переlVlестили файлы, содержащие опредсления классов и их реализации
в проект библиотеки DLL. Однако компилятору все paB!l0 необходимо знать, откуда
возьмутся классы фиryр, чтобы скомпилировать код проrраммы. Проrрамма Sketcher
должна включать файл заrоловка, опредеЛЯЮIIИЙ классы, которые подлежат ИlVIПОРТУ
из библиотеки DLL. Она также должна идентифицировать классы как внешние по от..
ношению к проекту, используя макрос declspec (dllirnport) в определениях клас
сов для имен классов.
Самый простой способ изменить файл Elernents. h в проекте DIJ так, чтобы он
применялся и в проекте Sketcher. Для определений классов в файле Elernents . h необ..
ходима квалификация макрокомандой declspec (dllexport), коrда они находятся
в проекте DLL, и макрокомандой declspec (dllirnport), коrда они находятся в про..
екте Sketcher. Вы можете сделать это, заменив макрокоманду #define для символа
SKETCHER API в файле Elernents . h следующим образом.
#ifdef ELEМENT EXPORTS
#define SKETCHER API declspec{dllexport)
#else
#define SKETCHER API declspec{dllimport)
#endif
Если символ ELEMENT EXPORTS определен, то символ SKETCHER API будет заменен
макрокомандой declspec (dllexport), в противном случае макрокомандой
declspec(dllirnport).
Символ ELEMENTEXPORTS не опредеJ1:ен в проекте Sketcher, поэтому использона
ние в нем файла заrоловка сделает то, что вы хотите. Достаточно определить символ
ELEMENT EXPORTS в проекте ExtDLLExarnple. Для этоrо можно добавить в файл заrо..
ловка stdafx. h проекта библиотеки DLI, непосредственно после директивы #pragrna
once, следующую директиву.
#define EtEМENT EXPORTS
Теперь проект библиотеки DLL можно откомпилировать.
Использование библиотеки DLL расширения в приложении Sketcher
Теперь, коrда вы скопировали новую версию файла Elernents . h в проект, вы долж..
ны добавить ero в проект снова, щелкнув правой кнопкой мыши на папке Header Files
в панели проводника реIпения и выбрав в контекстно:м меню пункт AddExisting Item
(ДобавитьСуществующий элемент).
Коrда вы будете перестраивать приложение Sketcher, компоновщику понадобится
указать зависимость проекта от файла ExtDLLExarnple .lib, поскольку этот файл coдep
жит информацию о содержимом библиотеки DLL. Щелкните правой кнопкой мыши
на Sketcher в панели проводника решения и выберите в контекстном меню пункт
Properties (Свойства). Затем введите имя файла .lib как дополнительную зависимость
(рис. 20.6).
На рис. 20.6 показано вхождение для отладочной версии приложения Sketcher.
Файл .lib для библиотеки DLL расположен в папке Debug папки проекта библиотеки
DLL. Если вы создаете рабочую версию приложения Sketcher, вам также понадобится
рабочая версия библиотеки DLL с соотвеТСТВУЮIЦИМ файлом .lib, поэтому необходимо
ввести полностью квалифицированное имя файлd. . 1 ib для рабочей версии библиотеки
rлава 20. Написание собственных библиотек DLL 1195
DLL, соответствующее рабочей версии приложения Sketcher. Файл, к которому при..
меняются свойства, выбирается в раскрывающемся окне списка Configuration (Кон"
фиryрация) в окне Properties (Свойства). Имеется только одна внешняя зависимость,
но при необходимости их можно ввести несколько, щелкнув на кнопке справа от TeK
cToBoro поля ввода. Поскольку здесь вводится полный путь файла .lib, компоновщик
будет знать не только о внешней зависимости от файла ExtDLLExarnple .lib, но и о ero
местоположении.
'1 . """':"""""" ."
Slc.etcr PI'Orty Pages
r . .
Conf'9uration: :i; U9) --:J Platform
............ .................................................
Configuration Properties ... I
GenerAr
Debugging
vC+ + Directories
С/С+ +
Gener1
OptimizMion
Preprocessor
Code Genera.tion
lзпguёtgе t
Precompiled Heade! 2
Ошри' Fites I
Browse Information:
I
Adv4nced I
Command tine '
Linkr
Gneral
Jnput
Manifest File
Debugging
System
Cptimlzation
Emdded IDL
Adwanced
Command Line
r
t I
!
I
I
Ignore AII Default libraries
Ignore Specific Defаuи. Libraries
Modlle Definitlon FiJe
Add Module to Assembly
Embed Managed Resoufce File
Fcrce Symbol Reference
Delay loaded DI15
AssembIy Linlc Rеюur<е
fk;;;[;;=. .=== :=== ;i Мiо -;п;r: I I
WtLb.."pi. \o.b..gIE.tOi 1.::J I
I
I
I
!
I
I
!
1,.
11
I
!
f
1
I
1
I
J
I
1
I
1.
f 1'. .::(. .. -:!:,.. =!,,,"-
I .. '"
L
I
l'
I
!
I
I
I
I
!
щ; j
:
.....
, Additionat ()pendetКies
Specmes eodltlcna-J ltm5 to edd to tne I.nk c-ommnd Ilne Н.е, kmJ31.llb]
f .........."....,."".. ("
ОК ':еn,еl
Рис. 20.6. Д обавле1luе к проекту допОЛ1luтелЪ1l0й завUС'U.t'Иостu
Чтобы приложение Sketcher компилировал ось и компоновалось правильно, файл
.lib для библиотеки DLL должен быть доступен приложению. При запуске приложе
ния Sketcher файл . dll обязателен. Чтобы позволить операционной системе Windows
найти и заrрузить библиотеку DLL дЛЯ запущенной проrраммы, ее обычно помещают
в папку SуstеrnЗ2. Если ее нет в этой папке, операционная система Windows ищет ее
в папке, содержащей исполняемый файл (Sketcher. ехе в данном случае). Если ее
не будет и там, то вы получите сообщение об ОIIIибке. Чтобы не заrромождать папку
SуstеrnЗ2, скопируйте библиотеку ExtDIIExarnple. dll из папки Debug проекта би
блиотеки DLL в папку Debug приложения Sketcher. Если теперь откомпилировать
приложение Sketcher еще раз, то окажется, что есть проблема. В частности, функция
Serialize () класса CSketcherDoc вызывает ошибку компоновщика LNK2001. Про..
блема также с функцией operator» () класса CArchive. Есть версия этой функции,
определенная в классе CArchi ve, который поддерживает тип CObj ect, и в первона
чальной версии приложения Sketcher. Компилятор был счастлив использовать ее для
десериализации объектов CElernent прежде, чем сохранять их в контейнере списка. Но
при импорте класса CElernent из библиотеки DLL это больше не работает. Вы можете
обойти эту проблему очень леrко. Измените код функции Serialize () , которая читает
объекты формы, так, как показано ниже.
1196 Visual с++ 2010. Полный курс
sizet elernentCount(O); // Хранит количество элементов
CObject* pElement(nullptr); // Указатель на элемент
ar » elernentCounti // количество элементов
// Теперь получить все элементы и сохранить в списке
for(size t i О ; i < elernentCount i ++i)
{
ar » pElernenti
) mElementList.pushback(staticcast<CElement*>(pElement) ;
Теперь вы храните адрес объекта формы, который предстоит десериализировать,
как тип CObj ect *. Затем вы приводите ero к типу CElement *, прежде чем сохранять в
списке. С этими изменениями функция Serialize () будет компилироваться и работать
как прежде. Приложение Sketcher должно выполниться как прежде, за исключением
Toro, что теперь оно использует классы формы из созданной библиотеки DLL.
Файлы, необходимые для использования библиотеки DLL
На основании Bcero вышеизложенноrо в контексте работы с созданной вами библи
отеки DLL в проrрамме Sketcher вы можете заключить, что для использования библи
отеки DLL в проrрамме должны быть доступны файлы, перечисленные в табл. 20.2.
Таблица 20.2. Файлы, необходимые ДЛЯ использования библиотеки DLL
.lib
Содержимое
Определяет элементы, экспортируемые из библиотеки DLL, и позволяет компилятору
правильно справляться со ссылками на эти элементы в исходном коде проrраммы,
использующей библиотеку DLL. Файл . h должен быть добавлен в исходный код про
rpaMMbI, использующей библиотеку DLL
Определяет элементы, экспортированные библиотекой DLL, в форме, позволяющей
компоновщику справляться со ссылками на экспортируемые элементы при компо..
новке проrрамм, использующих библиотеку DLL
Содержит исполняемый код библиотеки DLL, заrружаемый операционной системой
Windows при выполнении проrраммы, использующей эту библиотеку DLL
Расширение
.h
.dll
Если вы планируете поставлять проrраммный код в форме библиотеки DLL дЛЯ ис
пользования дрyrими проrраммистами, нужно будет поставлять все три файла в пакете.
Для приложений, уже использующих бибЛJ:fотеку DLL, понадобится только файл. dll
наряду с файлом . ехе.
Резюме
в настоящей rлаве вы изучили основы конструирования и применения динамиче
ски подключаемых библиотек.
Упражнения
Исходные коды упрюкнений и их решения можно заrрузить с вебсайта www.wrox.com.
1. Это последний шанс для усовершенствования данной версии проrраммы
Sketcher, так что воспользуйтесь им. Применяя созданную ранее библиотеку
DLL, реализуйте браузер документов Sketcher друrими словами, проrрамму,
rлава 20. Написание собственных библиотек DLL 1197
которая просто открывает документ, созданный проrраммой Sketcher, и OTO
бражает ero в окне целиком. Вам не нужно беспокоиться о редактировании, про
крутке и печати, но потребуется обрабатывать масштабирование, которое необ
ходимо для помещения большой картинки в маленьком окне!
Что вы узнали в этой rлаве
D Ди1lами'Чес'Ки nод'кл'ю'чаемыle библиотеки. Предоставляют средства динамическоrо
связывания стандартных функций при выполнении проrраммы вместо включе..
ния их в исполняемый модуль проrраммы.
D MFC DLL. Проrраммы, созданные мастером App1ication Wizard, по умолчанию
связываются с версией библиотеки MFC, сохраненной в библиотеке DLL.
D Исnолъзова1lие библиотеки DLL. Единственная копия библиотеки DLL в памяти MO
жет использоваться несколькими параллельно выполняющимися проrраммами.
D Библиотека DLL расшире1lUЯ. Называется так потому, что расширяет набор клас
сов библиотеки MFC. Библиотека DLL расширения должна применяться тоrда,
коrда необходимо экспортировать из библиотеки DLL классы, основанные на
библиотеке MFC, или объекты классов из библиотеки MFC. Библиотека DLL
расширения также может экспортировать обычные функции и rлобальные пере
менные.
D оБыlч1lая библиотека DLL. Можете использовать ее, если хотите экспортировать
только обычные функции или rлобальные переменные, не являющиеся экзем
плярами классов библиотеки MFC.
D Экспорт 'КЛассов из библиотеки DLL расшире1lUЯ. Осуществляется с помощью ключе
Boro слова AFX ЕХТ CLASS пред именем класса в библиотеке DLL.
D Импорт 'КЛассов из библиотеки DLL расшире1lия. Можете импортировать классы,
экспортированные библиотекой DLL расширения, применяя файл . h из библио
теки DLL, который содержит определения классов, с использованием ключевоrо
слова AFX ЕХТ CLASS.
Предметный указатель
А
АРI, 801
Application Programming Interface, 801
Application Wizard, 877
Assembly, 54
Assertion, 764
в
Boxing, 128
Breakpoint, 756
Brush, 953
с
Capture clause, 725
Casting, 98
CLI, 30; 34
Сliр rectangle, 1159
CLR, 30
ComInon Language Infrastructure, 30; 34
Соmmоп Language Runtime, 30
Соmmоп Туре SysteIn, 31
Constraint, 353
Container, 925
CTS, 31
D
DC, 850
DD 1081
DDX,1081
Debugging, 751
Decorated пате, 1192
Delegate, 615
Deserialization, 1161
Destructor, 446
Device context, 850, 947
Dialog Data Exchange, 1081
Dialog Data Validation, 1081
DLL, 613; 1180
Dockable toolbar, 40
Document template, 874
Document, 872
DPI, 949
DynaInic Link Library, 613, 1180
Е
Enunleration, 87
Enumerator, 87
Escape sequence, 49, 91
Event, 36; 615
Exception, 320
F
Finalizer, 628
Form, 925
Fl"аПlе window, 831; 873
Function template, 330
G
GDI,947
Generic function, 352
Glyph, 756
Graphical Device Interface, 947
GraphicallJser Interface, 32
GUI, 32; 33; 802
н
Handle, 135; 810
Неар, 224
I
IDE, 30; 37
Iпliпе function, 386
Inserter iterator, 716
Instantiation, 332
Integl"ated DеvеlОРПlепt Environment, 30; 37
Interior pointer, 265
J
jIT, 31
L
L"ЗНc:lчение, 115
Library,38
Listener, 791
Lvalue, 115
м
Mapping mode, 947
MDI, 815; 872
Memory leak, 226
Message loop, 816
Message риmр, 816
MFC, 32; 829
Мiсrоsоft Foundation Classes, 802
Мiсrоsоft Intermediate Language, 31
MSIL, 31
Multiple Documellt Interface, 815; 873
Mlltex, 860
N
.NET Frame\\yul.k, 3о.
NaInespace,54; 71
Предметный указатель 1199
о
w
Output, 39
Whitespace, 75
Windows Forms, 802; 834
р
Parallel Patterns Library, 838
Parent assembly, 610
Реп, 955
PPL, 838
Precompiled header, 892
Project, 41
Property, 427
R
А
Абстрактный класс, 586
Автоматизация, 1189
Адаптер контейнера, 647
Адаптер функции, 650
Адрес, 204
Алrоритм, 649
parallelfor, 839
parallelforeach, 841
parallelinvoke, 843
ApryMeHT, 272
Арифметическое выражение, 73
Ассоциативность, 104
Атрибут, 34; 141; 947
N onSerialized, 1161
OnDeserializing, 1162
OnSerializing, 1162
Seria1izable, 1161
R"значение, 115
Rank,249
Raster ope1 1 ation, 1109
Reference, 229
Resource View, 39
RO:g 1109
Rvalue, 115
5
Scope resolution operator, 55
Scope, 116
SDI, 872
Serialization, 1161
Single Document Interface, 872
Solution Explorer, 39
Solution, 42
Spin button, 1076
Standard TeInplate Library, 645
Start page, 39
STL, 645
Storage duratioh, 116
Stream, 74
Subscript operator, 660
т
Б
Базовый класс, 557
Безусловный переход, 164
Библиотека, 38
MFC, 32; 39; 802
Мiсrоsоft oundation Classes, 32; 829
STLjCLR, 732
Windows Forms, 39
шаблонов ДЛЯ параллельных вычислений, 838
Бинарное дерево, 642
Битовое
И, 109
ИЛИ, 111
НЕ, 113
исключающее ИЛИ, 112
оператор сдвиrа, 113
Бит состояния, 140
Блок, 75
catch,323
try, 322
Tag пате, 454
Toolbar, 40
Tooltip, 924
Trace switches, 792
Tracepoint, 758
Tracking handle, 238
Tracking reference, 264
в
u
Вектор, 647
Венrерская нотация, 807
Верификация данных диалоrовоrо окна, 1081
Виртуальная
машина, 31
функция, 580
Виртуальный деструктор, 593
Unboxing, 128
Union, 454
v
View, 872
Viewport, 1084
1200 Visual с++ 2010. Полный курс
Вложенный класс, 595
Внутренний указатель, 265
Возвращаемое значение, 273
Всплывающая подсказка, 924
Вставляющий итератор, 712; 716
Встроенная функция, 386
r
fлиф, 756
fлобальная переменная, 119
fрафический интерфейс пользователя, 32;
33; 802 I
д
Двунаправленная очередь, 647
двунаправленныIй связный список, 647
Декорированное имя, 1192
Декремент, 101
Делеrат, 615
Десериализация, 1161
Дескриптор, 135; 810; 813
Деструктор, 446
Диалоrовое окно, 1062
Динамическая память, 224
Динамически подключаемая библиотека,
613; 1180
Динамическое связывание, 1184
Директива, 71
#include, 47; 71
using, 124
препроцессора, 71
фиксации, 725
Диспетчеризация сообщения, 820
Документ, 872
Дружественная функция, 399
Дружественный класс, 577
з
Заrоловок функции, 273
Закрепляемая панель инструментов, 40
и
Идентификатор, 77
Импорт, 1192
Имя дескриптора, 454
Имя переменной, 77
Индекс, 191
Инициализация, 79
Инкапсуляция, 379
Инкремент, 101
Интеrрированная среда разработки, 30; 37
Интерфейс, 605; 1192
АРI Windows, 32; 806
IComparable, 353; 605
IController, 606
rрафических устройств, 947
класса, 495
прикладных проrрамм, 802
Инфраструктура общеrо языка, 30
Исключение, 225; 320
KeyNotoundFoundException, 637
out ofrange, 525
Исходный код, 38
Итератор, 648
к
Карта, 647
Карта сообщений, 900
Кисть, 813; 953; 957
Класс, 377; 379
Array, 242
CArchive, 1134
CBrush, 954; 957
CButton,1063
CCircle, 979
CCurve, 982
CDC, 950
CDialog, 1062; 1066
CDocument, 872
CFrameWnd, 831
Char, 183
CMultiDocTemplate, 874
Color, 999
combinable, 863
ConsoleKeyInfo, 135; 183
СРеп, 955
CPrintInfo, 1148
CRect, 374
CRectangle, 978
criticalsection, 860
СSiпglеDосТеПlрlаtе, 874
CStatic, 1063
CString, 1104
CTaskDialog, 1089
CWnd, 1063
Debug, 789
Delegate, 616
Dictionary<>, 637
File, 1164
Graphics, 999
LinkedI..ist<>, 636
Object, 598
Реп, 999
string, 231
Trace, 789
Предметный указатель 1201
TraceLevel, 793
wstring,231
Класс коллекции, 635
Класс типа значения, 128
Клиентская область, 804
Клиентские координаты, 1019, 1084
Ключевое слово, 78
abstract, 601
array, 240
auto, 108; 3З4
break, 161
case, 161
catch, 321
class, 377
const, 97
default,435
"
delegate, 616
dynaIniccast; 106
else, 153
enUIn class, 137
еп иm struct, 1 3 7
enUIn, 88
event, 624
explicit, 395
friend, 399
generic, 353
inline, 387
interface class, 606
interface struct, 606
interiorptr, 265
mutable, 725
naInespace, 124
nullptr, 239
operator, 457
override, 601
private, 380; 396
property, 428
protected, 380; 567
public, 378; 380
ref class, 418
ref struct, 418
signed, 83'
staticcast, 106
struct, 368
template, 331; 488
throw, 321
try,321
typedef,86
typenaIne, 331; 353
union, 454
unsigned, 84
valueclass,418
value struct, 418
virtual, 580
void, 274
where, 35З
Кнопка счетчика, 1076
Код возврата, 74
Командное сообщение, 902
Комментарий, 46; 70
Компилятор, 38
Компоновщик, 38
Конкатенация, 521
Консольное приложение, 35
Конструктор
безарryментов,389
класса, 264; 387
копирования, 403; 417
Контейнер, 646; 925
array, 694
deque,670
list, 674
mар, 699
InultiInap, 711
priorityqueue, 687
queue, 684
stack, 692
vector, 652
Контекст дисплея, 824
Контекст устройства, 824; 850, 947
Конфиryрация, 48
Координаты страницы, 1084
Координаты устройства, 1084
Критический раздел, 860
л
Литерал, 86
NULL,207
nullptr, 207
Литеральное поле, 423
Лоrические координаты, 1019, 1084
Лоrический тип, 84
Лоrическое
И, 156
ИЛИ, 156
НЕ, 157
Лямбдвыражение, 724
Лямбдаинтродуктор, 724
м
Макрокоманда
FUNCTION, 769
TO, 1026
ASSERT, 783
ASSERTE, 783
1202 Visual С++ 2010. Полный курс
BEGINMESSAGEМAPO, 900
DECLAREDYNCREATEO, 890; 1132
DECLAREMESSAGEМAPO, 888; 891; 901
ENDMESSAGEМAPO, 900
IMPLEMENTDYNCREATEO, 1132
IMPLEMENTSERIALO, 1136
ONCOMМANDO, 913
ONCOMМAND, 902
RGBO, 915
RGB, 848
RUNTIMEClASSO, 894
vaarg, 296
vaend, 296
vastart, 296
WINAPI,810
Макрос, 888
Манипулятор, 90
dec, 174; 208
hex, 174; 208
setiosf1agsO,532
setw, 90
Массив, 191
Мастер создания приложений, 877
Мноrодокументный интерфейс, 872
Мноrозадачность, 818
Мноrомерный массив, 200
Модальное диалоrовое окно, 1067
Модификатор
const, 96; 289
типа, 83; 97
Мьютекс, 860
н
Набор, 647
Наследование, 556
Незавершенное объявление класса, 463
Немодальное диалоrовое окно, 1067
Неочередизованноесообщение,816
Неполноеопределение,585
Неуправляемый код С++, 32
Нормализация, 977
о
ООП, 377; 379, 555
Область
видимости, 116
обновления, 968
просмотра, 1084
Обмен данными диалоrовоrо окна, 1081
Оболочка полиморфной функции, 731
Обработчик сообщений, 900
Обрамляющее окно, 831; 873
Обратный итератор, 654
Общая
инфраструктуры языка, 34
система типов, 31
функция, 352
Общеязыковая исполняющая среда, 30
Общий класс, 630
Объединение, 454
Объект, 379
Random, 243
функции, 474; 650
Объектноориентированное
проrраммирование, 377; 379
Объектный код, 38
Объектный файл, 38
Объявление переменной, 73; 78
Объявление
using, 72; 124
Оrраничение,353
Однодокументный интерфейс, 872
Окно вывода, 39
Оперативный компилятор, 31
Оператор, 73
%, 264
&&, 156
&, 109, 205
*, 206; 239
+,255;521
>, 240; 376
., 185
!, 157
«, 74; 89, 113
», 89, 113
1, 111
11,156
break, 161
continue, 171
decltype, 333
delete,224
for, 165
goto, 164
if, 148
ifelse, 153
new,224
return,275
safecast, 136
sizeof, 213
staticcast, 136
cout, 47
switch, 160
typeid, 108; 142
while, 175
Предметный указатель 1203
[], 525
,112
вставки, 89
вывода, 74
вызова функции, 474
доступа к чле 185; 369
извлечения, 89
индекса, 660
конкатенации, 255
KOCBeHHoro доступа, 206
непрямоrо доступа к члену, 376
отношения, 146
получения адреса, 205
присвоения, 73; 94
разрешения области видимоcrи, 55; 7,z 121; 386
, 113
Определение переменной, 79
Отладка, 751
Отладчик, 754
Отсекающий прямоуrольник, 1159
Отслеживаемый дескриптор, 238
Отслеживающая ссылка, 264
Отступ, 75
Очередизованное сообщение, 816
Очередь, 647
сообщений, 805
Очищаемая динамическая память, 240
n
Панель инструментов, 40; 804
Панель меню, 804
Параметр, 272
типа, 331
Переrрузка оператора, 456
Переrрузка функций, 327
Передача
по значению, 280
по ссылке, 287
по указателю, 281
Переключатель трассировки, 792
Переменная, 73; 77
Переменнаячлен, 377
Переменная элемента управления, 1079
Перечисление, 87; 137
Перечислитель, 87
Перо, 955
Подкачка сообщений, 816
Позднее связывание, 1184
Поле initonly, 440
Полиморфизм, 581
Поток, 74; 838
Потоковый итератор, 712
Предикат, 650
Представление, 873
ресурсов, 39
Префикс, 807
Приведение, 98; 105
Приложение SDI, 872
Приоритет операторов, 103
Проводник решений, 39
Проrрамма С++ jCLI, 30
Проrрамма CLR, 30
Продолжительность хранения, 116
Проект, 41
Прокрутка, 1018
Промежуточный язык Microsoft, 31
Пространство имен, 54; 71; 123
Прототип функции, 275
Р
Рамкаrруппы,1064
PaHr массива, 249
Раннее связывание, 1184
Распаковка, 128; 599
Растровая операция, 1109
Реализация, 332
класса, 503
шаблона, 486
Реrистрация класса окна, 814
Редактор, 38
Редактор ресурсов, 877
Режим отображения, 947
Режим рисования, 985
Рекурсия, 304
Ресурс, 877; 906
Решение, 42
Родительская сборка, 610
Родная проrрамма С++, 30
с
Сборка, 54; 61 О
Сборка \"мусора"\, 237
Свойство, 427; 907
индексированное, 427
СlGUIярное,427
только для записи, 427
только для чтения, 427
Связный список, 375
Семантическая ошибка, 752
Сериализация, 1131; 1161
Сиrнатура,328
Символ препроцессора
NDEBUG, 764
1204 Visual С++ 2010. Полный курс
Символьная строка, 197
Символьный массив, 197
Символ
DEBUG, 766
Синтаксическая ошибка, 752
Система Intellisense, 45
Слушатель, 791
Событие, 36; 615; 624
Создание экземпляра, 379
Сокет Windows, 1189
Сообщение
WМDESTROY, 825
WМLBUTTONDOWN, 963
WМLBUTTONU 964
WMMOUSEMOVE, 963
WМPAINT, 945
WМQUIT, 825
Сообщения Windows, 805
Составной оператор, 75
Спецификатор
видимости, 61 О
static, 123
Спецификации CLR, 30
Список инициализации, 394
Ссылка, 229
Стандартная библиотека, 71
С++,38
шаблонов, 645
Стандартный конструктор, 389
Стандарт ЕСМА, 30
Стандарт ISO, 30
Стандарт ISO jIEC, 30; 34
Стартовая страница, 39
Статическая переменная, 123
Статическая переменнаячлен, 41 О
Статический конструктор, 442
Стек, 443;648
Структура, 368
CrtMemState, 780
RECT, 374
WNDCLASSEX, 811
Т
Таблица истинности, 109
Текущая позиция, 951
Тело функции, 72; 273
Технолоrия Windows Forms, 925
Тип
bool, 84
BOOL, 968
char, 82
double, 85
errnot, 233
f1oat, 85
HBRUSH, 813
HINSTANCE,810
int, 80
long double, 85
long long, 81
long, 81
LONG, 374
short, 81
sizet, 213
valist, 296
wchart, 82
данных, 80
переменной, 73
Точка
останова, 756
трассировки, 758
у
Указатель, 204
this, 403
Упаковка, 128;599
Управляемый код С++, 32
Управляющая последовательность, 49; 91
Условный оператор, 158
Установка наблюдения, 761
Утверждение, 764
Утечка памяти, 226; 299; 779
ф
Файл заrоловка, 71
Финализатор, 628
Флаr, 140; 812
Форма, 883; 925
Windows, 834
Функтор, 474
Функциональная нотация, 80
Функция, 64; 272
CrtDbgFlagO, 782
CrtSetDbgFlagO, 782
CrtSetReportFileO, 783; 784
CrtSetReportModeO, 783; 784
AddSegmentO, 983
AfxMessageBoxO, 975
appendO,526
ЛrсО, 954
assertO, 764; 794
assign (), 662
atO,525; 660
averageO, 284
backO, 675
BeginPaintO, 823
Предметный указатель 1205
BinarySearchO, 246
BitBltO, 851
cstrO, 528
capacityO, 655
сеНО, 1154
cleaI'(), 660
ClearO, 242
COInpareO, 260
CompareToO,353; 605
СоруТоО,364
CreatePenO, 955
CreateWindowO, 814; 815
CreateWindowExO, 815
dataO, 528
DefWindowProcO, 806; 823
DispatchMessageO, 817; 819
DHMainO, 1186
DrawTextO,824
EHipseO, 953
eInptyO,655
EnableVisualStylesO, 930
EndsWithO, 261
EqualRect(),374
exitO,341
fillO, 721
findO,532; 722
findfirstnotofO, 535
findfirstofO, 533
findlastnotofO, 535
findlastofO, 533
freopensO, 784; 786
frontO, 675
g et O,427
GetCaptureO, 992
GetClientRectO, 824; 850
GetCursorPositionO, 1035
GetDe\!iceCapsO, 1086
GetDOCUInentO, 892; 950
getlineO, 198; 531
GetMessage(), 817
GetNumericValueO,363
GetSelectedRadioButtonID(), 1093
GetStockObjectO, 813
GetTypeO, 142
IndentO, 792
IndexOfO, 261
IndexOfAny(), 263
Inf1ateRectO,374
InitializeComponentO, 928
InitInstanceO, 893
insertO, 526
Insert(), 260
inserter(), 717
InvalidateRectO, 968
isalphaO,343
isdigitO, 343
IsDig it O,363
islower(), 343
IsLower(), 183
isu pper(), 343
IsUpperO, 183
]oi n O,256
LastIndexOf(), 262
LineToO, 952
LoadStdProfileSettings(), 894
lockO, 860
mainO, 64
makepairO, 700
maxsizeO, 655
NextO, 243
OnCancelO, 1069
OnDraw(), 949
ОПОКО, 1069
operatorO О, 650
PadLeft(), 259
PadRightO, 259
ParseO, 136
PolyLineO, 979
pushbackO, 526; 653
rbeg in O,654
ReadO, 135; 183
ReadKeyO, 135; 183
ReadLineO, 135
RectangleO, 979
RectVisibleO, 1013
RegisterClass(), 814
RegisterClassEx(), 814
ReleaseCaptureO, 992
reInoveifO, 680
rendO,654
replaceO, 528; 721
Replace(),260
reserveO, 653
resizeO, 656
RunO, 894
SelectObjectO, 956
SelectStockObjectO, 959
SerializeO, 1133; 1143
setO, 427
SetCaptureO, 992
SetDefaultRadioButtonO 1093
SetPixelO, 850
SetRegistryKeyO,894
SetROP20, 985
sizeO, 655
sortO, 667
1206 Visual С++ 2010. Полный курс
SortO,244
spliceO, 679
StartsWithO,261
strcat(), 232
strcmpO, 234
strcpy(), 234
strcpysO, 234; 351; 449
strlen(), 231
strncatO, 233
strspn(), 235
strstrO, 235
substrO, 526; 540
swapO, 527
ToLowerO, 183; 260
ToStringO, 258; 422
toupperO, 151
ToUpperO, 183; 260
transformO, 722
TranslateMessageO, 817
TrimO, 258
TriInEndO, 259
TrimStart (), 259
trylockO, 861
UnindentO, 792
uniqueO, 678
unlockO, 861
UpdateWindow(),816
wcscat(),233
wcscpy(), 234
wcscpys(), 234
wcslen(), 231
wcsncatO,233
wcsspn(),235
WindowProcO, 822; 828
WinMain(),809
WndProcO,805
WriteO, 131; 183
WriteLineO, 131
wstrcmp(),235
Функция..член, 383
Функция доступа, 427
ц
Цикл, 165
dowhile, 177
for each, 185
for, 166
while, 175
Цикл сообщений, 816
ч
Чистая виртуальная функция, 585
Член класса, 380
ш
Шаблон
function<>, 731
документа, 874
класса, 231; 486
функции, 330
3
Экземпляр шаблона класса, 486
Экранные координаты, 1084
Экспорт, 01192
Элемент
данных, 191
массива, 191
управления, 1062
TextBox, 1125
я
Язык С++ jCLI, 34
Создание реальноrо приложения по
мере изучения языка С++
Следуя подробному и доступному руководству автора
и выполняя упражнения, вы быстро станете квалифицированным
проrраммистом на языке С++. Полностью переделанная под
выпуск 201 О rода, эта книrа ознакомит вас с новейшей средой
разработки Visual С++ и научит создавать с ее помощью реальные
приложения. С этой книrой вы на верном пути к мастерству
написания приложений в обеих версиях языка С++ и успехам
проrраммирования.
В этой книrе рассматриваются следующие темы
· Изучение основных тем проrраммирования с использованием
обеих технолоrий языка С++, предоставляемых средой
разработки Visual С++ 201 О
· Общие технолоrии поиска ошибок в проrраммах на С++
и объяснение общих принципов отладки
· Обсуждение структуры и основных элементов, присутствующих
в каждом приложении Windows
· Демонстрация основ разработки приложений Windows
с использованием классов Мiсrоsоft Foundation Classes
· Руководство по разработке и созданию приложений Windows как
на языке С++, так и C++jCLI
· Множество практических примеров и упражнений, которые
помоryт получить навыки проrраммирования
Айвор Хортон один из выдающихся авторов книr о языках
проrраммирования Java, С и С++. Он широко известен своим
,( уникальным стилем изложения, который доступен как новичкам,
так и опытным проrраммистам. Хортон является также частным
системным консультантом, а ранее он более 25 лет преподавал
проrраммирование.
Книrи этой серии задуманы и написаны для практикующих проrраммистов,
поэтому отражают реальные потребности проrраммистов, разработчиков
и специалистов в области информационных технолоrий. Конкретные
и адекватные, они рассчитаны на повседневное использование
профессионалами. В этих книrах описано большое количество примеров,
реальных решений и экспертных оценок в контексте мноrих технолоrий,
и все они призваны помочь проrраммистам постоянно совершенствовать
свою работу.
Катеrория: языки проrраммирования
Предмет рассмотрения: язык С++
Уровень: для пользователей средней и высокой квалификации
Ш ,
www.dialektika.com
Wrох тм
Ап Imprint of
WILEY
ISBN 97858459 1б983
11010