Модераторы: Snowy, bartram, MetalFan, bems, Poseidon, Riply
  

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Сабклассинг и суперклассинг в Delphi, Для начинающих 
:(
    Опции темы
Rrader
  Дата 1.11.2005, 14:59 (ссылка) |    (голосов:1) Загрузка ... Загрузка ... Быстрая цитата Цитата


Inspired =)
***


Профиль
Группа: Экс. модератор
Сообщений: 1535
Регистрация: 7.5.2005

Репутация: 29
Всего: 191



"Сабклассинг и суперклассинг в Delphi для начинающих"

Здравствуйте!

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

Сабклассинг

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

Оконная процедура

Оконная процедура (window procedure) - специальная функция любого окна, имеющего дескриптор, которая принимает и обрабатывает все поступающие окну сообщения (от других программ или от Windows). Оконная процедура является косвенно вызываемой (callback) пользовательской (user-defined) функцией. Соответственно, реакцию на сообщения задаёт программист.

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

Оконная процедура обычно назначается при создании окна, когда заполняется структура класса последнего TWndClass(Ex).

Оконная процедура имеет такой прототип:
Код

function XWindowProc(hWnd: HWND; uMsg: UINT; 
  wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;

Где X - любой префикс (можно и опустить), по которому можно идентифицировать
нужную оконную процедуру (например, Edit или New).

Рассмотрим, какие параметры передаются при вызове оконной процедуры. В параметре hWnd передаётся дескриптор окна, классу которого принадлежит оконная процедура. В параметре uMsg передаётся идентификатор поступившего сообщения. В параметрах wParam и lParam передаётся дополнительная информация, которая зависит от типа посланного сообщения.

Возвращаемый функцией результат должен определить программист.

Рекомендуется обрабатывать сообщения через оператор case:
Код

case Msg of
  WM_DESTROY:
end;


Чтобы сообщение не обрабатывалось оригинальной оконной процедурой, необходимо после своих действий осуществить выход из блока case:
Код

case Msg of
  WM_CLOSE:
    begin
      MessageBox(0, 'WM_CLOSE', 'Caption', MB_OK);
      { Осуществляем выход из текущей процедуры }  
      Exit;
    end;
end; 

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

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

...
{ Тип первого параметра представляет собой простой указатель }
TFarProc = Pointer; 
TFNWndProc = TFarProc;
...
function CallWindowProc(lpPrevWndFunc: TFNWndProc; hWnd: HWND; uMsg: UINT;
  wParam: WAPRAM; lParam: LPARAM): LRESULT; stdcall;


Функция CallWindowProc позволяет нам, по сути, менять поведение окна, ведь мы можем сабклассировать его множество раз с сохранением адресов оконных процедур, а потом вызывать нужные оконные процедуры по надобности. Но на практике эта функция используется для вызова одной оригинальной оконной процедуры окна, которая была до его сабклассирования.

После детального рассмотрения основ сабклассинга непосредственно перейдём к его реализации в Delphi.

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

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

Объявление функции:
Код

function SetWindowLong(hWnd: HWND; nIndex: Integer;
  dwNewLong: LongInt): LongInt; stdcall;


Параметр hWnd определяет окно, с которым будет производиться работа. Параметр nIndex определяет индекс атрибута, который мы хотим изменить. Пока нас будут интересовать значения GWL_WNDPROC и GWL_USERDATA. Первый индекс определяет, что изменения затронут оконную процедуру окна, второй - то, что будет изменена специальная внутренняя четырёхбайтовая переменная, которой обладает каждое окно. В ней удобно хранить адрес старой оконной процедуры при сабклассинге.

Рассмотрим, как по шагам засабклассировать окно. smile
  •  Создаём заготовку новой оконной процедуры;
  •  Помещаем в переменную GWL_USERDATA адрес старой оконной процедуры;
  •  Изменяем адрес оконной процедуры на новый.
Последние два действия можно объединить в одно, так как функция SetWindowLong возвращает предыдущее значение изменённого параметра.

Далее я публикую примеры кода, в которых будут рассмотрены способы сабклассирования окон как средствами VCL, так и средствами WinAPI. Все примеры кода хорошо комментированы.

Сабклассинг окон на VCL

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

Ниже приведен пример сабклассирования компонента TEdit таким образом, чтобы последний не реагировал на вставку текста:
Код

unit UMain;

interface

uses
  Windows, Messages, SysUtils, Classes,
  Graphics, Controls, Forms, Dialogs,
  StdCtrls;

type
  TMainForm = class(TForm)
    procedure FormCreate(Sender: TObject);    
  private
    { Private declarations }
  public
    { Public declarations }
  end;

  { Новый класс с дополнительным методом,
     который вызвается при сообщении WM_PASTE }
   
  TNewEdit = class(TEdit)
  protected
    { Обработчик сообщения } 
    procedure OnWMCopy(var Msg: TWMPaste); message WM_PASTE;
  end;

var
  MainForm: TMainForm;

implementation

{$R *.dfm}

{ TNewEdit }

procedure TNewEdit.OnWMCopy(var Msg: TWMPaste);
begin
  { Игнорируем сообщение }
  Msg.Result := 0;
end;

procedure TMainForm.FormCreate(Sender: TObject);
begin
  { Создание и размещение компонента на форме }
  with TNewEdit.Create(Self) do
  begin
    Parent := Self;
    Left := 8;
    Top := 8;
    Width := MainForm.Width - 23;
    { Следующий метод работать не будет }
    PasteFromClipboard;
  end;
end;

end.

Таким образом, чтобы засабклассировать оконный компонент, нужно просто реализовать свой обработчик сообщений. Есть еще один способ, который заключается в модификации оконной процедуры компонента на VCL-уровне:
Код

unit UMain;

interface

uses
  Windows, Messages, SysUtils, Classes,
  Graphics, Controls, Forms, Dialogs,
  StdCtrls;

type
  TMainForm = class(TForm)
    procedure FormCreate(Sender: TObject);  
  private
    { Private declarations }
  public
    { Public declarations }
  end;
     
  TNewEdit = class(TEdit)  
  protected
    { Перекрытая оконная процедура компонента }
    procedure WndProc(var Msg: TMessage); override;
  end;

var
  MainForm: TMainForm;

implementation

{$R *.dfm}

{ TNewEdit }

procedure TNewEdit.WndProc(var Msg: TMessage);
begin
  case Msg.Msg of
    WM_PASTE:
      begin
        Msg.Result := 0;
        { Звуковой сигнал, оповещающий пользователя о
          невозможности вставки текста }
        MessageBeep(0);
        { Выход после обработки необходим, чтобы
          оригинальная оконная процедура не имела
          возможности обработать WM_PASTE; в противном
          случае вставка текста всё равно произойдёт }
        Exit;
      end;
  end;
  { Не забывайте вызывать унаследованную оконную процедуру }
  inherited WndProc(Msg);
end;

procedure TMainForm.FormCreate(Sender: TObject);
begin
  { Создание и размещение компонента на форме }
  with TNewEdit.Create(Self) do
  begin
    Parent := Self;
    Left := 8;
    Top := 8;
    Width := MainForm.Width - 23;
    { Следующий метод работать не будет }
    PasteFromClipboard;
  end;
end;

end.

Этот способ по функциональности ничем не отличается от первого (только озвучкой).

Вот и всё! Думаю, что Вы разобрались в примерах и мы можем переходить к сабклассингу средствами Windows API. Ту часть кода примеров, которые не относятся к теме статьи, я снабдил краткими комментариями. 

Сабклассинг окон с помощью Windows API

В следующем примере будет показано, как усовершенствовать кнопку (Button) и поле ввода (Edit). Вот список усовершенствований:

1) Для кнопки: создать такую кнопку, которая при нажатии левой кнопки мыши отображала бы текущую дату;
2) Для поля ввода: запретить контекстное меню; установить шрифт для текста синего цвета

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

