Изменить регистр? Легко!

© 2003 Сергей Каптарь

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

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

Итак, что требуется?

При нажатии клавиши, например F11, в любом TEdit любой формы программы, необходимо получить диалоговое окно:


рис.1 Диалог изменения текста.

с помощью которого преобразовать введённую в TEdit строку целиком или фрагмент строки. При чём если TEdit недоступен для изменения (ReadOnly=True или Enabled=False) или PasswordChar не равен #0, то диалогового окна показывать ненужно!

Цель ясна, приступаем...

Создаём форму (TForm2) изображённую на рис.1. Стиль формы делаем bsDialog и прячем кнопки максимизации и сворачивания окна. Всем компонентам TRadioButton устанавливаем следующие обработчики:


procedure TForm2.RadioButton1DblClick(Sender: TObject);
begin
 ModalResult:=mrOk;
end;

procedure TForm2.RadioButton1KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
 if Key=VK_Return then ModalResult:=mrOk;
end;

Кнопкам "ОК" и "Отмена" устанавливаем ModalResult в mrOk и mrCancel соответственно, дополнительно для кнопки "Отмена" устанавливаем Cancel=True.

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


TForm2 = class(TForm)
...
public
    { Public declarations }
    function GetUserChoice: integer;
end;
...

function TForm2.GetUserChoice: integer;
begin
 if RadioButton1.Checked then Result:=1
 else
  if RadioButton2.Checked then Result:=2
  else
   if RadioButton3.Checked then Result:=3
   else
    if RadioButton4.Checked then Result:=4
    else
     if RadioButton5.Checked then Result:=5
     else
      Result:=-1;
end;

Теперь переходим к главной форме проекта (TForm1). Исходный код будет поясняться в комментариях:


TForm1 = class(TForm)
...
private
    { Private declarations }
    ...
    procedure AppMessage(var Msg: TMsg; var Handled: Boolean); //глобальный обработчик
    procedure ChangeRegister(Msg: TMsg);  //процедура изменения текста
    ...
public
    { Public declarations }
     ...
end;

...

procedure TForm1.FormCreate(Sender: TObject);
begin
 ...
 Application.OnMessage:=AppMessage;  //устанавливаем глобальный обработчик сообщений
 ...
end;

//==============================================================================
//Глобальный обработчик сообщений
//==============================================================================
procedure TForm1.AppMessage(var Msg: TMsg; var Handled: Boolean); 
begin
 ...
 //если нажата клавиша F11, то...
 if (Msg.message=WM_KEYDOWN) and (Msg.wParam=VK_F11) then ChangeRegister(Msg);
 ...
end;

//==============================================================================
//Функция возвращает имя класса по хэндлу (Handle)
//==============================================================================
function GetClassNameStr(h: HWND): String;
var
 ClsName: array[0..127] of Char;
begin
 GetClassName(h,ClsName,128);
 Result:=Trim(StrPas(ClsName));
end;

//==============================================================================
//Функция возвращает истину, если указанному хэндлу соответствует имя класса
//==============================================================================
function IsClass(h: HWND; ClassName: String): boolean;
begin
 Result:=UpperCase(GetClassNameStr(h))=UpperCase(ClassName);    
end;

//==============================================================================
//Функция возвращает истину, если окно на которое указывает хэндл имеет стиль ReadOnly
//==============================================================================
function IsReadOnly(h: HWND): boolean;
begin
 Result:=(GetWindowLong(h,GWL_STYLE) and ES_READONLY)<>0;
end; 

//==============================================================================
//Функция возвращает текст окна на который указывает хэндл
//==============================================================================
function GetText(h: HWND): String;
var
 TXT: PChar;
 Size: integer;
begin
 //определяем размер
 Size:=GetWindowTextLength(h)+1;
 //выделяем память
 GetMem(TXT,Size);
 //получаем текст заданного размера
 GetWindowText(h,TXT,Size);         
 Result:=StrPas(TXT);
 //освобождаем память
 FreeMem(TXT,Size);
end;

//==============================================================================
// Следующие пять функций для обработки строк:
//TextToLowerCase, TextToUpperCase, TextToFirstUpperCase,
//TextToInversCase, ChangeLocalStr
//Во всех функциях входные параметры одинаковые:
//S: String; StartPos,EndPos: integer
//где S - изменяемая строка
// StartPos,EndPos - начальная и конечная позиция для указания изменяемого
//фрагмента
//==============================================================================

