SNK Software
Web Studio Монополия Metaproducts Утилиты Игры
Монополию Web Studio Библиотека
Вебмастер Дельфи Работа на ПК Самоучитель
Для PHP Для Delphi
Веб-дизайн Программирование Компьютеры Девайсы Заметки
SNK Software Индустрия hardware Индустрия software
О студии Портфолио Сопровождение сайтов

Новые материалы

Девайсы:
Сравнительный обзор Nokia Lumia 920 и HTC 8X
Девайсы:
Обзор Nokia Lumia 820 – смартфона на WP8
Вебмастеру:
Настройка Apache, PHP и MySQL для Linux-VPS
Вебмастеру:
VPS на домашнем ПК: настройка сети в VM VirtualBox и Debian
Вебмастеру:
VPS на домашнем ПК: устанавливаем Linux Debian 6
Вебмастеру:
VPS на домашнем ПК: установка VM VirtualBox
Работа на компьютере:
Иные возможности текстового процессора Word
Работа на компьютере:
Вставка объектов
Работа на компьютере:
Таблицы в Word
Работа на компьютере:
Печать и сохранение документов
Работа на компьютере:
Сноски, колонтитулы, оглавление и указатели в Word

Стандартные диалоги и редактор RTF

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

Диалоги работы с файлами

Все диалоги собраны на обзей закладке Dialogs палитры компонентов. Первым на ней является компонент OpenDialog, представляющий собой оболочку для функций Windows API, связанных с диалогом выбора файлов. Диалог, вызываемый при помощи компонента OpenDialog, используется для выбора (открытия) файла. Для операции сохранения следует использовать другой компонент - SaveDialog. Он показывает, в общем-то, тот же самый диалог, но в режиме сохранения.

Еще 2 компонента - OpenPictureDialog и SavePictureDialog являются частным случаем стандартных диалогов открыть-сохранить, но предназначенными для работы с графическими файлами. Визуально они отличаются от универсальных диалогов тем, что имеют область просмотра изображения. С точки зрения их использования в программе они ничем не отличаются от OpenDialog и SaveDialog.

Вообще, следует отметить, что базовым классом для всех диалогов выступает TCommonDialog, а для всех диалогов работы с файлами - класс TOpenDialog. Итак, рассмотрим общие свойства файловых диалогов на основе OpenDialog, для чего обратимся к таблице 14.1.

Таблица 14.1. Свойства файловых диалогов
СвойствоТипОписание
DefaultExtStringОпределяет расширение по умолчанию, которое будет добавляться к файлу, если пользователь не указал расширение явно
FileNameStringОпределяет имя файла, включая его путь
FilesTStringsСодержит список выбранных файлов, если таковых несколько (задается свойством Options путем включения флага ofAllowMultiSelect)
FilterStringОпределяет строку, содержащую фильтр типов файлов
FilterIndexIntegerОпределяет, какой из вариантов фильтра должен быть выбран по умолчанию
InitialDirStringОпределяет каталог, который будет отображен изначально
OptionsTOpenOptionsОпределяет вид особенности поведения диалога
OptionsExTOpenOptionsExОпределяет дополнительные опции, актуальные для Windows Me, 2000 и более новых версий
TitleStringОпределяет текст, который будет отображен в заголовке диалога

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

Редактор свойства Filter с данными по умолчанию для OpenPictureDialog
Рис. 14.1. Редактор свойства Filter с данными по умолчанию для OpenPictureDialog

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

OpenDialog1.Filter:='Все файлы|*.*|Текстовые файлы|*.txt';

Здесь мы определили 2 варианта фильтра - для отображения всех файлов (шаблон *.*) и для отображения только текстовых файлов (шаблон *.txt). Если один шаблон включает в себя несколько разных расширений, то они перечисляются через точку с запятой:

OpenDialog1.Filter:='Все файлы|*.*|Файлы Delphi|*.pas;*.dpr';

Еще одно важное свойство файловых диалогов - это Options. Именно при помощи множества флагов этого свойства определяют особенности поведения диалога, а также устанавливают отображение дополнительных элементов его интерфейса. Значения всех актуальных флагов приведены в таблице 14.2.

Таблица 14.2. Значения флагов свойства Options компонента OpenDialog
ФлагОписание
ofReadOnly"Делает опцию ""только чтение"" включенной по умолчанию"
ofOverwritePromptУказывает на необходимость вывода предупреждающего сообщения, если файл с указанным именем уже существует (для диалогов сохранения)
ofHideReadOnly"Удаляет из диалога переключатель ""только чтение"""
ofNoChangeDirВозвращает исходный путь после закрытия диалога
ofShowHelpОтображает кнопку справки на диалоге
ofNoValidateОтключает проверку на недопустимые символы в имени файла
ofAllowMultiSelectПозволяет пользователю выбрать несколько файлов одновременно
ofExtensionDifferentЭтот флаг включается автоматически, когда выбранный файл имеет расширение, отличное от указанного в свойстве DefaultExt
ofPathMustExistВыдает сообщение об ошибке, если указанного пользователем пути не существует
ofFileMustExistВыдает сообщение об ошибке, если указанного пользователем файла не существует (для диалогов открытия файла)
ofCreatePromptВыдает сообщение с предупреждением о необходимости создания файла, если указанного пользователем файла не существует
ofShareAwareПозволяет игнорировать ошибки доступа
ofNoReadOnlyReturnВыдает сообщение об ошибке, если пользователь выберет файл, доступный только для чтения
ofNoTestFileCreateОтключает проверку на права доступа к сетевым ресурсам
ofNoDereferenceLinksОтключает обработку ярлыков. Т.е. если этот флаг не установлен, то выбор ярлыка приведет к выбору файла, на который ярлык ссылается, в противном случае будет выбран сам файл ярлыка (lnk)
ofEnableSizingПозволяет пользователю изменять размеры диалогового окна (не действует в Windows 95 и Windows NT 4.0)
ofDontAddToRecentНе позволяет заносить выбранные файлы в список недавно открытых документов
ofForceShowHiddenПозволяет пользователю увидеть скрытые файлы

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

