Delphi - сбориник статей

       

Улучшение вспомогательных окон среды Delphi


Владимир Коднянко,

В практике программирования в среде часто приходится пользоваться вспомогательными окнами, в которых необходимо вывести сообщение - однострочное или многострочное или задать вопрос (также однострочный или многострочный) с тем, чтобы получить от пользователя программы ответ, который необходим для разрешения какой-либо ситуации. Задача эта простая и даже для малоопытного программиста не представляет особых затруднений: можно использовать процедуру ShowMessage, функцию MessageDlgPos стандартного модуля Dialogs.pas или подобные им подпрограммы. Однако есть несколько "но":

  • для ускорения программирования или отладки программы обычно возникает потребность в том, чтобы с наименьшими затратами времени программировать вывод констант и значений переменных наиболее часто используемых типов (обычно строковых и числовых) с помощью одной или нескольких "подручных" подпрограмм, не тратя время на конвертацию из одного типа в другой (чаще строковый); для большинства случаев это можно сделать воспользовавшись, например, типом Variant;
  • использование стандартных подпрограмм, например ShowMessage, иногда не удовлетворяет программиста по той причине, что это окно всегда выводится в центре экрана, и если окно приложения находится в этот момент не в центре, а в каком-нибудь углу экрана, то такое расположение окон нежелательно; можно, конечно, воспользоваться другой подпрограммой, позволяющей позиционировать окно где угодно, но "угадать", где в данный момент находится активное окно, обычными средствами непросто; наиболее приемлемой можно считать ситуацию, когда окно вопроса или сообщения имеет общий центр с активной формой, однако "не теряется" за пределами экрана если в большом окне активной формы ее центр находится вне экрана;
  • площадь стандартных окон достаточно велика из-за неоправданно низкого расположения рисунка и кнопок в окне, а также довольно большого расстояния от кнопок до нижнего края окна; можно также улучшить вывод надписи на метке, позиционируя ее по отношению к рисунку в зависимости от числа строк на метке; такие изменения позволят, во-первых, уменьшить высоту окна и, во-вторых, улучшить расположение надписи на нем;
  • если на компьютер устанавливается Delphi (англоязычная), то чтобы надписи в окнах сообщений и вопросов (в заголовках, на кнопках) были русскоязычными, надо затратить дополнительные усилия по русификации надписей, что требует отдельной работы: здесь желательно иметь подпрограммы, которые способны сразу "выдавать" надписи в окнах на русском языке вне зависимости от того, русифицирована Delphi или нет.

Разрешение этих "но" является целью настоящего сообщения.

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

var // кнопки ButtonEngCaptions: array[1..11] of string = ('Yes', 'No', 'OK', 'Cancel', 'Abort', 'Retry', 'Ignore', 'All', 'NoToAll','YesToAll', 'Help'); ButtonRusCaptions: array[1..11] of string = ('Да', 'Нет', 'OK', 'Отмена', 'Прервать','Повтор', 'Пропуск', 'Все', 'Нет Всем','Да Всем', 'Помощь'); // заголовки окон MsgEngCaptions: array[1..4] of string = ('Confirm', 'Information', 'Warning', 'Error'); MsgRusCaptions: array[1..4] of string = ('Подтвердите', 'Сообщение','Предупреждение','Ошибка');

Далее возьмем стандартную функцию MessageDlgPosHelp модуля Dialogs.pas и коррекцией ее кода создадим новую функцию KdnMessageDlg (текст функции снабжен необходимыми комментариями):



function KdnMessageDlg(MsgVariant: string; DlgType: TMsgDlgType; Buttons: TMsgDlgButtons): Integer; var w1,w2,h1,h2,t2,L2,cx,cy: Integer; ScreenActFormVisBoo: boolean; i,j: Integer; F: TForm; Msg,s: ^String; begin New(Msg); New(s); Msg^:= MsgVariant; // конвертируем Variant в строку F:= CreateMessageDialog(Msg^,DlgType,Buttons); with F do try w1:=0; w2:=0; h1:= 0; // рабочие переменные // русифицируем надпись на шапке F-формы for i:= 1 to 4 do if Caption = MsgEngCaptions[i] then Caption:= MsgRusCaptions[i]; // изменяем положение элементов формы и русифицируем кнопки for i:= 0 to F.ComponentCount-1 do begin // приподнимаем рисунок if F.Components[i] is TImage then With F.Components[i] as TImage do Top:= Top-4; // позиционируем метку относительно рисунка // в зависимости от числа строк if F.Components[i] is TLabel then With F.Components[i] as TLabel do begin w1:=1; // вычислим число строк в метке if Length(Caption)>2 then for j:= 1 to Length(Caption)-2 do if Copy(Caption,j,2) = #13#10 then Inc(w1); if w1=1 then Top:= Top+2 else if w1=2 then Top:= Top-2 else Top:= Top-4; w2:= Top+height; // положение нижней части метки end; // русифицируем надписи на кнопках и позиционирум кнопки // в зависимости от числа строк метки if F.Components[i] is TButton then With F.Components[i] as TButton do begin s^:= Caption; // приведем надпись к виду ButtonEngCaptions Delete(s^,Pos('&',s^),1); s^:= AnsiUpperCase(DelSymbAll(s^,' ')); for j:=1 to 11 do // поиск надписи if s^ = AnsiUpperCase(ButtonEngCaptions[j]) then Caption:= ButtonRusCaptions[j]; // русификация if w1=1 then Top:= w2+20 else // позиционирование if w1=2 then Top:= w2+12 else Top:= w2+10; h1:= Top+height; // положение нижней части кнопок end; end; // for i height:= h1+42; // подбираем подходящую высоту формы // вычисляем положение F-формы // 1. определяем центр активной формы cx:= -1; cy:= -1; // координаты центра активной формы ScreenActFormVisBoo:= false; // наличие и видимость активной формы if Screen.ActiveForm <> Nil then if Screen.ActiveForm.Visible then begin w2:= Screen.ActiveForm.width; h2:= Screen.ActiveForm.height; t2:= Screen.ActiveForm.Top; L2:= Screen.ActiveForm.Left; cx:= L2 + w2 div 2; // координаты центра активной формы cy:= t2 + h2 div 2; ScreenActFormVisBoo:= true; end; // 2. определяем координаты левого верхнего угла F-формы w1:= width; h1:= height; // параметры F-окна if ScreenActFormVisBoo then // активная форма видима begin w2:= Screen.width; // размеры экрана h2:= Screen.height; Top:= cy - h1 div 2; // F.Top Left:= cx - w1 div 2; // F.Left // F-окно должо быть полностью в экране if Top<0 then Top:=0 else if Top>h2-h1 then Top:= h2-h1; Left:= cx - w1 div 2; if Left<0 then Left:=0 else if Left>w2-w1 then Left:= w2-w1; end else Position:= poScreenCenter; // активной формы нет или невидима Result:= ShowModal; finally // освобождаем память Dispose(Msg); Dispose(s); F.Free; Application.ProcessMessages; // убираем следы F-окна end; end;

