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

         

Библиотека функций общего назначения


Достойна упоминания лишь одна функция: разбиение строки текста на строки меньшей и ограниченной длины с пословным переносом. Практически все остальные случаи жизни можно инкапсулировать в классы :-) Спецификация: void StringWordSplit(AnsiString Str, TStringList *StringList, const unsigned short LengthOfOneLine)

Пример использования:

void Func(void) { std::auto_ptr<TStringList> StrLst(new TStringList()); ToFile w(“w”, “C:\\TEMP\\ StringWordSplit.txt”); // Открыть файл // Разбить фразу на строки длиной не более 20 знаков StringWordSplit(“Четыре чёрненьких чумазеньких чертёнка чертили чёрными чернилами чертёж”, StrLst.get(), 20); // Вывести все строки в файл for(int i=0; i<StrLst.Count; ++i) { w.fprintf(“%d: %s\n”, i+1, StrLst->Strings[i].c_str()); } }

Результат:

1: Четыре чёрненьких 2: чумазеньких 3: чертёнка чертили 4: чёрными чернилами 5: чертёж



Библиотека классов низкого уровня


Здесь "низкий уровень" - условное название, отражающее тот факт, что эти классы используются всеми остальными компонентами "ASK Tools".



Часть I. Комплект инструментов.


Чем профессионал отличается от любителя? На мой взгляд, наличием собственных стратегии и "средств производства". Совокупность собственных инструментов, которыми пользуется профи, преодолев определённый объём и качество структуризации, становятся его собственной инструментальной средой. Её использование сокращает большинство этапов разработки, увеличивает надёжность продукта, упрощает сопровождение.

Должен без ложной скромности признаться, что некоторые идеи, реализованные мною в своей среде, которую я называю "ASK Tools", впоследствии обнаружил в .NET. Конечно же, совсем на другом качественном уровне.

Много лет я работаю в качестве прикладного программиста БД. Все последние проекты, как закрытые (корпоративные), так и общедоступные, используют "ASK Tools" и некоторые принципы разработки, которые я считаю весьма полезными.

"ASK Tools" содержит только те инструменты, которые нужны повседневно при разработке бизнес-приложений. Здесь вы не найдёте работы с мультимедиа и прочих буржуазных излишеств – спартанская простота, которая кому-то покажется скудостью. Возможно, не без основания.

Состав "ASK Tools":

AskMsg.* - файлы текстовых ресурсов

AskClasses.dll - библиотека классов низкого уровня, которая используется всеми прочими компонентами из состава "ASK Tools"

AskImg.dll – графическая библиотека, содержащая набор общеупотребительных изображений



AskFunc.dll – библиотека функций

ASK.bpl – пакет компонентов BCB, Delphi

AskForm.dll – библиотека форм

Средствами "ASK Tools" реализовано простое средство формирования текстовых отчётов; на звание "генератор отчётов" оно не претендует, однако ж довольно удобно в использовании.

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


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

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

Исполняющая система также не остаётся внакладе, ведь ресурсы, используемые разными приложениями, находятся в одном месте, что сокращает расход дискового пространства и объём оперативной памяти, занимаемой приложениями, - каждый ресурс загружается в память единожды, и в дальнейшем используется приложениями совместно.

Следствием моей приверженности этой идее является тот факт, что я никогда не собираю BCB и Delphi-приложения без использования runtime компонент. Более того, все ресурсы, необходимые для работы моих приложений, я отдаю заказчикам в виде отдельных дистрибутивов. За счёт этого дистрибутивы собственно прикладных программ получаются довольно компактными, что упрощает обновление версий через Интернет.


Графическая библиотека


AskImg.dll содержит графические образы общего назначения - такие, как картинки 16Х16 для кнопок TBitBtn и 32Х32 для кнопок в инструментальной панели. Зачем это нужно? Графика, как и любой другой повторяющийся ресурс, увеличивает объём результирующего .EXE-файла. Возьмите размер ресурса, умножьте его на количество копий – и получите объём балласта в программе. Неверящие могут провести простой эксперимент: создать форму в Delphi, бросить на неё несколько экземпляров кнопок TBitBtn с непустым свойством "Glyph", а затем посмотреть на содержимое файла ресурсов этой формы (.DFM) в текстовом виде.

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



Класс AskMoney


AskMoney.*. Класс "денежная сумма", предназначенный для финансовых расчётов. Имеет public-методы:

AnsiString __fastcall InWords(int CurrencyCode=810); // Сумма прописью char * __fastcall c_str(bool isSpaces=true); // Сумма прописью

Пояснение: if(isSpaces == true и сумма нулевая) then будет сформирована пустая строка, а не "0-00"



Класс IniFile (производный от TFile)


IniFile.* - класс-оболочка для функций работы с .INI-файлами. Удобен как способом формирования имени файла, так и методами "get" и "put". Пример использования:

void Func(int Count) { IniFile ini(“C:\\TEMP\\Sets%02d.ini”, Count); // Последний параметр - умолчание AnsiString Str = ini.get(“Section”, “StrParam”, “”); // Последний параметр – умолчание long Lng = ini.get(“Section”, “LongParam”, 0L); ini.put(“Section”, “LongParam”, Lng + 3); }



Класс TAskCursor


TAskCursor.*. В период выполнения программы для обозначения длительных операций часто требуется изменить форму курсора, а по завершении операции – вернуть курсор в нормальный вид. Писать каждый раз что-то типа "Screen->Cursor = crHourGlass", а затем не забыть "Screen->Cursor = crDefault" - это слищком тяжко. Как известно, лень – двигатель прогресса, поэтому гораздо приятнее изобрести простенький классик, при создании экземпляра которого курсор будет менять форму, а при прекращении сусуществования объекта – возвращать свою форму. Согласитесь, приятно написать такое:

void Func(void) { TAskCursor Cursor; // …тело "притормаживающей" функции… }



Класс TAskRecycleBin


Стоит несколько особняком, так как используется напрямую только в тех приложениях, которые должны поддерживать работу с "Корзиной". Думаю, декларация свойств и методов класса не нуждается в дополнительных комментариях:

#ifndef AskRecycleBin_H #define AskRecycleBin_H #include <vcl.h> #include "shellapi.h" #ifdef DLL_EXP # define CLASS_MODE __export #else # ifdef DLL_IMP # define CLASS_MODE __import # else # define CLASS_MODE # endif #endif const DWORD SHERB_SILENT = SHERB_NOCONFIRMATION | SHERB_NOPROGRESSUI | SHERB_NOSOUND; class CLASS_MODE TAskRecycleBin { private: bool __fastcall GetCanErase(); bool __fastcall GetCanUse (); protected: DWORD GetDLLVersion(String dllName); inline DWORD PackDLLVersion(DWORD major, DWORD minor) { return((major << 16) + minor); } public: bool DeleteFiles(String filespec, bool allowUndo=true); bool Erase(); __property bool CanErase = { read=GetCanErase }; __property bool CanUse = { read=GetCanUse }; }; #endif



Класс TFile


TFile.* - базовый класс для классов "ToFile" и "IniFile". Здесь уместно следующее пояснение. В языке "С++" есть средства, унаследованные из языка "С". Например, многие системные функции, функции работы с файлами, и т.д. К их числу относится подавляющее большинство функций Windows API.

Работа с ними кардинально отличается от работы со средствами С++, и не в лучшую сторону. В С++ (а также средах Delphi, BCB, и т.д.) принято обрабатывать ошибки времени исполнения с помощью исключений (exceptions). То есть если программист не обработал возникшую ошибку на прикладном уровне, она будет обработана системой исполнения, и ничего страшного не произойдёт.

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

Если же программист будет забывать или пренебрегать обработкой ошибок, получится некачественный код, ненадёжное ПО, которое будет "глючить" в самых неожиданных местах. Это, в свою очередь, может привести к самым плачевным результатам: потере данных, краху системы, и т.д.

Можно возразить: "не используйте устаревшие средства". Ну, во-первых, это возможно далеко не всегда: API-функции из песни не выкинешь; во-вторых, лично мне работа с потоками ввода вывода С++ (stream) кажется излишне громоздкой, во многих случаях гораздо приятнее обходиться старыми добрыми средствами.

Выход один: для каждого случая использования устаревших средств языка необходимо писать класс-обёртку, который будет обрабатывать все возможные коды возврата всех унаследованных функций и преобразовывать их в исключения (exception). Проблема кардинально решена в .NET – и именно описанным способом.

Так вот, классы, производные от TFile, являются оболочками для файловых функций. Их основной задачей является обработка кодов возврата файловых функций языка "С" и преобразование их в исключения. Дополнительным, но не менее приятным бонусом, является более удобная функциональность классов по сравнению с использованием простых функций. Это ведёт к написанию более лаконичного кода.

Итак, "завернув" все унаследованные функции в "оболочки" классов, мы делаем шажок к написанию безопасного кода.



Класс ToFile (производный от TFile)


ToFile.* ("открываемый файл") – класс-оболочка для работы с файлами, текстовыми и двоичными. Удобен как способом формирования имени файла, так и методами. Файл необязательно закрывать вручную, он будет закрыт автоматически перед прекращением существования экземпляра класса. В случае возникновения ошибки при работе с файлом будет возбуждено исключение, содержащее (в дополнительных числовых и строковых полях errNo, dosErrNo) всю информацию о файловой ошибке.

Пример использования:

void Func(int Count) { ToFile r(“r”, “C:\\TEMP\\read%02d.txt”, Count); ToFile w(“w”, “C:\\TEMP\\write%02d.txt”, Count); AnsiString Str; r.GetStr(Str); w.fprintf(“Прочитанная строка: %s\n”, Str.c_str()); }



Классы AskDate, BCDate, AskTime


AskDate.*, BCDate.*, AskTime - классы моей разработки, унаследованные из DOS. Иногда их использовать (лично мне) немножко удобнее, чем TDateTime



Пакет компонентов ASK.BPL


Ну, вот мы и добрались до ядра "ASK Tools". Пакет состоит из компонентов разного назначения, сгруппированных на нескольких закладках (после установки в среде BCB или Delphi). Пойдём от простого к сложному.



Пора копнуть поглубже…


Для начала хорошая новость: хочется надеяться, что Borland всё-таки не окончательно "кинула" разработчиков на C++ Builder . Кажется, теперь Delphi и C++ Builder сольются, наконец, в экстазе.

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

Приступаю к описанию системных компонентов с закладки "ASK Tools". В первой статье я агитировал за то, что обработка любых ошибок и нештатных ситуаций времени исполнения должна быть единообразной и должна использовать только механизм исключений (Exceptions). Все выгоды такой унификации описаны там же.

Исходим из предположения, что мы проделали большую работу по обёртке всех C и API-вызовов в свои классы с "исключительной" обработкой ошибок. Теперь программа не "валится" без объяснений в нештатной ситуации. Пришло время, наконец, снять сливки с наших трудов.

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

Общий план реализации таков. Компонент TAskExceptionHandler "бросается" на главную форму приложения. На этом, собственно, "ручное кодирование" заканчивается. Компонент перехватывает все исключения и "вываливает" их в лог-файл, после чего исключение обрабатывается стандартным образом. Если вам понадобится раскрыть более полно информацию об исключении особо хитрого типа, вы всегда сможете включить обработку этого типа в метод LogExceptionState.

В момент возникновения исключений делается "снимок состояния" системы, который также регистрируется в лог-файле вместе с событием. Лог-файл имеет структуру ini-файла и имя, сформированное по имени приложения.
Образец прилагается.

[12.11.2004 16:06:47] ExceptionType=EToFile ExceptionMess=Ошибка записи в файл ToFileFileName=A:\big_file.txt ToFileErrNo=28 ToFileDosErrNo=112 ToFileErrNoStr=Недостаточно места на устройстве ToFileDosErrNoStr= not enough space on the disk ClassName=FormOptions (TFormOptions) FormName=FormOptions (TFormOptions) ProgramVersion=1.0.0.0 TotalVirtMem=2047 AvailVirtMem=2000 TotalPageMem=1228 AvailPageMem=895 DiskC_Total=76316 DiskC_Free=41916

Компонент, естественно, использует классы из упоминавшейся библиотеки классов (IniFile, ToFile), а также некоторые компоненты, речь о которых пойдёт ниже. Также используются файлы текстовых ресурсов – для формирования сообщений на языке пользователя.

Пример использования. Функция CreateVeryBigFile создаёт ситуацию нехватки дискового пространства. Файловая ошибка (спасибо нашему классу-обёртке) преобразуется в исключение, которое перехватывается компонентом, выводится в лог-файл, и далее обрабатывается стандартным образом.

void CreateVeryBigFile() {

ToFile w("w", "A:\\big_file.txt");

while(1) w.fprintf("Очень-очень большой файл…\n");

}

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

Как уже говорилось, благодаря такой организации обработки ошибок, программы не "валятся" без объяснений причин. Протокол работы помогает выявить и устранить многие проблемы времени выполнения. Более того, можно организовать отсылку протоколов разработчику – через e-mail или FTP – для удалённой диагностики проблем и выпусков патчей.

Кстати, перехват ошибок файловой системы весьма помогает при работе с устаревшими Windows 9x, в которых часто возникают сбои в FAT.

TAskExceptionHandler использует несколько компонентов с закладки "ASK SysInfo", на которую мы плавно переместимся. Знакомьтесь: TAskOS.


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

enumOSType Type – Тип ОС. Может принимать значения: otUnknown, WIN_95, WIN_98, WIN_ME, WIN_NT, WIN_2000, WIN_XP, WIN_2003, WIN_LATEST

AnsiString OSName – строковое наименование ОС

enumOSPlatform Platform – тип платформы Windows; может принимать значения: opUnknown, _9x, NT

AnsiString Version – версия Windows

AnsiString WinDir – папка, в которую установлена Windows

AnsiString WinSetupDir – папка, из которой установлена Windows

AnsiString WinSysDir – системная папка (%WinDir%\SYSTEM для Windows 9x/ME и %WinDir%\SYSTEM32 для Windows NT/2000/XP/2003 etc.)

AnsiString TempPath – папка для временных файлов

AnsiString WinLang – строковое обозначение языка локализации Windows

bool Plus – индикатор, установлен ли пакет Plus! Для Windows 9x

enumWinRole Role – Роль Windows. Может принимать значения: wrUnknown, wrServer, wrWorkstation. Критики, минуточку внимания! Я знаю, что можно расширить и углубить значения этого свойства, но указанных значений для моих целей достаточно. Кому не хватает – напрягутся сами.

AnsiString AppData – полный путь к папке "\Documents and Settings\\Application Data"

AnsiString LocalSettings – полный путь к папке "\Documents and Settings\\Local Settings"

AnsiString LocalAppData – полный путь к папке "\Documents and Settings\\Local Settings\Application Data"

Компонент TAskOS, как и все остальные с этой закладки, имеет метод Refresh(), который вызывается автоматически, если компонент лежит на форме. То есть, после создания формы все свойства компонента уже содержат правильные значения и готовы к использованию. А вот если компонент создаётся динамически в куче, следует вызвать этот метод вручную для инициализации свойств.

Также многие компоненты имеют свойство bRefresh. Если этому свойству во время проектирования присвоить значение "true", то будет вызван метод Refresh и значения свойств можно будет увидеть в инспекторе объектов.



Компонент TAskComputer инкапсулирует следующие свойства:

AnsiString ComputerName – имя компьютера

AnsiString UserName – имя пользователя

long RAM – объём оперативной памяти

long RAMUsage – объём используемой памяти. Вычисляется динамически, в момент чтения значения свойства

Компонент TAskProcessor имеет следующие полезные свойства:

AnsiString CPUName – наименование процессора

AnsiString CPUIdentifier – идентификатор процессора

long Frequency – Частота процессора

double Usage – Процент использования процессора. Вычисляется динамически, в момент чтения значения свойства

Компонент TAskDisplay.

short Width – текущая ширина экрана в пикселях

short Height - текущая высота экрана в пикселях

__int64 ColorsCount – глубина цвета

short ColorsBits - глубина цвета в битах

TStringList * ListModes – список поддерживаемых режимов

AnsiString CardName – наименование видеокарты

short RefreshRate – частота обновления

AnsiString DisplayName – наименование монитора

Компонент TAskKeyboard.

bool NumLock – индикатор и манипулятор клавиши "NumLock". Метод Read возвращает состояние кнопки; метод Write устанавливает кнопку в соответствующее состояние (включает/выключает NumLock)

bool CapsLock – индикатор и манипулятор клавиши "CapsLock"

int LangsCount – количество установленных в системе языков локализации

AnsiString LangName – текущий язык локализации Windows (например, "Русский")

AnsiString LangCode3 – трёхбуквенное обозначение текущей раскладки (например, "RUS")

AnsiString LangCode2 - двухбуквенное обозначение текущей раскладки (например, "RU")

Именно с помощью этого компонента выставляются клавиатурные свойства у компонентов TAskEdit, TAskMaskEdit, TAskRxCalcEdit, TAskCurrencyEdit, TAskComboBox, TAskMemo из предыдущей статьи.

Компонент TAskPrinter. Я не настаиваю на общественной полезности этого компонента; просто в последующих статьях он будет упоминаться, поэтому он и описан здесь.

AnsiString DefPrinter – принтер по умолчанию



AnsiString CurPrinter – текущий принтер

AskPrinterSystem PrintSystem – система печати; может принимать значения: apsMatrix, apsLaser, apsStream, apsOther, apsUnknown=666

TStringList * Printers – список установленных в системе принтеров

int ItemIndex – индекс текущего принтера

AnsiString PaperFeed – управляющий символ "прогон листа" - различается для принтеров разных систем печати (матричный, лазерный)

Основные функции.

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

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

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

Компонент TAskDisk предоставляет наиболее употребимую информацию о логических дисках.

AnsiString Letter – буква диска. При присвоении значения этому свойству выполняется метод Refresh() для соответствующего диска

AnsiString SerialNum – серийный номер

enumDiskType Type - тип диска; может принимать значения: dtUnknown, dtNOTROOTDIR, dtREMOVABLE, dtFIXED, dtREMOTE, dtCDROM, dtRAMDISK

AnsiString Label – метка диска

AnsiString FileSystem – файловая система

__int64 Size – размер диска в байтах

__int64 Free – размер свободного места

bool DiskInDrive – признак, вставлен ли съёмный диск в устройство; обращение к этому свойству не вызывает неубираемого системного окошка типа "Нет диска в дисководе"

AnsiString sType – тип диска на языке локализации Windows (например, "Сменный", "Локальный", "CD-ROM", и т.д.).


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

Компонент TAskListDisks содержит список всех логических дисков.

int Count – количество дисков

AnsiString List – список дисков

TAskDisk * Disk – указатель на компонент, могущий полноценно описать выбранный диск

Компонент TAskCard – описание карты (платы).

enumCardType Type – тип карты; может принимать значения: ctNetwork, ctSound, ctVideo, ctSCSI

AnsiString CardName – наименование карты

AnsiString MACAddr – MAC адрес сетевой карты

AnsiString IPAddress – IP адрес сетевой карты

Компонент TAskShutdown несколько выбивается из ряда компонентов, предоставляющих информацию о системе. Он предназначен для удобного "гашения" Windows и обладает следующими свойствами:

ShutdownType Type – тип "гашения"; может принимать значения: ewxLOGOFF, ewxPOWEROFF, ewxREBOOT, ewxSHUTDOWN

bool Force – признак "ускоренного гашения" - без ожидания ответа на вопросы, закрывать ли то или иное приложение

Метод DoIt() выполнит поставленную задачу (заданную описанными свойствами).

Ну, и на сладкое – компонент TAskSysInfo, являющийся контейнером компонентов: TAskOS * OS, TAskCPU * CPU, TAskListDisks * ListDisks, TAskComputer * Computer, TAskKeyboard * Keyboard, TAskDisplay * Display, TAskCard * NetworkCard, TAskCard * SoundCard, TAskCard * SCSICard.

Помимо этого, компонент обладает следующими свойствами:

bool ActiveOnLoaded – указывает, начинать ли тестирование системы сразу же, как появляется форма, на которой лежит компонент. Может случиться, что это надо сделать не сразу, а немного погодя. В таком случае вызовите метод Refresh() вручную.

TStringList * Printers – список установленных принтеров

TStringList * Resources – список ресурсов, отданных "в общее пользование"

TStringList * SoftList – список установленного в системе софта. Кроме того, в массиве TMapSoft содержится следующая информация о каждом установленном продукте:

Версия

Производитель

URL продукта или производителя



ID продукта

На кого зарегистрирован

Компания, в которой работает товарищ, на которого зарегистрирован продукт

Кроме того, у компонента TAskSysInfo есть 2 события:

OnEndRefresh – возникает по окончании "нарывания" общесистемной информации; в общем случае это событие возникает раньше, чем OnEndRefreshDisks

OnEndRefreshDisks - возникает по окончании "нарывания" информации о логических дисках

Результат работы компонента во время проектирования представлен на рисунках , , , , , .

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

Скачать архив (35K)

1






Размышления старого программиста о жизни и о себе


План второго выпуска пришлось немножко подкорректировать, чтобы не оставить без внимания отзывы IceStudent’а на . Разбор полётов будет в стиле "Цитата IceStudent’а (IS) – Ответ Андрея Кочетова (АК)".

IS: идея повторного использования ресурсов очень хороша, при условии, что надёжность испольуемого кода очень высока

АК: Спасибо :) Смею надеяться, что код до некоторой степени отлажен, так как непрерывно шлифуется уже пятый год

IS: Но дальнейшее обсуждение узкоспециализированных утилит несёт не много пользы

АК: Я уже говорил, что в статьях будут обсуждаться не столько конкретные блоки кода, сколько идеи. См. ниже

IS: Класс TAskCursor - Маленькая фича, применяемая в отдельных случаях. В большинстве случаев, изменить курсор намного проще и быстрее, чем описано А уж если сравнить объём генерируемого кода для класса и для

АК: Вы забыли, что для "исключительного" случая (Exception) также придётся писать код, возвращающий курсор в прежний вид, а в случае использования класса (объект которого лежит в стеке), это выполнится автоматически – ведь при исключительной ситуации механизм обработки исключений нормально завершает процедуру, в т.ч. нормально завершат своё существование все локальные переменные и объекты; а в деструкторе класса, который непременно выполнится, и будет автоматически изменена форма курсора к прежнему виду. Так что с классом – писать меньше. К тому же во всех местах объявления класса будет подключаться один и тот же код, в то время, как при ручном написании обработки формы курсора каждый раз вновь пишется один и тот же код, который и увеличивает размер программы. Более того, по секрету напомню: реализация класса TAskCursor содержится в отдельной (статически линкуемой) DLL, что делает рассуждения о "разбухании" кода несостоятельными в принципе.

IS: Визуальные компоненты с дополнительными свойствами бывают полезны нередко, если не отличаются ненужными излишествами и глюкавостью :)

АК: Согласен. Именно функциональность, и никаких излишеств! Приглашаю к чтению второй статьи.

Итак, продолжаем рассмотрение визуальных компонентов с закладки "ASK Controls"



TAskDateEdit


– наследник компонента TDateEdit . Так уж получилось, что RX-компонент для ввода даты мне нравится больше стандартного; к тому же он более функционален. Ну а недостающую функциональность добирает производный компонент TAskDateEdit.

Отличается от предка следующим: если язык локализации Windows – русский или один из французских диалектов, то некоторые параметры компонента устанавливаются в значения, принятые для календаря во Франции, откуда, собственно, они и заимствованы Россией вместе с метрической системой мер и весов:

Неделя начинается в понедельник, а не в воскресенье, как в Пиндустане

Выходные – суббота и воскресенье, оба выделяются красным цветом.

Указанная доп. функциональность выполняет некие действия автоматически в "runtime", что позволяет не заморачиваться ручной установкой этих свойств в "designtime" для каждого компонента, брошенного на форму.

У этого компонента также есть дополнительное свойство "AskBegDateType", которому можно присвоить одно из следующих значений (в том числе и в "designtime", в "Инспекторе объектов"): ebdtNone, ebdtToday, ebdtBegPrevMonth, ebdtEndPrevMonth, ebdtBegPrevQuarter, ebdtEndPrevQuarter, ebdtBegCurMonth, ebdtEndCurMonth, ebdtYesterday

В зависимости от значения этого свойства, отображаемая дата будет:

Пустая

Текущая дата

1-й день предыдущего месяца

Последний день предыдущего месяца

1-й день предыдущего квартала

Последний день предыдущего квартала

1-й день текущего месяца

Последний день текущего месяца

Вчерашняя дата

Как уже понял проницательный читатель, это свойство введено для того, чтобы исключить написание кода для задания даты или периодов (начальная и конечная даты) – например, при формировании отчётов за период.

Для полноты описания следует добавить, что существует свойство "DateToStr6", которое переводит дату в строку вида "ГГММДД", что достаточно часто бывает нужным при разработке деловых приложений.



TAskEdit, TAskMaskEdit, TAskRxCalcEdit, TAskCurrencyEdit, TAskComboBox, TAskMemo, и т.д.


– наследники соответствующих компонентов без префикса ASK. Обладают дополнительными свойствами, позволяющими выполнять предустановки статусов клавиатуры при получении компонентами фокуса ввода, и возвращающими всё в исходное состояние при потере компонентами фокуса ввода (при уходе с них). Если это выглядит чуточку заумно, дальше всё станет понятно.

Итак, свойства:

bool AskSaveKeyboardStatus – запоминать ли статус клавиатуры (состояние "Caps Lock", "Num Lock", языковая раскладка) перед его изменением (при получении фокуса ввода)

bool AskRestoreKeyboardStatus – восстанавливать ли статус клавиатуры при потере фокуса ввода (уходе с компоненты редактирования)

bool AskCapsLockOff – выключает "Caps Lock"

bool AskCapsLockOn – включает "Caps Lock"

bool AskNumLockOff – выключает "Num Lock"

bool AskNumLockOn – включает "Num Lock"

bool AskSwitchToWinLang – переключать ли раскладку клавиатуры на язык локализации Windows (для русских Windows – это русский язык)

bool AskSwitchToENU - переключать ли раскладку клавиатуры на английский язык.

Таким образом, только редактированием свойств компонентов, - без написания кода - можно разработать максимально удобный для пользователей режим ввода данных: в поле ввода ФИО – русский язык, в поле ввода суммы – английский, "Num Lock" включен, и т.д.



TAskPageControl


– наследник компонента TPageControl, который обладает одним, на мой взгляд, недостатком: если в "designtime" сфокусироваться на любую закладку, отличную от первой, именно эта (не первая!) закладка и будет показана во время выполнения программы ("runtime"). Приходится возвращаться к редактированию проекта только для того, чтобы устранить эту досадность. TAskPageControl избавлен от этого недостатка: в "runtime" при первом показе компонента активна первая закладка. Я уже акцентировал внимание, что мои инструменты нацелены на облегчение (моего) труда разработчика, даже в мелочах.



TAskSpdBtnGroup.


Для этого "изобретен" ещё один компонент: TAskSpdBtnGroup. И хотя он невизуальный, находится он также на закладке "ASK Controls". Он обладает следующими свойствами:

TAskSpeedBtn * PtrAskSpdBtn0… PtrAskSpdBtn9 – указатели на взаимозависимые кнопки, эмулирующие функциональность TRadioGroup.

У всех этих кнопок значение свойства "GroupIndex" должно быть одинаковым.

short ItemIndex – индекс нажатой кнопки (от 0 до n-1). Если сохранять это свойство в INI-файле или реестре (и восстанавливать), то при показе формы уже будет нажата одна из множества кнопок. Соответственно, зависимые от неё компоненты будут видимы (разрешены), в то время, как компоненты, зависимые от других зависимых кнопок (простите за тавтологию), будут невидимы (запрещены).

TAskSpeedBtn * ActiveControl – указатель на нажатую кнопку.

Для сохранения/восстановления свойств компонентов я использую компонент, производный от TFormStorage из RX Lib. Знающие люди подтвердят, что это чрезвычайно удобная штука.

Поэтому разработка интерфейса любой степени сложности с сохранением/восстановлением настроек, не занимает много времени и не требует написания программного кода.



TAskSpeedButton


– наследник TRxSpeedButton (просто так исторически сложилось; можно было произвести и от TSpeedButton). О-о, TAskSpeedButton – это отдельная песня! Запеваем.

AnsiString AskNameDll, AskNamePic – свойства, позволяющие подгрузить на кнопку изображение, лежащее в библиотеке образов. По умолчанию используется библиотека AskImg.dll

bool AskAllowAllUp – Свойство времени проектирования. Пояснение. В "designtime", если на форме несколько (не дай бог, много!) подобных кнопок, бывает очень трудно не запутаться и правильно выставить свойство "GroupIndex". Так вот, "AskAllowAllUp, при установке значения "true" в "designtime", выполняет следующее: присваивает свойству "AllowAllUp" то же значение, а свойству "GroupIndex" присваивает уникальное значение, не совпадающее со свойствами "GroupIndex" прочих кнопок "SpeedButton", лежащих на форме.


рис 1.



TAskToolBar.


Последний компонент с закладки "ASK Controls". TAskToolBar – производен от TToolBar и предназначен для того, чтобы можно было с минимальными усилиями динамически формировать панель инструментов. Кнопки, располагающиеся на такой панели, связаны с элементами типа "TAction" из "TActionList". В целевом проекте это выглядит примерно так:

void __fastcall TFormMain::CreateButtons(void) { AskToolBar1->CreateButton(AExit, "Exit32"); AskToolBar1->CreateButton(AUpdate, "TwoComps"); AskToolBar1->CreateButton(AOptions, "Tools32"); }

На панели создаются кнопки типа TAskSpeedButton; 1-й параметр задаёт TAction, с которым связана кнопка, 2-й – наименование картинки, которая подгружается из заданной библиотеки образов (по умолчанию – AskImg.dll).

Если TAction->Enabled == false, кнопка не появится на панели.

В следующей статье будет рассмотрена более интересная тема – расширенная обработка исключений.

- некоторые компоненты из пакета "ASK Tools" производны от компонентов из свободно распространяемой библиотеки для Delphi и BCB «RX Library»




(см. рис. 2). Опять лирическое отступление. Каждый из нас видел в программных продуктах реализацию таких вещей: при нажатии на "CheckBox" или одну из точек "RadioGroup", связанные (по логике приложения) поля появляются или исчезают, или становятся (не)доступными (свойство "Enabled"). Однажды мне опостылело писать похожий код всякий раз, когда надо сделать что-то подобное. И я придумал для себя концепцию интерфейса: если от компонента ожидается простое значение типа "да/нет", - это будет "CheckBox". Если же потребуется включать/выключать логически связанные элементы управления, пусть это будет "SpeedButton" со включенным свойством "AllowAllUp" (сохранение нажатого/отжатого состояния). См. рис. 1.


рис 2.

Соответственно, к кнопке можно прицепить до 10-и компонентов (поля ввода, кнопки, надписи, да что угодно!), которые будут появляться в случае, если Down=true, и исчезать в противном :) случае. Эти-то свойства и указывают на зависимые компоненты; значения им присваиваются во время проектирования.

