* При перепечатке материалов ссылка на www.SeoLiga.ru обязательна!
Сабклассинг окон с помощью Windows API
19 февраля 2009
В следующем примере будет показано, как усовершенствовать кнопку (Button), поле ввода (Edit). Вот список усовершенствований:
1) Для кнопки: создать такую кнопку, которая при нажатии левой кнопки мыши отображала бы текущую дату; 2) Для поля ввода: запретить контекстное меню; установить шрифт для текста синего цвета
Разберем, как это выглядит в теории. Для создания кнопки, отображающей дату, мы должны получить текущую дату функцией GetLocalTime. В переданной функции структуре будет находиться текущая дата. Нас интересует только текущие час, минута и секунда. Мы преобразуем полученные значения в строковый формат и дополняем нулями слева, если это необходимо. После этого отображаем дату на кнопке, по срабатыванию таймера.
Что касается поля ввода, то для запрета контекстного меню необходимо проигнорировать сообщение WM_CONTEXTMENU, после чего осуществить выход из оконной процедуры. Для изменения цвета текста необходимо использовать функция SetTextColor для контекста Edit'а. Этот контекст можно получить, обрабатывая сообщение WM_CTLCOLOREDIT (обратите внимание, что это сообщение посылается родительскому окну поля ввода). Данное сообщение посылается при каждой отрисовке Edit'а, передавая в параметре WParam контекст для рисования. Не следует забывать включить прозрачность фона функцией SetBkMode (хотя для нашего примера эта функция ничего не изменяет, попробуйте использовать другие цвета, чтобы убедиться в её надобности). код Pascal/Delphi
Const { Идентификатор таймера } BtnTimer = 450; { Константы с заголовками дочерних окон } StaticInfoText = 'Метка без сабклассирования'; BtnText = 'Кнопка для сабклассирования';
Var { Главное окно } HWnd: THandle; { Три дочерних компонента для сабклассирования } Btn, Edit, InfoStatic: THandle;
{ Устанавливает для окна AWindow шрифт для контролов по умолчанию } Procedure SetDefFont(AWindow: THandle); Begin SendMessage(AWindow, WM_SETFONT, GetStockObject(DEFAULT_GUI_FONT), 1); End;
{ Косвенно-вызваемая процедура сообщений таймера } { Эта процедура выполняется при каждом срабатывании таймера } Procedure BtnTimerProc(HWnd: THandle; Msg: Cardinal; IDEvent, DWTime: Cardinal); 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: THandle; Msg: Cardinal; WParam, LParam: Integer): Cardinal; Stdcall; Begin Case Msg Of { Запрещаем показ контекстного меню } WM_CONTEXTMENU: Begin Result := 0; MessageBeep(0); Exit; End; End; { Не забываем вызвать оригинальную оконную процедуру } Result := CallWindowProc(Pointer(GetWindowLong(HWnd, GWL_USERDATA)), Hwnd, Msg, WParam, LParam); End;
{ Модифицированная оконная процедура кнопки } Function BtnWinProc(HWnd: THandle; Msg: Cardinal; WParam, LParam: Integer): Cardinal; Stdcall; Begin Case Msg 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, Msg, WParam, LParam); End;
{ Оконная процедура главного окна } Function MainWinProc(HWnd: THandle; Msg: Cardinal; WParam, LParam: Integer): Cardinal; Stdcall;
{ Конвертирует сроку PChar в String } Function StrPas(Const AStr: PChar): String; Begin Result := AStr; End;
Begin Case Msg 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 := 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)));
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, Msg, WParam, LParam); End;
Procedure WinMain; Var Msg: TMsg; { Оконный класс } WndClassEx: TWndClassEx; Begin { Подготовка структуры класса окна } ZeroMemory(@WndClassEx, SizeOf(WndClassEx));
{************* Заполнение структуры нужными значениями ******************* }
{ Размер структуры } 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 { Создание окна по зарегистрированному классу } HWnd := 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 HWnd = 0 Then MessageBox (0, 'Окно не создалось!', 'Ошибка', MB_OK Or MB_ICONHAND) Else Begin { Показ окна } ShowWindow(HWnd, SW_SHOWNORMAL); { Обновление окна } UpdateWindow(HWnd);
{ Цикл обработки сообщений } While GetMessage(Msg, 0, 0, 0) Do Begin TranslateMessage(Msg); DispatchMessage(Msg); End; { Выход по прерыванию цикла } Halt(Msg.WParam); End; End; End;
Begin InitCommonControls; { Создание окна } WinMain; End.