Что касается поля ввода, то для запрета контекстного меню необходимо проигнорировать сообщение WM_CONTEXTMENU, после чего осуществить выход из оконной процедуры. Для изменения цвета текста необходимо использовать функция SetTextColor для контекста Edit'а. Этот контекст можно получить, обрабатывая сообщение WM_CTLCOLOREDIT (обратите внимание, что это сообщение посылается родительскому окну поля ввода). Данное сообщение посылается при каждой отрисовке Edit'а, передавая в параметре wParam контекст для рисования. Не следует забывать включить прозрачность фона функцией SetBkMode (хотя для нашего примера эта функция ничего не изменяет, попробуйте использовать другие цвета, чтобы убедиться в её надобности). 

Код

program SampleProject03;

{$R *.res}
{$R WinXP.res} 

uses
  Windows,
  Messages,
  SysUtils;

procedure InitCommonControls; stdcall; external 'comctl32.dll';  

const
  { Идентификатор таймера }
  BtnTimer = 450;
  { Константы с заголовками дочерних окон }
  StaticInfoText = 'Метка без сабклассирования';
  BtnText = 'Кнопка для сабклассирования';

var
  { Главное окно }
  Wnd: HWND;
  { Три дочерних компонента для сабклассирования }
  Btn, Edit, InfoStatic: HWND;  