Что касается второго свойства - OptionsEx, то для него пока что доступна единственная настройка - ofExNoPlacesBar, позволяющая отключить боковую панель быстрого доступа к основным разделам компьютера, поддерживаемую в Windows Me, 2000, XP и 2003 Server.

Еще проще дела обстоят с методами. Кроме стандартных методов, доставшихся диалогам от класса TComponent, у него всего один собственный метод - Execute. Именно при помощи этого метода вызываются диалоги в коде программы. Метод Execute возвращает булевское значение - истину, если пользователь нажмет ОК, или ложь, если пользователь выберет отмену. Соответственно, типичный вызов диалога в программе выглядит следующим образом:

if OpenDialog1.Execute then begin ... end;

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

procedure TForm1.OpenFileButtonClick(Sender: TObject); begin if not OpenDialog1.Execute then exit; Form1.Caption:=OpenDialog1.FileName; Memo1.Lines.LoadFromFile(OpenDialog1.FileName); end;

Такой подход позволяет избежать лишнего блока begin-end, поскольку если диалог не выполнен (т.е. пользователь передумал открывать файл), то можно сразу же выйти из подпрограммы, предназначенной для обработки загрузки файла.

Диалоги печати

Для взаимодействия со средствами Windows по подготовке к печати в VCL предусмотрено 3 компонента: PrintDialog, PrintSetupDialog и PageSetupDialog. Первый из них реализует доступ к стандартному диалогу печати, второй - к диалогу настройки печати, а третий - к диалогу параметров страницы.

Свойства, которыми обладают данные компоненты, в основном, предназначены для непосредственного обмена информацией между приложением и окном диалога, вернее, даже с его составными элементами. Так, для диалога печати (PrintDialog, рис. 14.2), это будут свойства, отвечающие за диапазон печати и число копий. В частности, за число копий отвечает свойство Copies, за тип диапазона, который может принимать одно из 3 значений - все (prAllPages), указанные страницы (prPageNums), или выделенный фрагмент (prSelection) - отвечает свойство PrintRange. При этом, если выбран диапазон по страницам, то его границы будут определяться свойствами FromPage и ToPage. Ограничить границы выбора этих значений пользователем во время его работы с диалогом можно при помощи свойств MaxPage и MinPage.

Стандартный диалог печати Windows
Рис. 14.2. Диалог печати

Как и у большинства других диалогов, диалог печати имеет свойство Options, при помощи которого можно настроить вид и поведение диалога. Приведем описание значений его флагов:

Важно осознавать, что сам по себе диалог печати ничего не делает. Фактически он лишь предоставляет пользовательский интерфейс для выбора диапазона печати и указания числа копий. Дальнейшую обработку полученной информации должно выполнять само приложение, для чего используется объект Printer (см. главу 9). В частности, объект "принтер" имеет свойство Copies, обозначающее количество копий, так что с этой точки зрения все просто, надо лишь не забыть включить модуль Printers в блок Uses, дальше останется лишь присвоить соответствующее свойство:

Printer.Copies:=PrintDialog1.Copies;

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

Еще один диалог, имеющий отношение к печати - это диалог настройки параметров страницы, PageSetupDialog, появившийся в Delphi 7. С его помощью пользователь может указать ориентацию страницы (портретную или ландшафтную), установить размер листа и источник подачи, а также задать поля. Иначе говоря, этот диалог предоставляет больше возможностей для выбора параметров страницы, чем PrinterSetupDialog. Впрочем, при помощи свойства Options можно отключить те или иные настройки, как-то выбор размера листа, его ориентации, полей, или устройства подачи:

Что касается тех параметров, которые не назначаются принтеру автоматически (в частности, поля), то как раз они и представлены свойствами компонента PageSetupDialog. Это свойства MarginLeft, MarginTop, MarginRight и MarginBottom, отвечающие за поля слева, сверху, справа и снизу, соответственно. При этом существует еще и возможность ограничить минимальный размер полей, что делается путем включения флага psoDefaultMinMargins, а сами значения задаются при помощи свойств MinMarginLeft, MinMarginTop, MinMarginRight и MinMarginBottom. Типом данных для всех этих свойств является целое, однако вопрос состоит в том, что за единицы измерения они представляют. За этот вопрос отвечает свойство Units, которое может принимать 3 значения:

Эти же единицы применяются не только к полям, но и еще к 2 свойствам - PageWidth и PageHeight, представляющими собой размеры страницы в ширину и в высоту. Так, если свойство Units установлено в pmMillimeters, а в качестве носителя пользователь выберет лист размером A4, эти свойства примут значения 21000 и 29700, т.е. 210 мм и 297 мм.

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

Диалог шрифта

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