//==============================================================================
//Функция приводит фрагмент строки к нижнему регистру
//==============================================================================
function TextToLowerCase(S: String; StartPos,EndPos: integer): String;
var
 i: integer;
begin
 //проверяем границы диапазона
 if EndPos>Length(S) then EndPos:=Length(S);  
 if StartPos<1 then StartPos:=1;
 //перебираем все символы от начальной позиции до конечной
 for i:=StartPos to EndPos do
  S[i]:=AnsiLowerCase(S[i])[1];
 Result:=S;
end;

//==============================================================================
//Функция приводит фрагмент строки к верхнему регистру
//==============================================================================
function TextToUpperCase(S: String; StartPos,EndPos: integer): String;
var
 i: integer;
begin
 //проверяем границы диапазона
 if EndPos>Length(S) then EndPos:=Length(S);
 if StartPos<1 then StartPos:=1;
 //перебираем все символы от начальной позиции до конечной
 for i:=StartPos to EndPos do
  S[i]:=AnsiUpperCase(S[i])[1];
 Result:=S;
end;

//==============================================================================
//Функция приводит фрагмент строки к нижнему регистру, а так же первые буквы
//каждого слова к верхнему регистру
//==============================================================================
function TextToFirstUpperCase(S: String; StartPos,EndPos: integer): String;
var
 i: integer;
 isSpase: boolean;  //True - предыдущий символ был пробел, False - предыдущий символ не пробел