Свойство EControl AskControl – может принимать значения "ecVisible" или "ecEnabled". Соответствующему свойству зависимых компонентов и будет присваиваться значение свойства "Down". То есть, при нажатии кнопки зависимые компоненты становятся видимыми (или доступными для редактирования), а при отжатой кнопке – невидимыми (нередактируемыми).

Осталась лишь одна проблема: следует сохранять и восстанавливать не только свойство "Down", но и свойства Visible/Enabled всех зависимых компонентов. Если мы автоматизируем и этот момент, мы сможем разрабатывать поведение довольно сложных интерфейсов без написания кода. Итак, знакомьтесь: свойство

bool AskDown. Следует присваивать значение true/false именно ему, а не "Down". Также следует сохранять в реестре (INI-файле) именно его. В этом случае поведение/состояние кнопки вместе со всеми зависимыми элементами управления будет ожидаемым и в момент работы, и при повторном запуске формы.

Для функционирования приведённых на примере ((рис. 1) кнопок и сохранения/восстановления настроек не написано ни строчки кода. P.S. на примере – настройки программы - обновилки вирусных баз (*.vdb) для антивируса . К слову, все проекты, представленные , реализованы с использованием "AskTools". Как и (закрытые) проекты на основной работе.

Более того, набор таких кнопок удобно использовать в качестве функционально расширенного заменителя RadioGroup – см. рис. 1. К каждой Speed-кнопке зависимого выбора можно "прицепить" до 10 зависимых компонентов, и получается довольно мощная конструкция – без единой строчки кода.


Текстовые ресурсы


Как уже было сказано, они используются всеми остальными компонентами инструментального пакета. Уточнение: когда упоминаются "компоненты пакета инструментов "ASK Tools", имеются в виду не Delphi-компоненты, а составляющие пакета.

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

Пример содержимого файла текстовых ресурсов:

[TypeOfDisk] Unknown=Не определён REMOVABLE=Сменный FIXED=Локальный REMOTE=Сетевой CDROM=CD-ROM RAMDISK=Виртуальный (RAM) [Printer] ErrOpen=Ошибка открытия принтера ErrStartDoc=Ошибка начала печати ErrWrite=Ошибка печати [ErrNo] 1=Неверный № функции 2=Файл не найден 3=Путь не найден 7=Блок памяти уничтожен 9=Неверный адрес блока памяти . . .



Целевая аудитория: молодёжь, которая хотела


Целевая аудитория: молодёжь, которая хотела бы заниматься программированием; возможно, что-то полезное для себя найдут и более подкованные товарищи Структура заметок: рассуждения общего характера, плавно перетекающие в конкретные примеры с исходниками Среда разработки: Borland C++ Builder; большинство исходников с лёгкостью переносятся в Delphi; общие рассуждения вообще справедливы для любого средства разработки Затронутые темы:
разработка собственных компонентов (интерфейсных, вспомогательных, системных, работающих с БД, и т.д.)
разработка архитектуры приложений
унификация процесса проектирования приложений
Хочу обратить внимание читателей, что никаких откровений или мыслей, блистающих особой глубиной/новизной, вы здесь не найдёте. Просто прикладной программист среднего (или чуть выше) уровня излагает свои соображения, рождённые опытом работы, и иллюстрирует их конкретными примерами работающего инструментария.
Ещё раз подчёркиваю, что по всем вопросам изложена моя личная точка зрения. Я открыт для конструктивной дискуссии и с радостью восприму все замечания, исправления и дополнения к исходным текстам.
Предполагается, что читатель знаком с основными принципами объектно-ориентированного программирования и разработки приложений.
Последовательность изложения: вначале будет описана совокупность инструментов. Это займёт несколько статей (глав) – вместе с исходными текстами и примерами использования.
Во второй половине будет описана моя стратегия разработки приложений с примерами реализации.
Итак,

Закладка "ASK Controls"


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

Компонент TAskButton унаследован от "TButton" и обладает дополнительными свойствами:

PtrSecondBtn – указатель на соседнюю кнопку (чаще всего на кнопку "Cancel" - для кнопки "OK")

AskSetMouse – если "true", то при показе формы курсор мышки будет сфокусирован на этой кнопке

AskEnabledOff - если "true", то при нажатии на кнопку свойство "Enabled" примет значение "false"

AskKind – облегчает жизнь во время проектирования (design time). Значение по умолчанию – "akCustom".

Если в инспекторе объектов ему присвоить значение "akOK", следующим свойствам будут присвоены значения:

ModalResult - mrOk

Caption - читается из файла текстовых ресурсов, соответствующего языку локализации Windows: "Выполнить", "OK", и т.д.

Cancel – false

Default – true

AskSetMouse – true

AskEnabledOff – true

Если в инспекторе объектов ему присвоить значение "akCancel", следующим свойствам будут присвоены значения:

ModalResult - mrCancel

Caption - читается из файла текстовых ресурсов, соответствующего языку локализации Windows: "Выход", "Cancel", и т.д.

Cancel – true

Default – false

AskSetMouse – false

AskEnabledOff – false

Компонент TAskBitBtn унаследован от TBitBtn, аналогичен TAskButton, но дополнительно имеет ещё 2 свойства:

AskNameDll – имя DLL, содержащей графические ресурсы. По умолчанию – "AskImg.dll"

AskNamePic – имя графического ресурса (картинки 16Х16) из указанной DLL

Как видно из названия свойств, эта кнопка загружает картинку из DLL во время выполнения; в "design time" кнопка не содержит изображения, что способствует уменьшению размера исполняемого модуля.

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

До встречи, дорогие читатели!

Скачать архив: (18 K)