Основным свойством этого диалога можно назвать свойство Font, при помощи которого данный диалог обменивается информацией о шрифте с программой. С типом TFont мы уже отчасти знакомы, в частности, с его свойствами Color, Name, Size, и Style. Так же имеются свойства Charset и Height, определяющие набор символов и высоту, соответственно. При этом значение высоты напрямую зависит от размера шрифта, задаваемого свойством Size. Вместе с тем, для диалога параметров шрифта важно именно свойство Size, поскольку в самом диалоге пользователь указывает именно это свойство. Что касается свойства Charset, то его возможные значения определяются выбранным шрифтом. Для шрифтов OpenType, основанных на Unicode и используемых в новейших версиях Windows, допустимо наличие множества наборов символов, для обычных же TrueType шрифтов наборов бывает не более 2, обычно это основной латинский (127 символов ANSI) и один из дополнительных - греческий, восточноевропейский, кириллический и т.д. Значение набора символов задается при помощи констант, определенных в модуле Graphics, или непосредственно числами. Так, для ANSI это будет 0, для символьных шрифтов - 2, а для кириллических - 204.

Подобно другим диалогам, диалог параметров шрифта имеет свойство Options. В данном случае оно имеет следующий набор флагов:

Чаще всего диалог шрифта используют совместно с текстовым редактором. Допустим, если у нас имеется приложение, состоящее из многострочного редактора (компонент Memo) и предназначенное для просмотра текстовых файлов. Как минимум, такое приложение будет иметь 2 визуальных компонента - редактор и кнопку для вызова диалога открытия файла, а так же 1 невизуальный, представляющий собой собственно диалог открытья файла. Мы можем предоставить пользователю возможность изменять шрифт, которым отображаются файлы в редакторе, для чего нам потребуется компонент FontDialog и кнопка для обращения к этому диалогу. Не помешает добавить еще одну кнопку - для выхода из программы. Таким образом, мы получим приложение, вид которого в Delphi IDE будет примерно таким, как показано на рис. 14.3.

Приложение для просмотра текстовых файлов в Delphi
Рис. 14.3. Приложение для просмотра текстовых файлов

Из свойств, назначенных использованным компонентам, отметим лишь свойства Caption для всех 3 кнопок, а так же свойство Filter для диалога открытия файла. Пусть оно будет определено следующим образом:

Текстовые|*.txt;*.bat;*.ini;*.pas;*.dpr|Все|*.*

В данном случае фильтр будет иметь 2 варианта: "текстовые" (для файлов с расширениями txt, bat, ini, pas и dpr), а так же "все", для файлов с любым расширением. После этого остается добавить обработчики события OnClick для всех 3 кнопок, в результате чего мы получим программу, код которой приведен в листинге 14.1.

Листинг 14.1. Исходный код программы просмотра файлов с выбором шрифта

unit Unit1; interface uses Windows, Classes, Controls, Forms, Dialogs, StdCtrls; type TForm1 = class(TForm) Memo1: TMemo; Button1: TButton; Button2: TButton; Button3: TButton; OpenDialog1: TOpenDialog; FontDialog1: TFontDialog; procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); procedure Button3Click(Sender: TObject); var Form1: TForm1; implementation {$R *.dfm} procedure TForm1.Button1Click(Sender: TObject); begin if not OpenDialog1.Execute then exit; Memo1.Lines.LoadFromFile(OpenDialog1.FileName); end; procedure TForm1.Button2Click(Sender: TObject); begin if not FontDialog1.Execute then exit; Memo1.Font:=FontDialog1.Font; end; procedure TForm1.Button3Click(Sender: TObject); begin close; end; end.

Как видно из процедуры, обрабатывающей нажатие кнопки "Шрифт", для того, чтобы изменить все параметры шрифта сразу, достаточно присвоить свойству Font компонента-редактора значение одноименного свойства диалога. Если бы нам требовалось присвоить лишь часть атрибутов (например, только название гарнитуры и размер кегля), то следовало бы присваивать соответствующие свойства типа TFont по отдельности:

Memo1.Font.Name:=FontDialog1.Font.Name; Memo1.Font.Size:=FontDialog1.Font.Size;

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

Диалог цвета

Диалог параметров шрифта позволяет установить любые его параметры, включая цвет. Но он не предназначен для того, чтобы изменять цвет заднего плана. Для этих целей следует использовать специальный диалог выбора цвета, в VCL он представлен компонентом ColorDialog. С точки зрения свойств он аналогичен диалогу параметров, с той лишь разницей, что вместо свойства Font, определяющем шрифт, у него имеется свойство Color, определяющее цвета.

Что касается типичного для диалогов свойства Options, то оно у ColorDialog имеет всего 5 флагов:

По умолчанию предлагается палитра, состоящая всего лишь из 48 цветов, кроме того, еще 16 цветов могут быть заданы разработчиком при помощи свойства CustomColors. Это свойство имеет тип TStrings и должно состоять из строк типа имя=значение, где в качестве имени используется слово Color и буква, от a до p по алфавиту (итого 16 вариантов максимум), а в качестве значения - цвет, заданный при помощи RGB-триплета:

ColorA=00CCDD ColorB=DFCA72 ... ColorP=234567

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

Диалог выбора цвета в свернутом (слева) и в развернутом (справа) режиме
Рис. 14.4. Диалог выбора цвета в свернутом (слева) и в развернутом (справа) режиме

Чтобы проиллюстрировать работу этого диалога, добавим к приведенному в листинге 14.1 приложению компонент ColorDialog и еще одну кнопку, которую назовем "Фон". При этом код для события OnClick получится следующим:

procedure TForm1.Button4Click(Sender: TObject); begin ColorDialog1.Color:=Memo1.Color; if not ColorDialog1.Execute then exit; Memo1.Color:=ColorDialog1.Color; end;

Здесь вначале диалогу присваивается цвет фона редактора, после чего диалог вызывается, и, в случае положительного срабатывания, фону редактора назначается новый цвет. Модернизированное таким образом приложение можно посмотреть в каталоге Demo\Part3\Dlg2.

Редактор RTF

