Текст
                    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