
- •4.1. Ошибки и исключительные ситуации
- •4.2. Классы исключительных ситуаций
- •4.3. Обработка исключительных ситуаций
- •4.3.1. Создание исключительной ситуации
- •4.3.2. Распознавание класса исключительной ситуации
- •4.3.3. Пример обработки исключительной ситуации
- •4.3.4. Возобновление исключительной ситуации
- •4.3.5. Доступ к объекту, описывающему исключительную ситуацию
- •4.4. Защита выделенных ресурсов от пропадания
- •4.4.1. Утечка ресурсов и защита от нее
- •4.5. Итоги
1)Компонент StringGrid находится на странице Additional палитры компонентов. StringGrid - компонент для отображения различных данных в табличной форме. Как следует из названия, ячейки компонента StringGrid Delphi могут содержать данные, имеющие тип String, а также отображать графику. |
|
|
Таблица StringGrid состоит из выделенных серым FixedCols и FixedRows - зафиксированных ячеек-заголовков, и обычных, белых ячеек. Содержимое Fixed ячеек недоступно редактированию, и меняется только программно. За возможность редактирования обычных ячеек отвечает одно из значений свойства Options. Итак, компонент StringGrid имеет возможность адресации каждой отдельной ячейки по номеру столбца и строки. Содержимое ячейки (i, j), где где i - номер столбца, j - номер строки, имеет вид StringGrid1.Cells[i, j] и доступно как для чтения, так и для записи. Здесь, как и всегда, номера столбцов ( i ) и строк ( j ) отсчитываются от 0. Выделенная ячейка таблицы имеет
номер столбца: |
StringGrid1.Col |
номер строки: |
StringGrid1.Row |
поэтому содержимое выделенной ячейки будет адресоваться так: S:=StringGrid1.Cells[StringGrid1.Col, StringGrid1.Row]; Не правда ли, написание такой строки - утомительный процесс. Поэтому пользуйтесь оператором присоединения with: with StringGrid1 do S:=Cells[Col, Row]; А лучше сразу задать в свойстве Name имя покороче, например SG. За многие свойства компонента Delphi StringGrid отвечает свойство Options. В Инспекторе Объектов Options - это раскрывающийся список, представляющий собой элементы данного множества. Если значение элемента равно True, то он присутствует в множестве, если False - то нет.
|
|
|
Как следует из таблицы, за возможность редактировать содержимое ячеек с клавиатуры отвечает элемент goEditing свойства-множества Options. В Инспекторе Объектов установите его значение в True. Чтобы управлять этой возможностью программно, нужно включить или исключить из множества данный элемент: StringGrid1.Options:=StringGrid1.Options+[goEditing]; //Включаем редактирование, другие элементы не трогаем StringGrid1.Options:=StringGrid1.Options-[goEditing]; //Выключаем редактирование, другие элементы не трогаем StringGrid1.Options:=[goEditing, goFixedVertLine, goFixedHorzLine, goVertLine, goHorzLine, goRowSelect]; //Задаём список необходимых элементов Если элементы заданы списком, это аналогично присвоению в Инспекторе Объектов этим элементам значения True, остальным - False. Ячеек в таблице, как правило, много, и в рамках компонента видна только часть из них. В программе доступна информация как об общем количестве строк и столбцов, так и номерах и количестве строк и столбцов, видимых в рамках таблицы. Количество строк в Delphi StringGrid равно StringGrid1.RowCount. Количество столбцов в Delphi StringGrid равно StringGrid1.ColCount. Если ячейки не помещаются в таблице, появляются полосы прокрутки. При прокручивании
StringGrid1.LeftCol |
Номер столбца, видимого самым левым |
StringGrid1.TopRow |
Номер строки, видимой самой верхней |
StringGrid1.VisibleColCount |
Количество столбцов, видимых в рамках таблицы |
StringGrid1.VisibleRowCount |
Количество строк, видимых в рамках таблицы |
У таблицы StringGrid также есть свойство и для управления размером ячеек.Для всех ячеек
DefaultRowHeight - высота строк по умолчанию |
DefaultColWidth - ширина столбцов по умолчанию |
Эти значения ширины и высоты принимают все новые ячейки. При необходимости индивидуально установить ширину и высоту столбцов и строк соответственно, пользуемся свойствами
RowHeights - массив, содержащий высоты строк. То есть, например, RowHeights[5] - высота строки с индексом 5 |
ColWidths - массив, содержащий ширины столбцов. То есть, например, ColWidths[5] - ширина строки с номером 5 |
Все эти свойства настраиваем в обработчике события OnCreate Формы, так же как и надписи заголовков, располагающиеся в строках и столбцах "фиксированной" зоны таблицы. В результате таблица появляется уже в "настроенном" виде! Поскольку ячейки компонента StringGrid можно редактировать, точно так же как и строку ввода Edit, то возникает вопрос, можно ли программно установить курсор в заданную позицию в содержимом ячейки? Оказывается, есть такая возможность. Для этого требуются дополнительный тип данных на основе таблицы и вспомогательная процедура: type TGridCracker = class(TStringGrid); procedure SetCaretPosition(Grid: TStringGrid; col, row, x_pos: Integer); begin Grid.Col := Col; Grid.Row := Row; with TGridCracker(Grid) do InplaceEditor.SelStart := x_pos; end; Теперь можно установить желаемую позицию курсора в ячейке, например, по нажатию кнопки: procedure TForm1.Button1Click(Sender: TObject); begin StringGrid1.SetFocus; with StringGrid1 do SetCaretPosition(StringGrid1, Col, Row, 2); end; Правда, ещё один момент! Чтобы код сработал, нужно установить в Инспекторе Объектов значение параметра goAlwaysShoweEditor свойства Options в True. Можно это сделать также и программно, в той же процедуре нажатия кнопки: StringGrid.Options:=StringGrid.Options+[goAlwaysShoweEditor]; Отдельно требуется осветить вопрос очистки содержимого таблицы StringGrid. Так как таблица StringGrid, в отличие от, например, компонента Memo, не имеет метода для очистки содержимого сразу всех ячеек, то для удаления внесённых в таблицу ранее данных приходится очищать каждую ячейку отдельно. Делается это двумя вложенными циклами for, пробегающими по столбцам и строкам: var i, j: Integer; begin with StringGRid1 do for i:=1 to RowCount-1 do //Заголовки строк не трогаем for j:=1 to ColCount-1 do //Заголовки столбцов не трогаем Cells[j, i]:=''; end; Хотя, оказывается, есть метод для очищения содержимого целого столбца или строки: StringGrid1.Cols[i].Clear; //Очищается столбец с номером i StringGrid1.Rows[i].Clear; //Очищается строка с номером i Очевидно, очищение этими методами гораздо быстрее. Однако будут очищены и ячейки фиксированной зоны, содержащие, например, названия строк и столбцов, которые удалять не нужно. Их после очистки нужно просто "написать" заново, на глаз эта манипуляция совершенно незаметна. Для очистки всей таблицы достаточно последовательно очистить только строки или только столбцы: var i, j: Integer; begin with StringGRid1 do for i:=1 to RowCount-1 do //Заголовки столбцов не трогаем - цикл от 1 begin Rows[i].Clear; Cells[0, i]:="Заголовок строки i"; end; end; Казалось бы, можно поступить и по-другому, просто обнулить количество строк или столбцов! Однако так делать неправильно, так как при их последующем добавлении может оказаться, что каждая ячейка содержит прежние данные. А в Delphi4 даже при уменьшении количества строк или столбцов содержавшиеся в них данные вообще не пропадали, а так и повисали в воздухе! Так что так можно поступать только если в добавляемых ячейках сразу будет новое непустое содержимое.
2) Компонент Delphi MainMenu предназначен для добавления к программе главного меню, элемента, без которого не обходится ни одно из приложений для Windows. Чтобы добавить к программе Delphi главное меню, нужно расместить на Форме в произвольном месте компонент MainMenu. Компонент MainMemu невизуальный, то есть, хотя и отображается на прототипе Формы как небольшой квадрат, в работающей программе не будет виден. Опции главного меню создаются с помощью специального редактора. Редактор меню вызывается с помощью двойного щелчка по компоненту MainMenu. Первоначально меню пустое, но имеет один выделенный элемент:
Для создания первой опции (как правило, опция главного меню программы File) нужно перейти в Инспектор объектов и свойству Caption присвоить нужное название. В Windows опции меню, как правило, имеют возможность выбора при помощи сочетания клавиш ALT+<Key>, где Key - первая буква в названии данной опции должна иметь подчеркивание. Для создания такого подчеркивания перед этой буквой ставится символ &. После нажатия Enter созданный пункт меню появляется на Форме:
Обратите внимание на то, что автоматически Delphi создаёт следующий пустой пункт меню верхнего уровня. А щёлкнув в редакторе меню по синему прямоугольничку File, мы сразу получим пустой пункт меню второго уровня. Они не выделены, и отображаются белыми прямоугольниками. Щелкнув по одному из них, мы получим возможность редактировать его совершенно аналогично. Пока их свойства не заданы, в работающей программе на Форме они не появятся, и удалять в редакторе их не нужно. Далее, в редакторе меню щёлкнув по пункту меню правой клавишей мыши, мы получим контекстное меню, в котором есть пункты Insert (вставить) и Delete (удалить). Пункт Insert добавляет новый пункт меню над выделенным, а Delete - удаляет выбранный пункт. То, что мы пользуемся Инспектором объектов, говорит о том, что опция меню для Delphi - это объект, со своими свойствами и методами. И без их настройки будет пассивной структурой надписей. Для того чтобы меню выполняло свои функции, как минимум нужно описать обработчик события OnClick каждого пункта меню. Делается это совершенно стандартным способом. Опишем пункт меню, который будет закрывать программу. Выделим нижний элемент меню, в Инспекторе объектов изменим свойство Caption на Exit (ну или по-русски тоже можно - Выход). Затем перейдём на вкладку Инспектора объектов Events и щелкнем дважды мышкой по обработчику OnClick. В созданной процедуре напишем просто - Close; Всё, скомпилируем программу (нажмите F9). В работающей программе наше меню функционирует - при нажатии мышкой Exit программа закрывается. И при нажатии на клавиатуре ALT появляется подчеркивание первой буквы пунктов меню верхнего уровня - File, и далее можно выбрать нужный пункт, работая кнопками управления курсором. Во всплывающем меню выделенного элемента есть также пункт Create SubMenu, нажав на который мы создадим подменю выбранного элемента, а к его названию прибавится изображение треугольника - стрелки, указывающей на его наличие. Работа с подменю осуществляется также совершенно аналогично. Кстати, описывать обработчик OnClick пунктов меню верхнего уровня не обязятельно, раскрывание меню при щелчку мышкой происходит автоматически. Но в случае необходимости произвести какие-либо действия при раскрытии меню этот обработчик позволит это сделать. Наш компонент MainMenu обладает также возможностью сопровождать названия опций меню пиктограммами. Для этого нужно из редактора меню перейти к самому компоненту, и в Инспекторе объектов его свойству Image присвоить значение одного из компонентов ImageList, который необходимо предварительно поместить на Форму, и наполнить нужными пиктограммами (компонент ImageList описывается на странице Win32). Далее, выбрав нужный пункт меню в редакторе меню, его свойству ImageIndex нужно присвоить номер пиктограммы, который она имеет в компоненте ImageList. Для удобства выбора свойство ImageIndex имеет раскрывающийся список, содержащий пиктограммы, находящиеся в компоненте ImageList. Значение -1 означает отсутствие пиктограммы. Есть более простой альтернативный способ задать пиктограмму для пункта меню - через свойство BitMap этого пункта. Щёлкните по кнопочке, появляющейся при переходе в эту строку Инспектора объектов. Появится окно выбора файла, где можно выбрать и загрузить нужную пиктограмму. Стандартный набор пиктограмм поставляется в дистрибутиве Delphi и находится в папке Buttons по адресу C:\Program Files\Common Files\Borland Shared\Images. Правда, пиктограммы представлены в сдвоенном формате - для активного и неактивного состояния кнопки или пункта меню. Удобство применения компонента ImageList состоит в том, что он умеет разделять стандартные пиктограммы на две. Ненужную затем можно удалить, а оставшуюся сохранить для использования в других программах. Обычно это цветная пиктограмма для активного состояния, так как неактивная кнопка или меню умеют отображать свои пиктограммы в оттенках серого.
4)
4.1. Ошибки и исключительные ситуации
4.2. Классы исключительных ситуаций
4.3. Обработка исключительных ситуаций
4.3.1. Создание исключительной ситуации 4.3.2. Распознавание класса исключительной ситуации 4.3.3. Пример обработки исключительной ситуации 4.3.4. Возобновление исключительной ситуации 4.3.5. Доступ к объекту, описывающему исключительную ситуацию
4.4. Защита выделенных ресурсов от пропадания
4.4.1. Утечка ресурсов и защита от нее
4.5. Итоги
Когда программист после компиляции получает готовый к исполнению файл, он искренне верит, что программа будет работать именно так, как он хочет. Пока она в его заботливых руках, так оно обычно и бывает. Когда же программа попадает в более суровые условия — к новому пользователю и на другой компьютер — с ней может произойти все, что угодно. “Новый хозяин“ может вместо ожидаемых цифр ввести буквы, извлечь корень из отрицательного числа, делить на ноль и выполнять множество других необдуманных, часто случайных действий. Особенно это касается интерактивных (диалоговых) приложений, а таких — громадное большинство. Из этого следует, что программист должен организовать мощную оборону от всех посягательств на жизнедеятельность своей программы в процессе ее выполнения. О том, как это сделать, рассказывается в этой главе.
4.1. Ошибки и исключительные ситуации
Вы должны отдавать себе отчет в том, что в любом работающем приложении могут происходить ошибки. Причины этих ошибок бывают разными. Некоторые из них носят субъективный характер и вызваны неграмотными действиями программиста. Но существуют и объективные ошибки, их нельзя избежать при проектировании программы, но можно обнаружить во время ее работы. Примеров таких ошибок сколько угодно: недостаточный объем свободной памяти, отсутствие файла на диске, выход значений исходных данных из допустимого диапазона и т.д.
Хорошая программа должна справляться со своими ошибками и работать дальше, не зацикливаясь и не зависая ни при каких обстоятельствах. Для обработки ошибок можно, конечно, пытаться использовать структуры вида if <error> then Exit. Однако в этом случае ваш стройный и красивый алгоритм решения основной задачи обрастет уродливыми проверками так, что через неделю вы сами в нем не разберетесь. Из этой почти тупиковой ситуации среда Delphi предлагает простой и элегантный выход — механизм обработки исключительных ситуаций.
Исключительная ситуация (exception) — это прерывание нормального хода работы программы из-за невозможности правильно выполнить последующие действия.
Представим, что подпрограмма выделяет область динамической памяти и загружает в нее содержимое некоторого файла. Если в системе окажется недостаточно памяти, то данные будет негде разместить и попытка загрузить файл приведет к ошибке. Скорее всего, вся программа будет аварийно завершена из-за того, что оператор загрузки данных обратится по недоступному для программы адресу. Как этого избежать? При обнаружении проблемы подпрограмма должна создать исключительную ситуацию — прервать нормальный ход своей работы и передать управление тем операторам, которые смогут обработать ошибку. Как правило, операторы обработки исключительных ситуаций находятся в одной из вызывающих подпрограмм.
Механизм обработки исключительных ситуаций лучше всего подходит для взаимодействия программы с библиотекой подпрограмм. Подпрограммы библиотеки обнаруживают ошибки, но в большинстве случаев не знают, как на них реагировать. Вызывающая программа, наоборот, знает, что делать при возникновении ошибок, но, как правило, не умеет их своевременно обнаруживать. Благодаря механизму обработки исключительных ситуаций обеспечивается связь между библиотекой и использующей ее программой при обработке ошибок.
Механизм обработки исключительных ситуаций довольно сложен в своей реализации, но для программиста он прост и прозрачен. Для его использования в язык Delphi введены специальные конструкции try...except...end, try...finally...end и оператор raise, рассмотренные в этой главе.
4.2. Классы исключительных ситуаций
Исключительные ситуации в языке Delphi описываются классами. Каждый класс соответствует определенному типу исключительных ситуаций. Когда в программе возникает исключительная ситуация, создается объект соответствующего класса, который переносит информацию об этой ситуации из места возникновения в место обработки.
Классы исключительных ситуаций образуют иерархию, корнем которой является класс Exception. Класс Exception описывает самый общий тип исключительных ситуаций, а его наследники — конкретные виды таких ситуаций (таблица 4.1). Например, класс EOutOfMemory порожден от Exception и описывает ситуацию, когда свободная оперативная память исчерпана.
В следующей таблице приведены стандартные классы исключительных ситуаций, объявленные в модуле SysUtils. Они покрывают практически весь спектр возможных ошибок. Если их все-таки окажется недостаточно, вы можете объявить новые классы исключительных ситуаций, порожденные от класса Exception или его наследников.
Класс исключительных ситуаций |
Описание |
EAbort |
«Безмолвная» исключительная ситуация, используемая для выхода из нескольких уровней вложенных блоков или подпрограмм. При этом на экран не выдается никаких сообщений об ошибке. Для генерации исключительной ситуации класса EAbort нужно вызвать стандартную процедуру Abort. |
EInOutError |
Ошибка доступа к файлу или устройству ввода-вывода. Код ошибки содержится в поле ErrorCode. |
EExternal |
Исключительная ситуация, возникшая вне программы, например, в операционной системе. |
EExternalException |
Исключительная ситуация, возникшая за пределами программы, например в DLL-библиотеке, разработанной на языке C++. |
EHeapException |
Общий класс исключительных ситуаций, возникающих при работе с динамической памятью. Является базовым для классов EOutOfMemory и EInvalidPointer.Внимание! Создание исключительных ситуаций этого класса (и всех его потомков) полностью берет на себя среда Delphi, поэтому никогда не создавайте такие исключительные ситуации с помощью оператора raise. |
EOutOfMemory |
Свободная оперативная память исчерпана (см. EHeadException). |
EInvalidPointer |
Попытка освободить недействительный указатель (см. EHeadException). Обычно это означает, что указатель уже освобожден. |
EIntError |
Общий класс исключительных ситуаций целочисленной арифметики, от которого порождены классы EDivByZero, ERangeError и EIntOverflow. |
EDivByZero |
Попытка деления целого числа на нуль. |
ERangeError |
Выход за границы диапазона целого числа или результата целочисленного выражения. |
EIntOverflow |
Переполнение в результате целочисленной операции. |
EMathError |
Общий класс исключительных ситуаций вещественной математики, от которого порождены классы EInvalidOp, EZeroDivide, EOverflow и EUnderflow. |
EInvalidOp |
Неверный код операции вещественной математики. |
EZeroDivide |
Попытка деления вещественного числа на нуль. |
EOverflow |
Потеря старших разрядов вещественного числа в результате переполнения разрядной сетки. |
EUnderflow |
Потеря младших разрядов вещественного числа в результате переполнения разрядной сетки. |
EInvalidCast |
Неудачная попытка приведения объекта к другому классу с помощью оператора as. |
EConvertError |
Ошибка преобразования данных с помощью функций IntToStr, StrToInt, StrToFloat, StrToDateTime. |
EVariantError |
Невозможность преобразования варьируемой переменной из одного формата в другой. |
EAccessViolation |
Приложение осуществило доступ к неверному адресу в памяти. Обычно это означает, что программа обратилась за данными по неинициализированному указателю. |
EPrivilege |
Попытка выполнить привилегированную инструкцию процессора, на которую программа не имеет права. |
EStackOverflow |
Стек приложения не может быть больше увеличен. |
EControlC |
Во время работы консольного приложения пользователь нажал комбинацию клавиш Ctrl+C. |
EAssertionFailed |
Возникает при вызове процедуры Assert, когда первый параметр равен значению False. |
EPackageError |
Проблема во время загрузки и инициализации библиотеки компонентов. |
EOSError |
Исключительная ситуация, возникшая в операционной системе. |
Таблица 4.1. Классы исключительных ситуаций
Наследование классов позволяет создавать семейства родственных исключительных ситуаций. Примером такого семейства являются классы исключительных ситуаций вещественной математики, которые объявлены в модуле SysUtils следующим образом.
type EMathError = class(Exception); EInvalidOp = class(EMathError); EZeroDivide = class(EMathError); EOverflow = class(EMathError); EUnderflow = class(EMathError); |
Класс исключительных ситуаций EMathError является базовым для классов EInvalidOp, EZeroDivide, EOverflow и EUnderflow, поэтому, обрабатывая исключительные ситуации класса EMathError, вы будете обрабатывать все ошибки вещественной математики, включая EInvalidOp, EZeroDivide, EOverflow и EUnderflow.
Нетрудно заметить, что имена классов исключений начинаются с буквы E (от слова Exception). Этого правила полезно придерживаться при объявлении собственных классов исключений, например:
type EMyException = class(Exception) MyErrorCode: Integer; end; |
Как описываются классы исключительных ситуаций понятно, рассмотрим теперь, как такие ситуации обрабатываются.