Рассмотренные выше диалоги для работы со шрифтами и с цветом представляют наибольшую ценность для форматирования текста. Для этих целей в Windows имеется специальный компонент - редактор форматированного текста, представленный в библиотеке VCL компонентом RichEdit, относящимся к компонентам Win32. Этот компонент фактически делает то же, что и все остальные компоненты этой группы - предоставляет простой и удобный доступ к одному из стандартных системных элементов управления, причем в данном случае таковой представлен отдельным системным файлом - richedit32.dll.

В иерархии классов VCL компонент RichEdit является наследником класса TCustomMemo, на основе которого построен "обычный" многострочный редактор. Вместе с тем, этот компонент обладает рядом свойств и методов, позволяющих, с одной стороны, форматировать текст, а с другой - обеспечивающих ряд сервисных функций, вроде поиска совпадений или вывода на печать. Всего в распоряжении редактора форматированного текста имеется свыше 100 свойств. Впрочем, непосредственно у класса TCustomRichEdit, на основе которого и создан рассматриваемый компонент, определено только 12, при этом ряд из них - HideSelection, Lines, SelLength, SelStart и SelText являются лишь переопределение одноименных свойств уже рассмотренных нами разновидностей редакторов. В итоге нам остается рассмотреть не так уж и много новых свойств этого компонента - см. таблицу 14.3.

Таблица 14.3. Собственные свойства редактора RTF
СвойствоТипОписание
DefAttributesTTextAttributesОпределяет характеристики текста по умолчанию
DefaultConverterTConversionClassОпределяет класс того объекта, который будет использоваться для преобразования формата текста. Автоматически используются преобразователи простого текста из и в RTF
HideScrollBarsBooleanОпределяет, должны ли полосы прокрутки появляться только при необходимости
PageRectTRectОпределяет размеры страницы (в пикселях), которые будут использоваться для вывода на печать
ParagraphTParaAttributesОпределяет параметры форматирования абзаца
PlainTextBooleanОпределяет тип текста – форматированный (false) или простой (true)
SelAttributesTTextAttributesОпределяет характеристики текста выделенного фрагмента

Свойство DefAttributes типа TTextAttributes, задающее параметры шрифта, по своей сути похоже на свойство Font, имеющееся у многих других компонент, но доступно только во время выполнения. В то же время, в момент инициализации приложения, именно на основе параметров, указанных для свойства Font, и назначаются значения для свойства DefAttributes. Что касается типа TTextAttributes, то он имеет одно важное отличие от типа TFont, а именно - свойство ConsistentAttributes, которое показывает отличия выбранного фрагмента текста по отношению к остальному тексту. Впрочем, такая информация является более полезной для другого свойства редактора, а именно - для SelAttributes. Именно это свойство позволяет изменять параметры выделенной части текста, или же той его части, где находится каретка ввода. Поскольку текст может быть выделенным только в работающем приложении, то и это свойство доступно только во время выполнения.

Чтобы лучше представить себе суть свойств DefAttributes и SelAttributes, создадим небольшое приложение, которое выводило бы информацию о состоянии обоих этих свойств для компонента RichEdit. Для этого, помимо самого компонента RichEdit, нам понадобятся так же 2 компонента типа Memo - для вывода информации, а так же кнопка, по нажатию на которую интересующие нас сведения будут выводиться.

Кроме того, нам понадобится готовый файл в формате RTF, который будет содержать предварительно отформатированный различными способами текст. Подготовить его можно в любом текстовом процессоре, включая Word или WordPad, надо только будет при сохранении указать соответствующий формат файла. Для загрузки такого файла разместим на форме еще одну кнопку. В результате у нас получится форма с редактором RTF, 2 кнопками и 2 обычными редакторами (рис. 14.5).

Приложение для тестирования DefAttributes и SelAttributes
Рис. 14.5. Приложение для тестирования DefAttributes и SelAttributes

Для обработчика события OnClick кнопки "Загрузить" достаточно будет написать следующий код:

RichEdit1.Lines.LoadFromFile('simple.rtf');

Здесь подразумевается, что файл с примером текста называется simple.rtf и расположен в том же каталоге, что и исполняемый файл приложения.

Что касается кода, выводящего информацию о значениях исследуемых свойств, то, учитывая, что нам оба раза придется выводить сведения об объекте одного и того же типа - TtextAttribuitrs, то имеет смысл предварительно создать процедуру, которая могла бы получить нужные данные на обработку и вывести их в указанное место. Назовем ее PrintAttrInfo, и определим как публичную процедуру класса TForm1:

type TForm1 = class(TForm) ... public procedure PrintAttrInfo(a: TTextAttributes; m: TMemo); end;

Реализация же этой процедуры будет заключаться в последовательном добавлении в указанный в качестве 2-го аргумента редактор строк со значениями атрибутов:

procedure TForm1.PrintAttrInfo(a: TTextAttributes; m: TMemo); var s: string; begin m.Lines.Clear; //предварительно очищаем содержимое m.Lines.Add('Charset: '+IntToStr(a.Charset)); //набор символов m.Lines.Add('Colour: '+IntToStr(a.Color)); //цвет m.Lines.Add('Name: '+a.Name); //гарнитура { для текстового вывода информации о типе шрифта определим, какой из 3 возможных вариантов используется } case a.Pitch of fpDefault: m.Lines.Add('Pitch: fpDefault'); fpVariable: m.Lines.Add('Pitch: fpVariable'); fpFixed: m.Lines.Add('Pitch: fpFixed'); end; m.Lines.Add('Size: '+IntToStr(a.Size)); //размер { поскольку шрифт может одновременно иметь сразу несколько признаков свойства Style, то проверим их все } if fsBold in a.Style then s:='fsBold '; if fsItalic in a.Style then s:=s+'fsItalic '; if fsUnderline in a.Style then s:=s+'fsUnderline '; if fsStrikeOut in a.Style then s:=s+'fsStrikeOut '; m.Lines.Add('Style: [ '+s+']'); end;