где функция DelSymbAll имеет код

function DelSymbAll(s: String; Ch: Char): String; // удаляет символ везде var i: Integer; begin i:= pos(Ch,s); while i>0 do begin Delete(s,i,1); i:= pos(Ch,s); end; Result:= s; end;

Теперь всякое окно, построенное на основе функции KdnMessageDlg, будет иметь с активной формой общий центр, за исключением тех случаев, когда центрирование увело бы любую часть F-окна за пределы экрана (F-окно будет всегда находиться полностью в экране), все надписи русифицированы, метка "правильно" позиционирована относительно рисунка.

  • Используя KdnMessageDlg построим процедуру - усовершенствованый аналог стандартной процедуры ShowMessage:

procedure KdnMessage(Msg: Variant); //однострочное сообщение begin KdnMessageDlg(Msg, mtInformation,[mbOK]); end;

Несколько примеров обращения к процедуре:

KdnMessage(24); // числовой целочисленный тип аргумента KdnMessage(-224.89); // числовой вещественный тип аргумента KdnMessage('Это строка'); // строковый тип KdnMessage(Now); // тип TDateTime KdnMessage(Tim); // тип TTime KdnMessage(Dat); // тип TDate

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

  • На основе предыдущей процедуры построим многострочное сообщение:

procedure KdnMessageV(Msg: array of Variant); //многострочное сообщение begin KdnMessage(DinVarArrToStrs(Msg); end;

где функция DinVarArrToStrs имеет код:

function DinVarArrToStrs(a: array of Variant): Variant; // конвертация Variant-массива в многострочный Variant var s: array of String; i: byte; begin SetLength(s,2); s[0]:=''; if Length(a)>0 then begin s[0]:= a[0]; if Length(a)>1 then for i:= 1 to Length(a)-1 do begin s[1]:= a[i]; s[0]:= s[0]+''#13#10''+s[1]; end; end; Result:= s[0]; s:= Nil; end;

Пример обращения к процедуре:

KdnMessageV([1355,-15.87,Now,DateOf(Now),TimeOf(Now)]);

и окно, отображающее результат обращения:

  • Аналогичным образом создадим однострочное окно для вывода вопроса с целью получения ответа от пользователя программы

function KdnYesNo(Question: Variant): boolean; // однострочный вопрос begin Result:= KdnMessageDlg(Question,mtConfirmation, [mbYes,mbNo]) = mrYes; end;

и соответствующее многострочное окно

function KdnYesNoV(Question: array of Variant): boolean; // многострочный вопрос begin Result:= KdnYesNo(DinVarArrToStrs(Question)); end;

Примеры обращения к функциям:

if KdnYesNo('Удалить рисунок ?') then DeleteFile(ImFile); if not KdnYesNoV(['Вы действительно желаете', 'удалить непустую папку', ExeDir,'?']) then exit;

Соответствующие окна показаны ниже.

Точно также можно создать окна с тремя кнопками:

function KdnYesNoCancel(Question: Variant): byte; // однострочное окно с тремя кнопками var r: Integer; begin r:= KdnMessageDlg(Question,mtConfirmation, [mbYes,mbNo,mbCancel]); Result:= 3; // на случай выхода вне кнопок if r = mrYes then Result:= 1 else if r = mrNo then Result:= 2; end; function KdnYesNoCancelV(Question: array of Variant): byte; // многострочное окно с тремя кнопками begin Result:= KdnYesNoCancel(DinVarArrToStrs(Question)); end;

Ограничимся примером обращения к последней функции

if KdnYesNoCancelV(['Вы действительно желаете', 'удалить непустую папку', ExeDir,'?']) = 1 then if KdnYesNo('Подтвердите') then DeleteFolder(ExeDir);

Первое окно, которое появится в результате исполнения этого кода, имеет вид:

Аналогично на основе функции KdnMessageDlg могут быть без труда созданы другие подобные процедуры и функции.



Содержание раздела