{ Устанавливает для окна AWindow шрифт для контролов по умолчанию }
procedure SetDefFont(AWindow: HWND);
begin
  SendMessage(AWindow, WM_SETFONT, GetStockObject(DEFAULT_GUI_FONT), 1);
end;

{ Косвенно-вызваемая процедура сообщений таймера }
{ Эта процедура выполняется при каждом срабатывании таймера }
procedure BtnTimerProc(hWnd: HWND; uMsg, idEvent: UINT;
  dwTime: DWORD); stdcall;
var
  { Переменная, куда будет помещено текущее время }
  Time: TSystemTime;
  { Для анализа времени }
  Hour, Minute, Second: String;
begin
  { Получаем время }
  GetLocalTime(Time);
  { Инициализируем переменные }
  Hour := IntToStr(Time.wHour);
  Minute := IntToStr(Time.wMinute);
  Second := IntToStr(Time.wSecond);
  { Добавляем нули при необходимости }
  if Length(Hour) = 1 then Hour := '0' + Hour;
  if Length(Minute) = 1 then Minute := '0' + Minute;
  if Length(Second) = 1 then Second := '0' + Second;
  { Отображаем дату }
  SetWindowText(hWnd, PChar(Hour + ':' + Minute + ':' + Second));
end;

{ Модифицированная оконная процедура поля ввода }
function EditWinProc(hWnd: HWND; uMsg: UINT;
  wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
begin  
  case uMsg of
    { Запрещаем показ контекстного меню }
    WM_CONTEXTMENU:
      begin
        Result := 0;
        MessageBeep(0);
        Exit;
      end;
  end;
 { Не забываем вызвать оригинальную оконную процедуру }
  Result := CallWindowProc(Pointer(GetWindowLong(hWnd, GWL_USERDATA)),
    hWnd, uMsg, wParam, lParam);
end;

{ Модифицированная оконная процедура кнопки }
function BtnWinProc(hWnd: HWND; uMsg: UINT;
  wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
begin
  case uMsg of
    { При нажатии мыши запускаем таймер, интервал - 10 миллисекунд }
    WM_LBUTTONDOWN: SetTimer(hWnd, BtnTimer, 10, @BtnTimerProc);

    { При отпускании мыши уничтожаем таймер }
    WM_LBUTTONUP:
      begin
        KillTimer(hWnd, BtnTimer);
        { Восстанавливаем прежний текст }
        SetWindowText(hWnd, BtnText); 
      end;  
  end;
  { Не забываем вызвать оригинальную оконную процедуру }
  Result := CallWindowProc(Pointer(GetWindowLong(hWnd, GWL_USERDATA)),
    hWnd, uMsg, wParam, lParam);
end;

{ Оконная процедура главного окна }
function MainWinProc(hWnd: HWND; uMsg: UINT;
  wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;

  { Конвертирует сроку PChar в String }
  function StrPas(const AStr: PChar): String;
  begin
    Result := AStr;
  end; 

begin  
  case uMsg of
    { Здесь будет произведено создание дочерних окон }
    WM_CREATE:
      begin
        InfoStatic := CreateWindowEx(0, 'Static', StaticInfoText,
          WS_CHILD or WS_VISIBLE or SS_LEFT,
          8, 8, 270, 16, hWnd, 0, HInstance, nil);
        SetDefFont(InfoStatic);
        { Edit }
        Edit := CreateWindowEx(WS_EX_CLIENTEDGE, 'Edit', nil,
          WS_CHILD or WS_VISIBLE or ES_LEFT,
          8, 28, 300, 21, hWnd, 0, HInstance, nil);
        SetDefFont(Edit);
        { Выделяем весь текст }
        SendMessage(Edit, EM_SETSEL, 0, -1);
        { Далее делаем сабклассинг поля ввода }
        SetWindowLong(Edit, GWL_USERDATA,
          SetWindowLong(Edit, GWL_WNDPROC, LongInt(@EditWinProc)));
        { Button }
        Btn := CreateWindowEx(0, 'Button', BtnText, WS_CHILD or WS_VISIBLE
           or BS_PUSHBUTTON, 8, 52, 300, 25, hWnd, 0, HInstance, nil);
        SetDefFont(Btn); 
        { Далее делаем сабклассинг кнопки }
        SetWindowLong(Btn, GWL_USERDATA,
          SetWindowLong(Btn, GWL_WNDPROC, LongInt(@BtnWinProc)));
      end;

    WM_KEYDOWN:
      { Закрытие окна по нажатию Enter'а }
      if wParam = VK_RETURN then PostQuitMessage(0);

    { Данное сообщение посылается при отрисовке Edit'a;
       вы можете использовать переданный контекст для рисования
       фона, либо для смены цвета текста; после завершения рисования
       верните модифицированный контекст как результат сообщения и не
       забудьте сделать выход из оконной процедуры, так как в противном
       случае DefWindowProc снова разукрасит Edit в стандартный системный цвет }
    WM_CTLCOLOREDIT:
      begin 
        { Устанавливаем прозрачность фона }
        SetBkMode(wParam, TRANSPARENT);
        { Устанавливаем цвет шрифта }
        SetTextColor(wParam, $FF0000);
        { Возвращаем нужный нам контекст }
        Result := wParam;
        Exit;
      end;

    WM_DESTROY:
      begin
        { Выход для освобождения памяти }
        PostQuitMessage(0);
      end;
  end;
  { Обработка всех остальных сообщений по умолчанию }
  Result := DefWindowProc(hWnd, uMsg, wParam, lParam);
end;

procedure WinMain;
var
  Msg: TMsg;
  { Оконный класс }
  WndClassEx: TWndClassEx;
begin
  { Подготовка структуры класса окна }
  ZeroMemory(@WndClassEx, SizeOf(TWndClassEx));

  {************* Заполнение структуры нужными значениями ******************* }

  { Размер структуры }
  WndClassEx.cbSize := SizeOf(TWndClassEx);
  { Имя класса окна }
  WndClassEx.lpszClassName := 'SubclassSampleWnd';
  { Стиль класса, не окна }
  WndClassEx.style := CS_VREDRAW or CS_HREDRAW;
  { Дескриптор программы (для доступа к сегменту данных) }
  WndClassEx.hInstance := HInstance;
  { Адрес оконной процедуры }
  WndClassEx.lpfnWndProc := @MainWinProc;
  { Иконки }
  WndClassEx.hIcon :=  LoadIcon(HInstance, MakeIntResource('MAINICON'));
  WndClassEx.hIconSm := LoadIcon(HInstance, MakeIntResource('MAINICON'));
  { Курсор }
  WndClassEx.hCursor := LoadCursor(0, IDC_ARROW);
  { Кисть для заполнения фона }
  WndClassEx.hbrBackground := COLOR_BTNFACE + 1;
  { Меню }
  WndClassEx.lpszMenuName := nil;

  { Регистрация оконного класса в Windows }
  if RegisterClassEx(WndClassEx) = 0 then
    MessageBox(0, 'Невозможно зарегистрировать класс окна',
      'Ошибка', MB_OK or MB_ICONHAND)
  else
  begin
    { Создание окна по зарегистрированному классу }
    Wnd := CreateWindowEx(0, WndClassEx.lpszClassName,
      'Subclassing Sample by Rrader', WS_OVERLAPPEDWINDOW and not WS_BORDER
       and not WS_MAXIMIZEBOX and not WS_SIZEBOX,  Integer(CW_USEDEFAULT),
       Integer(CW_USEDEFAULT), 320, 116, 0, 0, HInstance, nil);
    if Wnd = 0 then 
      MessageBox (0, 'Окно не создалось!',
        'Ошибка', MB_OK or MB_ICONHAND)
    else
    begin
      { Показ окна }
      ShowWindow(Wnd, SW_SHOWNORMAL);
      { Обновление окна }
      UpdateWindow(Wnd);
      { Цикл обработки сообщений }
      while GetMessage(Msg, 0, 0, 0) Do
      begin
        TranslateMessage(Msg);
        DispatchMessage(Msg);
      end;
      { Выход по прерыванию цикла }
      Halt(Msg.wParam);
    end;
  end;
end;

begin
  InitCommonControls;
  { Создание окна } 
  WinMain;
end.


Все примеры очень простые, они должны дать Вам базовое представление о сабклассинге.

Теперь можно переходить к суперклассингу.

Суперклассинг

Сабклассинг особенно удобен, когда дело касается изменения одного окна, класс которого не совпадает с другими окнами, подлежащими сабклассированию. А что, если нам нужно засабклассировать сотню Edit'ов? Сабклассинг здесь будет громоздким. Решением этой проблемы является суперклассинг.

Суперклассинг (superclassing) - создание и регистрация нового класса окна в системе. После чего этот класс окна готов к использованию.

VCL-суперклассинг мы рассматривать не будем. Думаю, Вам понятно, что реализация суперклассинга на VCL - это создание компонентов. При создании оконного компонента в Delphi вы неявно создаёте подобие суперкласса. После этого вы можете использовать хоть сотню таких компонентов (например, создать из них массив). Заметьте, что такой компонент будет, как правило не стандартным, например, кнопка TBitBtn. Чтобы Вам было понятней, почему это суперкласс, можете посмотреть имя класса окна компонента через любой сканер окон (я использовал InqSoft Window Scanner) - это имя будет совпадать с тем именем, которое обозначает имя компонента в Delphi (например, TBitBtn или TLabeledEdit). Из этого мы можем сделать вывод, что суперклассинг прекрасно прижился в Delphi и широко там используется.

У каждого потомка класса TWinControl в Delphi есть метод CreateParams. Можете воспользоваться им, чтобы изменить название класса окна.

Гораздо более интересен суперклассинг на WinAPI. Необходимо уметь его использовать.

Рассмотрим, как по шагам создать суперкласс. smile
  •  Вызываем функцию GetClassInfoEx, чтобы получить информацию о классе окна, который мы будем далее модернизировать. Эта функция заполнит переданную ей запись TWndClassEx параметрами класса;
  •  Изменяем всё, что нам нужно в полученной записи. Нужно задать свое имя класса, размер структуры, а также дескриптор HInstance, также нас будет интересовать оконная процедура - мы также изменим её у класса;
  •  Регистрируем новый класс при помощи функции RegisterClassEx;
  •  По окончании работы программы освобождаем класс функцией UnregisterClass.
Далее новый класс можно использовать. В примерах я буду делать простые изменения в классах окон.

Давайте рассмотрим функции для суперклассинга более подробно.

Суперклассинг начинается с функции GetClassInfoEx.

Объявление функции:
Код

function GetClassInfoEx(Instance: Cardinal; Classname: PChar; 
  var WndClass: TWndClassEx): LongBool; stdcall;


Первый параметр функции - дескриптор приложения, которое создало класс. Если же Вы желаете модифицировать предопределённые класс окон Windows (например, классы 'Button', 'Edit', 'ListBox' и т. п.), то передайте нуль в параметре.

Следующий параметр - собственно название интересующего Вас класса. Сюда можно передать атом (см. ниже)

В последнем параметре передается структура типа TWndClassEx, в которую в случае успешного вызова функции будет помещена информация о классе.

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

После подготовки класса окна Вы регистрируете его в Windows с помощью функции RegisterClassEx.

Код

function RegisterClassEx(const WndClass: TWndClassEx): Word; stdcall;


Функция возвращает атом, который по сути есть числовое уникальное значение. Это будет идентификатор класса окна в системе.

По завершению работы приложения желательно уничтожить класс. В противном случае - "утечка памяти".
Для этого существует функция UnregisterClass:
Код

function UnregisterClass(lpClassName: PChar; hInstance: Cardinal): LongBool; stdcall;


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

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

Для полного ознакомления с суперклассингом следует обобщить знания о самом классе окна.

Класс окна
Вообще, класс окна - объемная тема. Мы рассмотрим её самые главные особенности.

Класс окна (window class) - набор свойств, который используются как шаблон для создания окон. Класс окна всегда можно расширить, изменить. Давайте подробнее разберем атрибуты класса.

Первый атрибут - имя класса. Оно позволяет отличать одни классы от других. Классы с одинаковыми именами считаются идентичными. После создания окна по классу это окно может подвергнуться сабклассингу. Сабклассинг не изменяет класс окна. Не делайте имена классов длиннее 64 символов.

Второй атрибут - это адрес оконной процедуры для окна. Об оконной процедуре подробно рассказано выше.

Третий атрибут - дескриптор приложения (или DLL), которое зарегистрировало класс.

Четвёртый - курсор окна при создании.

Пятый - дескриптор большой иконки для окна.

Шестой - тоже дескриптор иконки, но маленькой. Этого атрибута нет у структуры типа TWndClass (поняли, в чем отличие TWndClass от TWndClassEx?). 

Седьмой - дескриптор кисти, которой будет зарисована клиентская область окна.

Восьмой - дескриптор меню, которое присваивается окну при создании.

Девятый - стили класса (см. ниже)

Десятый - дополнительная память, выделяемая классу (тип Integer).

Одиннадцатый - дополнительная память (Integer), выделяемая под каждое окно класса.

Напоследок рассмотрим стили класса. Стили класса - это комбинация значений, которые определяют поведение класса.
Вот они:

CS_BYTEALIGNCLIENT - выстраивает клиентскую часть окна на границу байта, что позволяет достичь большей производительности при отрисовке;
    
CS_BYTEALIGNWINDOW - то же, что и CS_BYTEALIGNCLIENT, только увеличивает производительность при перемещении окна;    

CS_CLASSDC - создает контекст устройства, который разделяется между всеми наследниками этого класса - общий контекст для рисования;    

CS_DBLCLKS - разрешает обработку сообщений при двойном щелчке мыши;    

CS_GLOBALCLASS - разрешает создание окон с независимыми идентификаторами (HInstance) приложений. Создаётся глобальный класс. Если этот флаг не указан, то значение HInstance при создании окна должно быть таким же как и при регистрации класса RegisterClass(Ex).
    
CS_HREDRAW - перерисовывает окно при его перемещении по горизонтали (и при изменении горизонтальных размеров);    

CS_VREDRAW - перерисовывает окно при его перемещении по вертикали (и при изменении вертикальных размеров);    

CS_NOCLOSE - убирает команду "Закрыть" из системного меню окна;    

CS_OWNDC - создает уникальный контекст устройства для каждого вновь создаваемого окна.

На суперклассинг я публикую один пример, в котором на главном окне будет создано 10 "измененных" Edit'ов. Каждый такой Edit при клике на нём мышки уничтожит себя сам.

Код

program SampleProject04;

{$R *.res}
{$R WinXP.res} 

uses
  Windows, Messages;

procedure InitCommonControls; stdcall; external 'comctl32.dll';  

var
  { Главное окно }
  Wnd: HWND;
  { Массив Edit'ов }
  Edits: array[0..9] of HWND;
  { Сюда будет помещено значение оригинальной оконной процедуры класса Edit }
  OldProc: Pointer;

{ Устанавливает для окна AWindow шрифт для контролов по умолчанию }
procedure SetDefFont(AWindow: HWND);
begin
  SendMessage(AWindow, WM_SETFONT, GetStockObject(DEFAULT_GUI_FONT), 1);
end;

{ Модифицированная оконная процедура каждого поля ввода }
function EditWinProc(hWnd: HWND; uMsg: UINT;
  wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
begin
  case uMsg of
    { Уничтожение Edit'а }
    WM_LBUTTONDOWN: DestroyWindow(hWnd);
  end;
  { Вызов оригинальной оконной процедуры }
  Result := CallWindowProc(OldProc,
    hWnd, uMsg, wParam, lParam);
end;

{ Оконная процедура главного окна }
function MainWinProc(hWnd: HWND; uMsg: UINT;
  wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
var
  TmpEdit: TWndClassEx;
  I: Integer;
begin
  case uMsg of 
    { Здесь будет произведено создание дочерних окон }
    WM_CREATE:
      begin
        { Начало суперклассинга }
        if not GetClassInfoEx(0, 'Edit', TmpEdit) then Halt;
        { Запоминаем оконную процедуры для правильной работы окна }
        OldProc := TmpEdit.lpfnWndProc;
        { Модификация класса }
        TmpEdit.cbSize := SizeOf(TWndClassEx);
        TmpEdit.lpfnWndProc := @EditWinProc;
        TmpEdit.lpszClassName := 'Sample04EditWindowClass';
        TmpEdit.hInstance := GetModuleHandle(nil);
        { Регистрация класса }
        if RegisterClassEx(TmpEdit) = 0 then Halt;
        { Подготовка массива }
        FillChar(Edits, SizeOf(Edits), 0);
        for I := Low(Edits) to High(Edits) do
        begin
          Edits[I] := CreateWindowEx(WS_EX_CLIENTEDGE,
            'Sample04EditWindowClass', 'Sample',
            WS_CHILD or WS_VISIBLE or ES_LEFT,
            8, 28, 300, 21, hWnd, 0, HInstance, nil);
          SetDefFont(Edits[I]);   
        end;
      end;

    WM_KEYDOWN:
      { Закрытие окна по нажатию Enter'а }
      if wParam = VK_RETURN then PostQuitMessage(0);

    WM_DESTROY:
      begin
        { Уничтожение классов}
        UnregisterClass('Sample04EditWindowClass', HInstance);
        { Выход для освобождения памяти }
        PostQuitMessage(0);
      end;
  end;
  { Обработка всех остальных сообщений по умолчанию }
  Result := DefWindowProc(hWnd, uMsg, wParam, lParam);
end;

procedure WinMain;
var
  Msg: TMsg;
  { Оконный класс }
  WndClassEx: TWndClassEx;
begin
  { Подготовка структуры класса окна }
  ZeroMemory(@WndClassEx, SizeOf(TWndClassEx));

  {************* Заполнение структуры нужными значениями ******************* }

  { Размер структуры }
  WndClassEx.cbSize := SizeOf(TWndClassEx);
  { Имя класса окна }
  WndClassEx.lpszClassName := 'SuperclassSampleWnd';
  { Стиль класса, не окна }
  WndClassEx.style := CS_VREDRAW or CS_HREDRAW;
  { Дескриптор программы (для доступа к сегменту данных) }
  WndClassEx.hInstance := HInstance;
  { Адрес оконной процедуры }
  WndClassEx.lpfnWndProc := @MainWinProc;
  { Иконки }
  WndClassEx.hIcon :=  LoadIcon(HInstance, MakeIntResource('MAINICON'));
  WndClassEx.hIconSm := LoadIcon(HInstance, MakeIntResource('MAINICON'));
  { Курсор }
  WndClassEx.hCursor := LoadCursor(0, IDC_ARROW);
  { Кисть для заполнения фона }
  WndClassEx.hbrBackground := COLOR_BTNFACE + 1;
  { Меню }
  WndClassEx.lpszMenuName := nil;
  { Регистрация оконного класса в Windows }
  if RegisterClassEx(WndClassEx) = 0 then
    MessageBox(0, 'Невозможно зарегистрировать класс окна',
      'Ошибка', MB_OK or MB_ICONHAND)
  else
  begin
    { Создание окна по зарегистрированному классу }
    Wnd := CreateWindowEx(0, WndClassEx.lpszClassName,
      'Superclassing Sample by Rrader', WS_OVERLAPPEDWINDOW and not WS_BORDER
       and not WS_MAXIMIZEBOX and not WS_SIZEBOX, Integer(CW_USEDEFAULT),
       Integer(CW_USEDEFAULT), 320, 116, 0, 0, HInstance, nil);
    if Wnd = 0 then 
      MessageBox (0, 'Окно не создалось!',
        'Ошибка', MB_OK or MB_ICONHAND)
    else
    begin
      { Показ окна }
      ShowWindow(Wnd, SW_SHOWNORMAL);
      { Обновление окна }
      UpdateWindow(Wnd); 
      { Цикл обработки сообщений }
      while GetMessage(Msg, 0, 0, 0) do
      begin
        TranslateMessage(Msg);
        DispatchMessage(Msg);
      end;
      { Выход по прерыванию цикла }
      Halt(Msg.wParam);
    end;
  end;
end;

begin
  InitCommonControls;
  { Создание окна } 
  WinMain;
end.

Это было базовое знакомство с сабклассингом и суперклассингом. Надеюсь, материал данной статьи поможет Вам при программировании!

Все примеры Вы найдёте в прикрепленном файле к статье в порядке их упоминания в статье.

Присоединённый файл ( Кол-во скачиваний: 188 )
Присоединённый файл  Examples.rar 24,11 Kb


--------------------
Let's do this quickly!
Rest in peace, Vit!
PM MAIL Skype   Вверх
Alex
Дата 2.11.2005, 12:09 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Экс. модератор
Сообщений: 4147
Регистрация: 25.3.2002
Где: Москва

Репутация: 6
Всего: 162





--------------------
Написать можно все - главное четко представлять, что ты хочешь получить в конце. 
PM Skype   Вверх
Sajtran
Дата 12.11.2011, 20:43 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 9
Регистрация: 15.10.2008
Где: Мегион

Репутация: нет
Всего: 2



Вопрос, а при сабклассинге обязательно пользоваться CallWindowProcA CallWindowProcW ???
просто я не вижу как можно сделать эту процедуру без протоколирования всех записей в GWL_WNDPROC
вот простенький пример
Код

program TestSubclass;

{$APPTYPE CONSOLE}

uses
  Windows,Messages,CommCtrl,SysUtils;

var bh:LPARAM;  
function MyWinProc(handle: HWND; Msg: UINT; wPrm: WPARAM; lPrm: LPARAM): LRESULT;stdcall;
var s:WideString;
begin
  case msg of
   WM_SETTEXT:
     begin
       s:=PWideChar(LPrm);
       Writeln('WinProc:',s);
     end;
   WM_COMMAND:
     if lPrm=bh then begin
       Windows.SetWindowTextW(Handle,'Test str');
     end;
   WM_CREATE: bh:=CreateWindowW('BUTTON','OK',
      WS_CHILD or WS_VISIBLE or BS_PUSHBUTTON or WS_TABSTOP or BS_DEFPUSHBUTTON    ,
      8,8,100,20,Handle,0,HInstance,nil);
   WM_DESTROY:PostQuitMessage(0);
  end;
  Result:=DefWindowProcW(handle,Msg,wPrm,lPrm);
end;
  
const
  StdWinClass='stdwin';
var
  StdWc:TWndClassExW=
   (
    cbSize: SizeOf(StdWc);
    style:
        CS_DROPSHADOW or
        CS_HREDRAW or
        CS_VREDRAW or
        CS_DBLCLKS;
    lpfnWndProc: @MyWinProc;
    cbClsExtra: 0;
    cbWndExtra: 0;
    hInstance: 0;
    hIcon: 0;
    hCursor: 0;
    hbrBackground: 0;
    lpszMenuName: nil;
    lpszClassName: StdWinClass;
    hIconSm: 0;
   );

procedure Init;
begin

  StdWc.hCursor:= LoadCursor(0,IDC_ARROW);
  StdWc.hbrBackground:=Windows.GetSysColorBrush(COLOR_BTNFACE);
  StdWc.hInstance:=HInstance;

  if RegisterClassExW(StdWc)=0 then RaiseLastOSError;
end;

var p1,p2:Pointer;

function SubClassProcAnsi(handle: HWND; Msg: UINT; wPrm: WPARAM; lPrm: LPARAM): LRESULT;stdcall;
var s:AnsiString;
begin
  if msg=WM_SETTEXT then begin
    s:=PAnsiChar(LPrm);
    Writeln('AnsiSubClassProc:',s);
  end;
  Result:=CallWindowProcA(p1,Handle,Msg,wPrm,Lprm);
end;

function SubClassProcU(handle: HWND; Msg: UINT; wPrm: WPARAM; lPrm: LPARAM): LRESULT;stdcall;
var s:WideString;
begin
  if msg=WM_SETTEXT then begin
    s:=PWideChar(LPrm);
    Writeln('UnicodeSubClassProc:',s);
  end;
  Result:=CallWindowProcW(p2,Handle,Msg,wPrm,Lprm);
end;

 var msg: TMsg;
  w:HWND;
function SetWindowLongPtrW(hWnd:HWND; nIndex:longint; dwNewLong:Pointer):Pointer;stdcall; external 'user32.dll' name 'SetWindowLongW';
function SetWindowLongPtrA(hWnd:HWND; nIndex:longint; dwNewLong:Pointer):Pointer;stdcall; external 'user32.dll' name 'SetWindowLongA';

begin
  Init;
  w:=CreateWindowExW(0,StdWinClass,'Test win',
      WS_MINIMIZEBOX or WS_MAXIMIZEBOX or
     WS_VISIBLE or
     WS_CLIPSIBLINGS or
     WS_CLIPCHILDREN or
     WS_SYSMENU or
     WS_THICKFRAME,
     100,50,640,480,0,0,HInstance,nil);

    p1:=SetWindowLongPtrA(w,GWL_WNDPROC,@SubClassProcAnsi);
    p2:=SetWindowLongPtrW(w,GWL_WNDPROC,@SubClassProcU);

    while GetMessageW(msg, 0, 0, 0) do
      begin
        TranslateMessage(msg);
        DispatchMessageW(msg);
      end;

end.


если пройтись отладчиком и посмотреть значения p1,p2, @SubClassProcAnsi, @SubClassProcU, @MyWinProc на WinXP
видно что p1,p2  ссылаются на верхние адреса


если поменять местами замену процедур 
Код

   p2:=SetWindowLongPtrW(w,GWL_WNDPROC,@SubClassProcU);
   p1:=SetWindowLongPtrA(w,GWL_WNDPROC,@SubClassProcAnsi);


видно что в p2 находится указатель на MyWinProc 


отсюда  - в p1, p2  либо полиморфные процедуры уже приведённые к Ansi или Unicode версии либо просто структура которую анализирует CallWindowProc

Ваше мнение господа, может кто знает как это работает на Win98, Win95 и как вообще это работает?

PM MAIL   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
Правила форума "Delphi: WinAPI и системное программирование"
Snowybartram
MetalFanbems
PoseidonRrader
Riply

Запрещено:

1. Публиковать ссылки на вскрытые компоненты

2. Обсуждать взлом компонентов и делиться вскрытыми компонентами

  • Литературу по Delphi обсуждаем здесь
  • Действия модераторов можно обсудить здесь
  • С просьбами о написании курсовой, реферата и т.п. обращаться сюда
  • Вопросы по реализации алгоритмов рассматриваются здесь
  • 90% ответов на свои вопросы можно найти в DRKB (Delphi Russian Knowledge Base) - крупнейшем в рунете сборнике материалов по Дельфи
  • 99% ответов по WinAPI можно найти в MSDN Library, оставшиеся 1% здесь

Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, Snowy, bartram, MetalFan, bems, Poseidon, Rrader, Riply.

 
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | Delphi: WinAPI и системное программирование | Следующая тема »


 




[ Время генерации скрипта: 0.1465 ]   [ Использовано запросов: 22 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.