Наконец, для кнопки "Показать" остается написать 2 вызова определенной нами процедуры PrintAttrInfo:

PrintAttrInfo(RichEdit1.DefAttributes, Memo1); PrintAttrInfo(RichEdit1.SelAttributes, Memo2);

Если теперь запустить это приложение и нажать на кнопку "Показать", то можно будет увидеть, что для обоих свойств отображаются одни и те же значения. Если же загрузить файл с форматированным текстом, то значения свойства SelAttributes изменятся. Более того, если в загруженном файле применены различные шрифты или стили оформления, то, изменяя текущую позицию каретки, можно будет видеть текущие атрибуты шрифта. В то же время, если изменить значение свойства Font, то изменятся не только значение свойства DefAttributes, но и параметры всего текста. Чтобы в этом убедиться, можно добавить еще одну кнопку, которая будет вызывать компонент FontDialog и написать для нее следующий код:

FontDialog1.Font:=RichEdit1.Font; if FontDialog1.Execute then RichEdit1.Font:=FontDialog1.Font;

Таким образом, после обращения к диалогу шрифта и назначению новых данных свойству Font, изменится оформление всего текста. Готовый пример можно найти в каталоге Demo\Part3\Rich1.

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

При работе с редактором форматированного текста следует учитывать, что в разных версиях Windows поддерживаются разные версии редактора. Так, в Windows 95 это версия 1.0, в Windows 98 - 2.0, а в Windows 2000 и XP - 3.0. Вместе с тем, если в системе с Windows 95 установлен Office 97 или MSIE 4.0, то компонент будет обновлен до версии 2.0. Разумеется, новые версии имеют обратную совместимость с предыдущими, однако на практике программа, использующая этот компонент, и запущенная под Windows 95 будет вести себя не совсем так, как та же программа, работающая в Windows XP. В основном, это связано с ошибками, допущенными при разработке первой редакции этого элемента управления для Windows 95. И хотя вряд ли вы найдете сейчас компьютер, работающий под этой версией ОС, да еще и без установленного Office, требования к обратной совместимости заставляют Borland от версии к версии Delphi оставлять свой компонент RichEdit привязанным к версии 1.0 данного элемента Windows. Поэтому, если вам придется на практике разрабатывать приложение, построенное вокруг этого компонента (т.е. приложение, в котором текстовый редактор является одной из важнейших составляющих), то используете сторонние компоненты, ориентированные на более новые версии этого системного элемента, например, RichEdit98. В частности, помимо более предсказуемого поведения и отсутствия специфических "особенностей", новые версии имеют более широкую функциональность, например, возможность выравнивания текста по ширине, а так же куда большие возможности оформления. В то же время, если RichEdit нужен лишь как обычный блокнот с готовыми функциями поиска и печати, то функциональности стандартного компонента будет вполне достаточно.

Пример текстового редактора

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

Но прежде, чем браться за разработку приложения, хотелось бы остановиться на таком аспекте, как правила именования компонент. По умолчанию, как мы знаем, Delphi присваивает им имена, убирая первую букву (T) и добавляя порядковый номер (1,2,3). Но на практике это не очень удобно, поскольку в достаточно большой программе трудно будет понять, что такое Button22 или CheckBox17. По этой причине существуют некие общие правила наименования компонентов. Сводятся они к тому, что используется 2-3-4 буквенный префикс (или, по желанию - суффикс), идентифицирующий принадлежность объекта к классу, в сочетании со словом, отражающем суть данного объекта. Например, текстовое поле (класс TEdit), предназначенное для ввода имени пользователя, согласно таким правилам может называться, скажем, EdtUserName или UserNameEd, а кнопка (TButton) запуска чего-либо - BtnStart или StartBtn.

Итак, для текстового редактора нам понадобится создать новое приложение, после чего разместить на его форме ряд компонент, необходимых для его работы. Прежде всего, это главное меню (MainMenu) и, разумеется, сам RichEdit. Очевидно, что нам пригодятся и стандартные диалоги, включая диалоги для работы с файлами (OpenDialog и CloseDialog), диалоги печати (PrintDialog и PrintSetupDialog), а так же диалоги для работы с цветом и шрифтами (FontDialog и ColorDialog). Теперь для компонента RichEdit установим свойство Align в AlClient, чтобы область редактора заняла все свободное место на форме (рис. 14.6).

Окно текстового редактора в начале разработки в Delphi
Рис. 14.6. Окно текстового редактора в начале разработки

Разместив нужные нам компоненты на форме, установим имена следующим образом:

Теперь можно заняться обустройством главного меню. Предусмотрим в нем 3 раздела: Файл, Правка и Формат. Для этого откроем редактор меню (двойным щелчком по компоненту MainMenu) и создадим эти 3 основных пункта главного меню. Они автоматически получат названия N1, N2 и N3, но изменять их мы не будем по той простой причине, что эти пункты - лишь заголовки для самих меню, и в программном коде обращаться к ним нам не придется.

Далее заполним меню "Файл", создав в нем пункты "Открыть…", "Сохранить…", "Печать…", "Принтер…" и "Выход". Назовем их OpenFileM, SaveFileM, PrintFileM, PrintSetupFileM и ExitFileM. Общие суффиксы FileM в будущем, будут нам подсказывать, что мы имеем дело с меню файловых операций. А многоточия после слова в подписи первых 4 пунктов меню будут подсказывать пользователю, что данные пункты выполняются не сами по себе, а вызывают диалоговое окно. Внешний вид конструктора меню на данном этапе будет таким, как показано на рис. 14.7. Само меню в программе будет выглядеть практически так же, что может навести нас на мысль о необходимости добавить разделители перед пунктами "Печать" и "Выход". Для этого следует выбрать в конструкторе нужный пункт и нажать клавишу Ins на клавиатуре, после чего в появившемся новом пункте в качестве значения свойства Caption указать символ "-".