begin
 //проверяем границы диапазона
 if EndPos>Length(S) then EndPos:=Length(S);
 if StartPos<1 then StartPos:=1;
 if StartPos=1 then 
  isSpase:=True
 else
  isSpase:=S[StartPos-1]=#32;
 //перебираем все символы от начальной позиции до конечной
 for i:=StartPos to EndPos do
 begin
  if (S[i]<>#32) then
  begin
   //если предыдущий символ был пробел, то текущий символ приводим к верхнему регистру
   if isSpase then
   begin
    S[i]:=AnsiUpperCase(S[i])[1];
    isSpase:=False;
   end
   else
    S[i]:=AnsiLowerCase(S[i])[1];
  end
  else isSpase:=True;
 end;
 Result:=S;
end;

//==============================================================================
//Функция выполняет инверсию регистра в выделенном фрагменте строки
//==============================================================================
function TextToInversCase(S: String; StartPos,EndPos: integer): String;
var
 i: integer;
function IsLowerCase(Ch: Char): boolean;
begin
 Result:=Ch=AnsiLowerCase(Ch);
end;
begin
 //проверяем границы диапазона
 if EndPos>Length(S) then EndPos:=Length(S);
 if StartPos<1 then StartPos:=1;
 //перебираем все символы от начальной позиции до конечной
 for i:=StartPos to EndPos do
  if IsLowerCase(S[i]) then
   S[i]:=AnsiUpperCase(S[i])[1]
  else
   S[i]:=AnsiLowerCase(S[i])[1];
 Result:=S;
end;

//==============================================================================
//qwerty<>йцукен
//==============================================================================
function ChangeLocalStr(S: String; StartPos,EndPos: integer): String;
const
 EnStr='`~@#$^&|qQwWeErRtTyYuUiIoOpP[{]}aAsSdDfFgGhHjJkKlL;:'+''''+'"zZxXcCvVbBnNmM,<.>/?';
 RuStr='ёЁ"№;:?/йЙцЦуУкКеЕнНгГшШщЩзЗхХъЪфФыЫвВаАпПрРоОлЛдДжЖэЭяЯчЧсСмМиИтТьЬбБюЮ.,';
var
 i, ien, iru: integer;
begin
 //проверяем границы диапазона
 if EndPos>Length(S) then EndPos:=Length(S);
 if StartPos<1 then StartPos:=1;
 //перебираем все символы от начальной позиции до конечной
 for i:=StartPos to EndPos do
 begin
  //ищем позицию в "английской" строке
  ien:=pos(S[i],EnStr);
  //если нашёл, то заменяем символ на соответствующий символ "русской" строки
  if ien>0 then
   S[i]:=RuStr[ien]
  else
  begin
   //Ищем позицию в "русской" строке
   iru:=pos(S[i],RuStr);
   if iru>0 then S[i]:=EnStr[iru]
  end;
 end;
 Result:=S;
end;

//==============================================================================
//Процедура смены регистра введённого текста в TEdit. (По нажатии F11)
//==============================================================================
procedure TForm1.ChangeRegister(Msg: TMsg);
var
  Form2: TForm2; 
  UserChoice, StartPos, EndPos, i, OriginalStartPos, OriginalEndPos: integer;
  isSelected: boolean;
  S, S1: String;
  isOk: boolean;
begin
 //если ссылка указывает на TEdit и этот элемент управления видимый (Visible),
 //разрешённый (Enabled), не ReadOnly и PasswordChar=#0, то...
 if IsClass(Msg.hwnd,'TEDIT') and  	//проверяем класс элемента на который указывает Handle
    IsWindowVisible(Msg.hwnd) and     //Visible ?
    (IsWindowEnabled(Msg.hwnd)) then  //Enabled ?
  if not IsReadOnly(Msg.hwnd) and     //ReadOnly?
    (SendMessage(Msg.hwnd,EM_GETPASSWORDCHAR,0,0)=0) then	//PasswordChar ?
  begin
   //получаем текст окна
   S:=GetText(Msg.hwnd);
   //если текст есть, то...
   if S<>'' then
   begin
    //создаём форму TForm2
    Form2:=TForm2.Create(Application);
    //определяем начальную и конечную позицию выделенного блока
    StartPos:=0; EndPos:=0;
    i:=SendMessage(Msg.hwnd,EM_GETSEL,StartPos,EndPos);
    //запоминаем результат для дальнейшего восстановления выделения
    OriginalStartPos:=LoWord(i);
    OriginalEndPos:=HiWord(i);  
    //а с этими значениями мы будем работать
    StartPos:=OriginalStartPos;
    EndPos:=OriginalEndPos;
    //если есть выделенный текст, то устанавливаем галочку "Изменить только выделенный текст"
    Form2.CheckBox1.Enabled:=StartPos<>EndPos;
    Form2.CheckBox1.Checked:= Form2.CheckBox1.Enabled;
    //открываем диалоговое окно и запоминаем на какую кнопку пользователь нажал (ОК или Отмена)
    isOk:=Form2.ShowModal=mrOk;
    //если пользователь нажал кнопку "ОК", то...
    if isOk then
    begin
     //запоминаем пользовательский выбор
     UserChoice:=Form2.GetUserChoice;	  //выбор пользователя
     isSelected:=Form2.CheckBox1.Checked; //изменять только выделенный текст
    end else
    begin
     UserChoice:=-1;
     isSelected:=False;
    end; 
    //освобождаем память занимаемую диалоговым окном
    Form2.Free;   
    if isOk then
    begin
     //если пользователь выбрал "Изменить только выделенный текст", то...
     //устанавливаем границы, так как строки в pascal нумеруются с 1, то...
     StartPos:=StartPos+1;
     if isSelected then
     begin
      //проверяем и при необходимости корректируем границы диапазона
      if StartPos=EndPos then
      begin
       StartPos:=1;
       EndPos:=Length(S);
      end; 
      if StartPos>EndPos then
      begin
       i:=StartPos;       
       StartPos:=EndPos;
       EndPos:=i;
      end;
     end
     else
     begin
      //выбираем весь диапазон
      StartPos:=1;
      EndPos:=Length(S);
     end; 
     //в зависимости от выбора пользователя вызываем ту или иную функцию
     //для обработки текста
     case UserChoice of
      1: S1:=TextToLowerCase(S,StartPos,EndPos);
      2: S1:=TextToUpperCase(S,StartPos,EndPos);
      3: S1:=TextToFirstUpperCase(S,StartPos,EndPos);
      4: S1:=TextToInversCase(S,StartPos,EndPos);
      5: S1:=ChangeLocalStr(S,StartPos,EndPos);
     end;
     //если текст изменился, то сохраняем его в TEdit
     if S1<>S then
     begin
      SetWindowText(Msg.hwnd,PChar(S1));
      //восстанавливаем курсор в исходное положение, а так же
      //восстанавливаем выделение, если оно было.
      SendMessage(Msg.hwnd,EM_SETSEL,OriginalStartPos,OriginalEndPos);
     end;
    end; 
   end;
  end;
end;  

Вот и всё! Буду рад услышать ваши предложения и замечания по статье!

Желающие могут взглянуть на пример (161K) и его исходные тексты (5.74K)

Copyright© 2003 Сергей Каптарь  Специально для Delphi Plus

2011123456789101112
2010123456789101112
2009123456789101112
2008123456789101112
2007123456789101112
2006123456789101112
2005123456789101112
2004123456789101112
2003123456789101112
2002123456789101112
2001123456789101112
2000123456789101112
1999123456789101112

Последние статьи
Литература