Отловить внутри компонента нажатие Ctrl
Модератор: Модераторы
Отловить внутри компонента нажатие Ctrl
Здравствуйте.
Я разрабатываю компонент для лазаруса (потомок TCustomPanel), и мне нужно, чтоб один из дочерних контролов (TImage) моей панели реагировал на нажатие и отпускание клавиши Ctrl - перерисовывал себя.
При этом хотелось бы сделать автономный и кроссплатформенный компонент. То есть я не хочу использовать события OnKeyDown и OnKeyUp родительской формы, влазить в обработчик событий TApplication и использовать win32 api (GetAsyncKeyState).
Я разрабатываю компонент для лазаруса (потомок TCustomPanel), и мне нужно, чтоб один из дочерних контролов (TImage) моей панели реагировал на нажатие и отпускание клавиши Ctrl - перерисовывал себя.
При этом хотелось бы сделать автономный и кроссплатформенный компонент. То есть я не хочу использовать события OnKeyDown и OnKeyUp родительской формы, влазить в обработчик событий TApplication и использовать win32 api (GetAsyncKeyState).
VKB писал(а):При этом хотелось бы сделать автономный и кроссплатформенный компонент. То есть я не хочу использовать события OnKeyDown и OnKeyUp родительской формы,
А почему Вы думаете, что это не кроссплатформенное средство?
Vadim писал(а):VKB писал(а):При этом хотелось бы сделать автономный и кроссплатформенный компонент. То есть я не хочу использовать события OnKeyDown и OnKeyUp родительской формы,
А почему Вы думаете, что это не кроссплатформенное средство?
А почему Вы думаете, что я думаю, что это не кроссплатформенное средство? Говоря о некроссплатформенности я имел ввиду только GetAsyncKeyState.
VKB
Вы это так ловко объединили, что другой мысли у меня возникнуть и не могло.
Тогда задам вопрос по другому - а почему Вы не хотите использовать OnKeyDown?
Вы это так ловко объединили, что другой мысли у меня возникнуть и не могло.
Тогда задам вопрос по другому - а почему Вы не хотите использовать OnKeyDown?
Vadim писал(а):VKB
Вы это так ловко объединили, что другой мысли у меня возникнуть и не могло.
Тогда задам вопрос по другому - а почему Вы не хотите использовать OnKeyDown?
Почему я не хочу использовать OnKeyDown родительской формы я объяснил - у меня автономный компонент, и негоже ему залазить не в свою епархию.
А для того, чтоб использовать собственные OnKeyDown и OnKeyUp нужно чтоб контрол имел фокус ввода. Что в общем-то логично. У TPanel и TImage по умолчанию свойство TabStop установлено в False. А свойства OnKeyDown и OnKeyUp неопубликованы. В принципе это можно обойти, присваивая эти свойства в момент инициализации. Но тогда всё равно срабатывать эта штуковина будет только при переходе фокуса ввода на мой компонент. И в этом есть свои неудобства - переключение фокуса ввода обычно выполняется при нажатии на Tab или клике мышкой. Чтобы отлавливались нажатия на клавиатуру мне придётся перехватывать фокус из события OnMouseMove. Пользователю моего компонента будет не совсем удобно, что мой компонент отхватывает себе фокус при проведении поверх него мышки. Тем более, что клавиатурой там делать нечего и в плане общепрограммной логики ему это не нужно ни для чего кроме реагирования на клавишу Ctrl.
Код: Выделить всё
TWinControl = class(TControl)
...
protected
procedure ControlKeyDown(var Key: Word; Shift: TShiftState); virtual;
procedure ControlKeyUp(var Key: Word; Shift: TShiftState); virtual;
procedure KeyDown(var Key: Word; Shift: TShiftState); virtual;
procedure KeyDownBeforeInterface(var Key: Word; Shift: TShiftState); virtual;
procedure KeyDownAfterInterface(var Key: Word; Shift: TShiftState); virtual;
procedure KeyPress(var Key: char); virtual;
procedure KeyUp(var Key: Word; Shift: TShiftState); virtual;
procedure KeyUpBeforeInterface(var Key: Word; Shift: TShiftState); virtual;
procedure KeyUpAfterInterface(var Key: Word; Shift: TShiftState); virtual;
...
можно override-ить любой метод, по-вкусу.
настроятельно рекомендуется вызывать inherited внутри каждого из переопределённых методов.
скалогрыз писал(а):Код: Выделить всё
TWinControl = class(TControl)
...
protected
procedure ControlKeyDown(var Key: Word; Shift: TShiftState); virtual;
procedure ControlKeyUp(var Key: Word; Shift: TShiftState); virtual;
procedure KeyDown(var Key: Word; Shift: TShiftState); virtual;
procedure KeyDownBeforeInterface(var Key: Word; Shift: TShiftState); virtual;
procedure KeyDownAfterInterface(var Key: Word; Shift: TShiftState); virtual;
procedure KeyPress(var Key: char); virtual;
procedure KeyUp(var Key: Word; Shift: TShiftState); virtual;
procedure KeyUpBeforeInterface(var Key: Word; Shift: TShiftState); virtual;
procedure KeyUpAfterInterface(var Key: Word; Shift: TShiftState); virtual;
...
можно override-ить любой метод, по-вкусу.
настроятельно рекомендуется вызывать inherited внутри каждого из переопределённых методов.
Вы смотрели код тех процедур, что предлагаете?
Например
Код: Выделить всё
procedure TWinControl.KeyDown(var Key: Word; shift : TShiftState);
begin
if Assigned(FOnKeyDown) then FOnKeyDown(Self, Key, Shift);
end;
и? это не то что нужно?
Негоже контролу или компоненту изменять чей-либо OnKeyDown/OnKeyUp (тем более свой), если этот OnKeyDown/OnKeyUp, может быть изменён пользователем.
Кстати это касается ВСЕХ событий а не только OnKeyDown/OnKeyUp
Если нужно, по-особому, обработать нажатие клавиши для этого специально сделали защищённые виртуальные методы!
Защищённые - чтобы пользователь не мог их некорректно вызывать.
Виртуальные - чтобы наследники от TWinControl-а могли изменять поведение по-умолчанию (т.е. ничегонеделанье)
Негоже контролу или компоненту изменять чей-либо OnKeyDown/OnKeyUp (тем более свой), если этот OnKeyDown/OnKeyUp, может быть изменён пользователем.
Кстати это касается ВСЕХ событий а не только OnKeyDown/OnKeyUp
Если нужно, по-особому, обработать нажатие клавиши для этого специально сделали защищённые виртуальные методы!
Защищённые - чтобы пользователь не мог их некорректно вызывать.
Виртуальные - чтобы наследники от TWinControl-а могли изменять поведение по-умолчанию (т.е. ничегонеделанье)
Мне нужно, чтоб мой контрол реагировал на нажатие и отпускание клавиши Ctrl даже если фокус ввода находится не у него. Более того, лучше, чтоб ему вообще фокус ввода не попадал никогда - он ему не нужен.скалогрыз писал(а):и? это не то что нужно?
В целом я согласен, но не согласен с акцентами. Как раз свой OnKeyDown и OnKeyUp компонент более вправе изменять, если по логике работы пользователю компонента эти свойства будут не нужны. Так как мой компонент является потомком TPanel и я всё равно создаю для него новый класс, то действительно лучше переписать методы KeyUp и KeyDown. Это правильное замечание, но оно не имеет никакой пользы для решения той проблемы, с которой я столкнулся, потому что итог будет такой же. А скажем внутрь своей панели я вставляю TImage и мне не надо от этого TImage ничего, кроме перехвата событий OnMouseDown и OnMouseMove. Так зачем мне создавать потомка TImage только для того, чтоб переопределить там методы, если я могу просто присвоить свойствам стандартного TImage адреса процедур с тем же эффектом?скалогрыз писал(а):Негоже контролу или компоненту изменять чей-либо OnKeyDown/OnKeyUp (тем более свой), если этот OnKeyDown/OnKeyUp, может быть изменён пользователем.
скалогрыз писал(а):Кстати это касается ВСЕХ событий а не только OnKeyDown/OnKeyUp
Если нужно, по-особому, обработать нажатие клавиши для этого специально сделали защищённые виртуальные методы!
Защищённые - чтобы пользователь не мог их некорректно вызывать.
Виртуальные - чтобы наследники от TWinControl-а могли изменять поведение по-умолчанию (т.е. ничегонеделанье)
Я объяснил, почему мне этого не достаточно. Мне было бы вполне достаточно изменить свойства родительской формы, если бы я просто вставлял в свою форму TPanel, TImage и т.д., но я хочу сделать компонент, который будет вставляться потом в любую форму и не хочу заранее сужать пользователю моего компонента возможности по обработке его формой KeyDown и KeyUp. Ведь разработчик родительской формы не ожидает, что один из вставленных контролов покушается на обработку его формой этих событий!
Добавлено спустя 36 минут 12 секунд:
Похоже то, что мне нужно, в Delphi называется TApplicationEvents. А в Лазарусе я такого не нашёл.
Мне нужно, чтоб мой контрол реагировал на нажатие и отпускание клавиши Ctrl даже если фокус ввода находится не у него. Более того, лучше, чтоб ему вообще фокус ввода не попадал никогда - он ему не нужен.
А каким макаром ты узнаешь о нажатии кнопки вообще?
по таймеру будешь вызывать GetKeyboardState (и её налоги для других систем).
У формы есть свойство: KeyPreview. Установив эго в True, методы формы OnKeyDown/Press/Up всегда вызываются вперёд обработчика контрола, находящегося в фокусе. Так что: всегда есть шанс, что нажатие ctrl твой контрол не отловит.
Ещё вариант, это вешать системные обработчики на системные объекты (в обход LCL) и получать нажатие клавиш, до того как об этом узанала форма или кто-то ещё... По-сути это хак, но если его красиво исполнить, то всё получится.
как итог: регистрировать нужно Application.AddOnUserInputHandler. Если этого недостаточно, то клянчить у разработчиков дополнительные методы, и строго-на-строго избегать хаков! а главное НИКОГДА не трогать публичные (и опубликованные) OnXXX события. Потому что они не для взаимодействия компонентов, а для программиста, использующего эти компоненты.
Именно такая мысль и была, когда я писал о некроссплатформенности GetAsyncKeyStateскалогрыз писал(а):А каким макаром ты узнаешь о нажатии кнопки вообще?
по таймеру будешь вызывать GetKeyboardState (и её налоги для других систем).
Ну понятно, что пользователь контрола может его замордовать так, что тот не сможет работать. Если ему нужно, чтоб контрол нормально работал - будет пропускать нажатие и отпускание Ctrl.скалогрыз писал(а):У формы есть свойство: KeyPreview. Установив эго в True, методы формы OnKeyDown/Press/Up всегда вызываются вперёд обработчика контрола, находящегося в фокусе. Так что: всегда есть шанс, что нажатие ctrl твой контрол не отловит.
Ты имеешь ввиду что-то типа SetWindowsHookEx? Во-первых оно не кроссплатформенное, а во-вторых для него нужны полномочия администратора. Это уж совсем неизящно и черезмерно для той простой задачи, котораую мне нужно решить.скалогрыз писал(а):Ещё вариант, это вешать системные обработчики на системные объекты (в обход LCL) и получать нажатие клавиш, до того как об этом узанала форма или кто-то ещё... По-сути это хак, но если его красиво исполнить, то всё получится.
О, вот это похоже то, что надо. Буду пробовать, спасибо.скалогрыз писал(а):как итог: регистрировать нужно Application.AddOnUserInputHandler.
То есть если мне скажем нужно вставить в свой компонент несколько картинок, которые должны по разному реагировать на клики мышкой, то я должен для каждого из них создавать свой класс и перекрывать MouseDown? Полагаю, что это черезчур ортодоксальный взгляд на ООП. Я ограничиваю доступ пользователя к этим свойствам внутренних контролов моего компонента. Считаю, что этого вполне достаточно. По принципу инкапсуляции пользователь компонента не должен лезть внутрь.скалогрыз писал(а): Если этого недостаточно, то клянчить у разработчиков дополнительные методы, и строго-на-строго избегать хаков! а главное НИКОГДА не трогать публичные (и опубликованные) OnXXX события. Потому что они не для взаимодействия компонентов, а для программиста, использующего эти компоненты.
VKB писал(а):Именно такая мысль и была, когда я писал о некроссплатформенности GetAsyncKeyState
...
Ты имеешь ввиду что-то типа SetWindowsHookEx? Во-первых оно не кроссплатформенное, а во-вторых для него нужны полномочия администратора. Это уж совсем неизящно и черезмерно для той простой задачи, котораую мне нужно решить.
Осбой разницы между GetAsyncKeyState и Hook-ами нет! Для обоих прийдётся писать платформенные реализации, с общей кроссплатформенной обёрткой. Аналоги хуков и GetAsyncKeyState, есть и в линуксе и маке...
VKB писал(а):То есть если мне скажем нужно вставить в свой компонент несколько картинок, которые должны по разному реагировать на клики мышкой, то я должен для каждого из них создавать свой класс и перекрывать MouseDown? Полагаю, что это черезчур ортодоксальный взгляд на ООП. Я ограничиваю доступ пользователя к этим свойствам внутренних контролов моего компонента. Считаю, что этого вполне достаточно. По принципу инкапсуляции пользователь компонента не должен лезть внутрь.
Всё правильно, у контролов для "внутреннего использования", события можно менять и использовать как угодно, без дополнительных спец-классов.
зачем такие сложности:
и не важно где фокус, главное что бы форма была активной
Код: Выделить всё
1.У формы KeyPressed:=true;
2.на FormKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState );
begin
if(Shift=[ssCtrl]) then ......и не важно где фокус, главное что бы форма была активной
Inferno писал(а):зачем такие сложности:Код: Выделить всё
1.У формы KeyPressed:=true;
2.на FormKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState );
begin
if(Shift=[ssCtrl]) then ......
и не важно где фокус, главное что бы форма была активной
1-х) тогда ответственность за работу компонента ложится на программиста. Т.е. ему нужно будет писать этот код каждый раз! А ведь хочется: положил компонент на форму - и всё работает.
2-х) Да. такой код работает, только если форма активна.
3-х) Соглашусь, что задуманный компонент - ну уж слишком какой-то заумный. Имхо, ни к чему это всё )) И хочет получить информацию, которую знать не нужно!
Ну как бы разница-то всё-таки есть. Для того, чтоб вставить Хук должны быть нужны полномочия суперпользователя, но зато не нужно использовать таймер. Что же касается того, что есть аналоги для линукса и мака, то я даже верю. Это будет мой запасной вариантскалогрыз писал(а):Осбой разницы между GetAsyncKeyState и Hook-ами нет! Для обоих прийдётся писать платформенные реализации, с общей кроссплатформенной обёрткой. Аналоги хуков и GetAsyncKeyState, есть и в линуксе и маке...
А я ни на что большее и не покушаюсьВсё правильно, у контролов для "внутреннего использования", события можно менять и использовать как угодно, без дополнительных спец-классов.
А предположим, что мой компонент имеет одно основное состояние, где действует своя логика по обработке мышки, но ему иногда нужно показывать некую дополнительную информацию, слегка видоизменяя свой внешний вид. Это существенно более редкая ситуация, которую нужно включать обычно на очень короткое время. И удобно навесить переключение в это состояние по нажатию на Ctrl. Потому что предполагается, что пользователь в основном будет только недолго смотреть на это видоизменённое состояние. Но так же я хочу ему дать возможность кое что подправить в этом видоизменённом состоянии (с этим как раз меньше всего проблем - при нажатии на мышку передаётся состояние кнопки Ctrl).3-х) Соглашусь, что задуманный компонент - ну уж слишком какой-то заумный. Имхо, ни к чему это всё )) И хочет получить информацию, которую знать не нужно!
Добавлено спустя 7 часов 15 минут 37 секунд:
Попробовал AddOnKeyDownHandler - то что, нужно, но странно, что нет такого же для KeyUp. Так что всё равно придётся использовать AddOnUserInputHandler. Всем спасибо за обсуждение, особенно скалогрызу!скалогрыз писал(а):как итог: регистрировать нужно Application.AddOnUserInputHandler.
Добавлено спустя 56 минут 2 секунды:
Мда. Всё-таки чуть-чуть они не доделали. Почему в UserInputEvent передают только поле Msg, а не всё сообщение? Я отлавливаю, что отжата какая-то кнопка, причём какая именно вызывающей процедуре известно, а мне придётся вызывать GetKeyState (и его аналогов для не Windows)...