Следующим пунктом у нас идет меню "Правка". Разместим в нем стандартные пункты "Отменить", "Вырезать", "Копировать", "Вставить" и "Выделить все", присвоив им имена UndoEdM, CutEdM, CopyEdM, InsertEdM и SelAllEdM, соответственно. При этом после отмены не помешает поместить разделитель. Наконец, в последнюю группу - "Формат" - внесем пункты "Шрифт…" и "Цвет…", назвав их FontFmtM и ColorFmtM.

Теперь подготовим к работе файловые диалоги. Поскольку мы имеем дело с текстовым редактором типа RTF, то основным типом файла будет как раз RTF. Тем не менее, не помешает предусмотреть возможность открытия файлов другого типа, в частности обычных текстовых (TXT). Таким образом, для свойства Filter в инспекторе объекта следует указать следующее значение:

Файлы RTF|*.rtf|Текстовые файлы ASCII|*.txt|Все файлы|*.*

Что касается свойства DefaultExt, то для диалога сохранения было бы целесообразным указать rtf.

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

if OpenDlg.Execute then RichEd.Lines.LoadFromFile(OpenDlg.FileName);

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

if if OpenDlg.FileName<>'' then SaveDlg.FileName:=OpenDlg.FileName; if SaveDlg.Execute then RichEd.Lines.SaveToFile(SaveDlg.FileName);

Подобным образом выполняются и обращения к остальным диалогам в программе. Что касается элементов меню "Правка", то там так же не должно быть проблем: достаточно обращаться к соответствующим методам самого редактора для выполнения соответствующих операций. При этом, правда, было бы полезным предварительно проверять операцию на допустимость. Например, перед выполнением отмены (метод Undo) не помешает проверить состояние свойства-флага CanUndo:

if RichEd.CanUndo then RichEd.Undo;

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

if RichEd.SelText<>'' then RichEd.CutToClipboard;

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

if Clipboard.HasFormat(CF_TEXT) then ...

Здесь мы обратились к глобальному объекту Clipboard (он создается автоматически, подобно Screen или Application) и воспользовались его методом HasFormat, чтобы убедиться, что информация, находящаяся в буфере обмена, является текстом.

После создания обработчиков событий для всех пунктов меню, остается сохранить проект, выбрав в качестве имени файла формы "main", а файла проекта - "myedit". Таким образом, полный исходный код модуля main получится примерно таким, как показано в листинге 14.2.

Листинг 14.2. Исходный код редактора MyEdit

unit main; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, Menus, StdCtrls, ComCtrls, ToolWin, Clipbrd; type TMainFrm = class(TForm) MainMenu: TMainMenu; ToolBar: TToolBar; RichEd: TRichEdit; OpenDlg: TOpenDialog; SaveDlg: TSaveDialog; PrintDlg: TPrintDialog; PrintSetupDlg: TPrinterSetupDialog; FontDlg: TFontDialog; ColorDlg: TColorDialog; N1: TMenuItem; N2: TMenuItem; N3: TMenuItem; OpenFileM: TMenuItem; SaveFileM: TMenuItem; PrintM: TMenuItem; PrintSetupM: TMenuItem; ExitFileM: TMenuItem; N4: TMenuItem; N5: TMenuItem; UndoEdM: TMenuItem; N6: TMenuItem; CutEdM: TMenuItem; CopyEdM: TMenuItem; InsertEdM: TMenuItem; SelAllEdM: TMenuItem; FontFmtM: TMenuItem; ColorFmtM: TMenuItem; procedure OpenFileMClick(Sender: TObject); procedure SaveFileMClick(Sender: TObject); procedure PrintMClick(Sender: TObject); procedure PrintSetupMClick(Sender: TObject); procedure ExitFileMClick(Sender: TObject); procedure FontFmtMClick(Sender: TObject); procedure ColorFmtMClick(Sender: TObject); procedure UndoEdMClick(Sender: TObject); procedure CutEdMClick(Sender: TObject); procedure CopyEdMClick(Sender: TObject); procedure InsertEdMClick(Sender: TObject); procedure SelAllEdMClick(Sender: TObject); end; var MainFrm: TMainFrm; implementation {$R *.dfm} procedure TMainFrm.OpenFileMClick(Sender: TObject); begin if OpenDlg.Execute then RichEd.Lines.LoadFromFile(OpenDlg.FileName); end; procedure TMainFrm.SaveFileMClick(Sender: TObject); begin if if OpenDlg.FileName<>'' then SaveDlg.FileName:=OpenDlg.FileName; if SaveDlg.Execute then RichEd.Lines.SaveToFile(SaveDlg.FileName); end; procedure TMainFrm.PrintMClick(Sender: TObject); begin if PrintDlg.Execute then RichEd.Print(''); end; procedure TMainFrm.PrintSetupMClick(Sender: TObject); begin PrintSetupDlg.Execute; end; procedure TMainFrm.ExitFileMClick(Sender: TObject); begin close; end; procedure TMainFrm.FontFmtMClick(Sender: TObject); begin FontDlg.Font.Name:=RichEd.SelAttributes.Name; FontDlg.Font.Color:=RichEd.SelAttributes.Color; FontDlg.Font.Charset:=RichEd.SelAttributes.Charset; FontDlg.Font.Size:=RichEd.SelAttributes.Size; FontDlg.Font.Style:=RichEd.SelAttributes.Style; if not FontDlg.Execute then exit; RichEd.SelAttributes.Name:=FontDlg.Font.Name; RichEd.SelAttributes.Color:=FontDlg.Font.Color; RichEd.SelAttributes.Charset:=FontDlg.Font.Charset; RichEd.SelAttributes.Size:=FontDlg.Font.Size; RichEd.SelAttributes.Style:=FontDlg.Font.Style; end; procedure TMainFrm.ColorFmtMClick(Sender: TObject); begin ColorDlg.Color:=RichEd.SelAttributes.Color; if not ColorDlg.Execute then exit; RichEd.SelAttributes.Color:=ColorDlg.Color; end; procedure TMainFrm.UndoEdMClick(Sender: TObject); begin if RichEd.CanUndo then RichEd.Undo; end; procedure TMainFrm.CutEdMClick(Sender: TObject); begin if RichEd.SelText<>'' then RichEd.CutToClipboard; end; procedure TMainFrm.CopyEdMClick(Sender: TObject); begin if RichEd.SelText<>'' then RichEd.CopyToClipboard; end; procedure TMainFrm.InsertEdMClick(Sender: TObject); begin if Clipboard.HasFormat(CF_TEXT) then RichEd.PasteFromClipboard; end; procedure TMainFrm.SelAllEdMClick(Sender: TObject); begin RichEd.SelectAll; end; end.

Если теперь запустить программу (готовый исходный код находится в каталоге Demo\Part3\Editor), то можно будет убедиться, что она действительно работает - открывает файлы, позволяет вводить и редактировать текст, изменяя его оформление. Из недостатков можно сходу выделить лишь надпись "RichEd", которую редактор содержит изначально и отсутствие полос прокрутки, если текста будет больше, чем помещается в окне. Первый недостаток исправляется правкой свойства Lines, путем удаления ненужного текста, а второй - путем назначения свойству ScrollBars значения ssBoth. Отметим, что если при этом свойство HideScrollBars оставить в значении истины, то полосы прокрутки будут появляться лишь при необходимости.

Более серьезный недостаток кроется в заложенной нами возможности работать не только с файлами RTF, но и с обычными текстовыми. Дело в том, что если при сохранении вы даже выберите тип текстовых файлов и укажете расширение txt, то программа все равно сохранит файл с разметкой. Но, как нам известно, у компонента RichEdit существует свойство PlainText, отвечающее за текущий формат. Таким образом, остается лишь установить нужное значение для этого свойства в момент перед сохранением файла. А для того, чтобы определить, какой формат выбрал пользователь, используем свойство FilterIndex диалога сохранения:

if SaveDlg.Execute then begin RichEd.PlainText:=(SaveDlg.FilterIndex>1); RichEd.Lines.SaveToFile(SaveDlg.FileName); RichEd.PlainText:=false; end;

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

Диалоги поиска и замены

Работа с текстом не ограничивается изменением параметров шрифта. Часто гораздо более полезными операциями являются такие, как поиск подстроки с возможной заменой ее на иной текст. Для этих целей так же предусмотрены стандартные диалоговые окна - FindDialog и ReplaceDialog. Свойства этих компонентов включают в себя стандартное для диалогов Options, а так же группу свойств, определяющих положение диалога на экране - Left, Top и Position. Но наиболее важным, безусловно, является свойство FindText, которое собственно и содержит строку для поиска. У диалога замены предусмотрено еще одно свойство - ReplaceText, которое определяет строку для замены.

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

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

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

function FindText(const SearchStr: string; StartPos, Length: Integer; Options: TSearchTypes): Integer;

Здесь в качестве SearchStr указывается строка для поиска, StartPos обозначает место, с которого следует начинать поиск, а Length - место, до которого следует производить поиск. В качестве Options можно указать флаги stWholeWord и stMatchCase, включающие распознавание слов целиком и регистра. Таким образом, мы можем модернизировать наш текстовый редактор таким образом, чтобы он поддерживал поиск текста (модернизированный вариант находится в каталоге Demo\Part3\Editor2).

Прежде всего, поместим на форму оба рассматриваемых компонента - FindDialog и ReplaceDialog, присвоив им имена FindDlg и ReplaceDlg. Затем откроем конструктор меню и в раздел "Правка" добавим разделитель и 2 новых пункта - "Найти…" и "Заменить...", назвав их, соответственно, SearchEdM и ReplaceEdM. Теперь для пункта "Найти" определим процедуру вызова диалога поиска:

procedure TMainFrm.SearchEdMClick(Sender: TObject); begin FindDlg.Execute; end; Таким способом мы лишь отображаем диалог. Саму процедуру поиска следует разместить в обработчике события OnFind самого диалога. В простейшем случае она должна лишь определять место начала и конца поиска, а в случае нахождения искомого - выделять найденный фрагмент. Таким образом, мы можем получить примерно следующую процедуру: procedure TMainFrm.FindDlgFind(Sender: TObject); var StartPos, ToPos, FoundPos: Integer; begin StartPos:=RichEd.SelStart+RichEd.SelLength; ToPos:=Length(RichEd.Text)-StartPos; FoundPos:=RichEd.FindText(FindDlg.FindText, StartPos, ToPos, []); if FoundPos<>-1 then begin RichEd.SelStart:=FoundPos; RichEd.SelLength:=Length(FindDlg.FindText); RichEd.SetFocus; end else ShowMessage('Текст не найден!'); end;

Здесь вначале вычисляется точка начала поиска - StartPos, затем определяется длина поиска - ToPos, в типичном случае она должна равняться размеру текста от точки начала поиска до конца документа, после чего вызывается собственно метод FindText, а результат его работы присваивается переменной FoundPos. Затем, если результат не равен -1 (т.е. если вхождение подстроки найдено), фрагмент текста выделяется, и фокус ввода передается окну редактора, в противном случае выводится сообщение "Текст не найден".

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

if frMatchCase in FindDlg.Options then Opt:=[stMatchCase]; if frWholeWord in FindDlg.Options then Opt:=Opt+[stWholeWord];

Теперь наша функция поиска распознает заданные пользователем установки, так что можно перейти к реализации процедуры замены. Собственно вызов диалога замены производится точно таким же способом - путем обращения к методу Execute. При этом процедура поиска, реализуемая диалогом замены, будет в точности такой же, как и у диалога поиска - достаточно будет изменить имя компонента (см. так же листинг 14.3).

Что касается процедуры обработки события OnReplace, которое возникает, когда пользователь нажимает на кнопку "Заменить" или "Заменить все", то она, в простейшем случае будет выглядеть аналогичным образом, с той лишь разницей, что к ней добавляется замена вхождения на указанный пользователем текст. А именно после выделения найденного текста следует добавить еще одну строку кода:

RichEd.SelText:=ReplaceDlg.ReplaceText;

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

repeat ... until (FoundPos=-1) or not(frReplaceAll in ReplaceDlg.Options);

Если теперь заключить весь блок операций процедуры в этот цикл, за исключением, разве что, первых 2 строк, которые определяют параметры поиска, запустить программу и попытаться произвести замену по всем вхождениям, то можно будет убедиться, что все замечательно работает. Единственная проблема состоит в том, что по окончании работы, вне зависимости от того, были ли произведены замены или нет, вы получите сообщение "Текст не найден". Это объясняется тем, что при последней итерации в любом случае будет выполнен блок else условного оператора, проверяющего наличие вхождения искомого текста. Таким образом, необходимо вынести эту проверку за пределы цикла, а для того, чтобы определить, было ли что-либо найдено, используем переменную-счетчик, которую перед началом выполнения цикла установим в 0, а при каждой выполненной замене будем увеличивать на 1. Таким образом, мы сможем не только выдавать сообщение о том, что текст не найден, но и показывать количество произведенных замен, если пользователь нажимал на кнопку "Заменить все". Итоговый вариант процедуры замены приведен в листинге 14.3.

Листинг 14.3. Процедуры поиска и замены для редактора RTF и ReplaceDialog

procedure TMainFrm.ReplaceDlgFind(Sender: TObject); var StartPos, ToPos, FoundPos: Integer; Opt: TSearchTypes; begin if frMatchCase in ReplaceDlg.Options then Opt:=[stMatchCase]; if frWholeWord in ReplaceDlg.Options then Opt:=Opt+[stWholeWord]; StartPos:=RichEd.SelStart+RichEd.SelLength; ToPos:=Length(RichEd.Text)-StartPos; FoundPos:=RichEd.FindText(ReplaceDlg.FindText, StartPos, ToPos, Opt); if FoundPos<>-1 then begin RichEd.SelStart:=FoundPos; RichEd.SelLength:=Length(ReplaceDlg.FindText); RichEd.SetFocus; end else ShowMessage('Текст не найден!'); end; procedure TMainFrm.ReplaceDlgReplace(Sender: TObject); var i, StartPos, ToPos, FoundPos: Integer; Opt: TSearchTypes; begin if frMatchCase in ReplaceDlg.Options then Opt:=[stMatchCase]; if frWholeWord in ReplaceDlg.Options then Opt:=Opt+[stWholeWord]; i:=0; repeat StartPos:=RichEd.SelStart+RichEd.SelLength; ToPos:=Length(RichEd.Text)-StartPos; FoundPos:=RichEd.FindText(ReplaceDlg.FindText, StartPos, ToPos, Opt); if FoundPos<>-1 then begin RichEd.SelStart:=FoundPos; RichEd.SelLength:=Length(ReplaceDlg.FindText); RichEd.SelText:=ReplaceDlg.ReplaceText; RichEd.SetFocus; inc(i); end; until (FoundPos=-1) or not(frReplaceAll in ReplaceDlg.Options); if i=0 then ShowMessage('Текст не найден!') else if frReplaceAll in ReplaceDlg.Options then ShowMessage('Произведено '+IntToStr(i)+' замен'); end;

Таким образом, мы создали редактор, который, в принципе, может делать все необходимые в повседневной работе вещи. Для удобства остается только определить сочетания горячих клавиш для основных действий, прежде всего - для открытия и сохранения файлов, а так же для поиска и замены (горячие клавиши для редактирования текста типа Ctrl+Z или Ctrl+C поддерживаются автоматически). Чтобы назначить сочетания, в конструкторе меню для меню "Открыть" в свойстве ShortCut выберем Ctrl+O, для "Сохранить" - Ctrl+S, для "Найти" - Ctrl+F, а для "Заменить" - Ctrl+H. Все эти сочетания являются стандартными для данных действий в Windows, а изобретать собственные варианты для стандартных действий крайне не рекомендуется.

Избранное

SNK GSCP
SNK GSCP - новая библиотека для PHP 5!
Web Studio
Web Studio и Visual Workshop
Библиотека:
Стандарты на web-технологии
Монополия
Монополия Android
Загрузки:
скачать программы
Продукция:
программы и книги
Техподдержка / Связаться с нами
Copyright © 1999-2020 SNK. Все права защищены.
При использовании материалов с сайта ссылка на источник обязательна.
Рейтинг@Mail.ru