Как я могу предотвратить столкновение / взаимодействие ярлыков в Delphi?

Я использую стандартные действия «Вырезать», «Копировать» и «Вставить» в главном меню. У них есть горячие клавиши Ctrl-X, Ctrl-C и Ctrl-V.

Когда я открываю модальную форму, например FindFilesForm.ShowModal, тогда все ярлыки работают из формы.

Но когда я открываю немодальную форму, например FindFilesForm.Show то ярлыки не работают.

Я бы подумал, что эти действия должны работать, если FindFilesForm является активной формой. Это модальность не должна иметь к этому никакого отношения, или я ошибаюсь в своем мышлении?

Тем не менее, как я могу заставить ярлыки работать с немодальной формой?


После ответа Кэри я продолжил исследование. Это не проблема с некоторыми элементами управления, например TMemo или TEdit.

Но это для некоторых других. В частности, те, где это происходит, включают:

  1. текст в TComboBox
  2. текст в TFindDialog
  3. элемент управления TElTreeInplaceEdit, часть LMD ElPack

Я посмотрю, есть ли другие, и добавлю их в список.

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

Так что мне все еще нужно решение.


Хорошо. Мне очень нужна помощь с этим. Так что это первый вопрос, за который я назначаю награду.

Мое обсуждение с Кэри, которое происходит через его ответ, и комментарии к нему более подробно описывают мою проблему.

И как я упоминал в одном из этих комментариев, похожая проблема, похоже, обсуждается здесь. / а>

Мне нужно решение или обходной путь, который позволит Ctrl-X, Ctrl-C и Ctrl-V всегда работать в TComboBox и TFindDialog в немодальном окне. Если эти две проблемы будут решены, я уверен, что мой TElTreeInplaceEdit также будет работать.

Как описывает Кэри, настройка простой тестовой программы занимает всего пару минут. Надеюсь, кто-нибудь сможет это решить.

Просто будьте осторожны, потому что, кажется, есть что-то, что позволяет ему работать иногда, но не работать в другое время. Если я смогу выделить это более подробно, я сообщу об этом здесь.

Спасибо за любую помощь, которую вы можете мне предложить.


Mghie очень много работал, чтобы найти решение, и его обработчик OnExecute в сочетании с обработчиком ActionListUpdate сделали свое дело. Поэтому за его усилия я даю ему принятое решение и награды.

Но его обработчик обновления списка действий непростой, и вам нужно указать в нем все случаи, которые вы хотите обработать. Допустим, есть также Ctrl + A для выбора всего или Ctrl-Y для отмены, которую вы, возможно, захотите. Общая процедура была бы лучше.

Так что, если вы столкнетесь с этим вопросом в поисках ответа, попробуйте сначала ответ, который я предоставил, который добавляет обработчик IsShortcut. Он сработал для меня и должен обрабатывать все случаи и не требует обработчиков OnExecute, так что это намного проще. Питер Боул написал этот код, и Уве Молжан получает гонорар со стороны искателей.

Спасибо Кэри, mghie, Уве и Питеру за то, что помогли мне решить эту проблему. Без тебя не справился бы. (Возможно, я мог бы это сделать, но это могло занять у меня 6 месяцев.)


person lkessler    schedule 05.12.2009    source источник
comment
Если мой ответ решит вашу проблему, вам, вероятно, также следует изменить вопрос на что-то вроде «Как можно заставить стандартные действия редактирования работать для всех элементов управления редактированием (поля со списком, локальные редакторы и т. Д.)?   -  person mghie    schedule 10.12.2009
comment
Или как я могу сделать так, чтобы стандартные действия редактирования не нарушали ярлыки для собственных элементов управления редактированием ?. Или что-то в этом роде, чтобы его было легче найти при поиске.   -  person mghie    schedule 10.12.2009
comment
Изменен заголовок, как вы предложили   -  person lkessler    schedule 11.12.2009
comment
Изменено название, чтобы отразить анализ происходящего Питером Беловом.   -  person lkessler    schedule 11.12.2009


Ответы (3)


Хорошо, первое, что нужно сделать: это не имеет ничего общего с модальными или немодальными формами, это ограничение того, как работают компоненты действий Delphi (если вы хотите это так называть).

Позвольте мне доказать это на простом примере: создайте новое приложение с новой формой, поместите на него TMemo и TComboBox и запустите приложение. Оба элемента управления будут иметь системное контекстное меню с командами редактирования и будут правильно на них реагировать. Они будут делать то же самое для ярлыков меню, за исключением Ctrl + A, который не поддерживается для поля со списком.

Теперь добавьте TActionList компонент с тремя стандартными действиями: «Вырезать», «Копировать» и «Вставить». Дела по-прежнему будут работать, в поведении никаких изменений.

Теперь добавьте главное меню и добавьте меню «Правка» из шаблона. Удалите все команды, кроме «Вырезать», «Копировать» и «Вставить». Установите соответствующие компоненты действия для пунктов меню и запустите приложение. Обратите внимание на то, что в поле со списком все еще есть контекстное меню, и команды там все еще работают, но ярлыки больше не работают.


Проблема в том, что стандартные действия редактирования были разработаны для работы только с TCustomEdit элементами управления. Взгляните на метод TEditAction.HandlesTarget() в StdActns.pas. Так как элементы управления редактированием в полях со списком, встроенные редакторы в элементах управления в виде дерева или элементы управления редактированием в собственных диалоговых окнах не улавливаются этим, они не будут обрабатываться. Команды меню всегда будут отключены, если один из этих элементов управления находится в фокусе. Что касается ярлыков, работающих только некоторое время - это зависит от того, отображает ли VCL в какой-то момент ярлыки на команды действия или нет. Если этого не произойдет, они наконец достигнут собственной оконной процедуры и инициируют команду редактирования. В этом случае ярлыки будут работать. Я предполагаю, что для модальных диалогов обработка действий приостановлена, поэтому поведение модальных и немодальных диалогов различается.

Чтобы обойти это, вы можете предоставить обработчики для OnExecute этих стандартных действий. Например, для команды Вставить:

procedure TMainForm.EditPaste1Execute(Sender: TObject);
var
  FocusWnd: HWND;
begin
  FocusWnd := GetFocus;
  if IsWindow(FocusWnd) then
    SendMessage(FocusWnd, WM_PASTE, 0, 0);
end;

и аналогичные обработчики для команды «Вырезать» (WM_CUT) и команды «Копировать» (WM_COPY). Выполнение этого в небольшом демонстрационном приложении заставит снова работать для поля со списком. Вы должны попробовать в своем приложении, но я полагаю, это поможет. Сложнее правильно включить и отключить команды главного меню для всех встроенных элементов управления редактированием. Возможно, вы могли бы отправить сообщение EM_GETSEL, чтобы проверить, есть ли выделение в элементе управления сфокусированным редактированием.

Изменить:

Подробнее, почему поведение полей со списком в модальных и немодальных диалогах различается (анализ выполнен в Delphi 2009): интересный код находится в TWinControl.IsMenuKey() - он пытается найти компонент действия в одном из списков действий родительской формы сфокусированного элемента управления, который обрабатывает ярлык. Если это не удается, он отправляет сообщение CM_APPKEYDOWN, что в конечном итоге приводит к той же проверке, выполняемой со списками действий главной формы приложения. Но вот в чем дело: это будет сделано только в том случае, если включен дескриптор окна главной формы приложения (см. Код TApplication.IsShortCut()). Теперь вызов ShowModal() в форме отключит все другие формы, поэтому, если модальное диалоговое окно не содержит самого действия с тем же ярлыком, собственная обработка ярлыков будет работать.

Изменить:

Я мог бы воспроизвести проблему - ключ в том, чтобы каким-то образом отключить действия редактирования. Оглядываясь назад, это очевидно, свойство Enabled действий, конечно же, тоже нуждается в обновлении.

Попробуйте использовать этот дополнительный обработчик событий:

procedure TForm1.ActionList1Update(Action: TBasicAction; var Handled: Boolean);
var
  IsEditCtrl, HasSelection, IsReadOnly: boolean;
  FocusCtrl: TWinControl;
  FocusWnd: HWND;
  WndClassName: string;
  SelStart, SelEnd: integer;
  MsgRes: LRESULT;
begin
  if (Action = EditCut1) or (Action = EditCopy1) or (Action = EditPaste1) then
  begin
    IsEditCtrl := False;
    HasSelection := False;
    IsReadOnly := False;

    FocusCtrl := Screen.ActiveControl;
    if (FocusCtrl <> nil) and (FocusCtrl is TCustomEdit) then begin
      IsEditCtrl := True;
      HasSelection := TCustomEdit(FocusCtrl).SelLength > 0;
      IsReadOnly := TCustomEdit(FocusCtrl).ReadOnly;
    end else begin
      FocusWnd := GetFocus;
      if IsWindow(FocusWnd) then begin
        SetLength(WndClassName, 64);
        GetClassName(FocusWnd, PChar(WndClassName), 64);
        WndClassName := PChar(WndClassName);
        if AnsiCompareText(WndClassName, 'EDIT') = 0 then begin
          IsEditCtrl := True;
          SelStart := 0;
          SelEnd := 0;
          MsgRes := SendMessage(FocusWnd, EM_GETSEL, WPARAM(@SelStart),
            LPARAM(@SelEnd));
          HasSelection := (MsgRes <> 0) and (SelEnd > SelStart);
        end;
      end;
    end;

    EditCut1.Enabled := IsEditCtrl and HasSelection and not IsReadOnly;
    EditCopy1.Enabled := IsEditCtrl and HasSelection;
    // don't hit the clipboard three times
    if Action = EditPaste1 then begin
      EditPaste1.Enabled := IsEditCtrl and not IsReadOnly
        and Clipboard.HasFormat(CF_TEXT);
    end;
    Handled := TRUE;
  end;
end;

Я не проверял, является ли встроенный элемент управления редактированием доступным только для чтения, это, вероятно, можно сделать, добавив следующее:

IsReadOnly := GetWindowLong(FocusWnd, GWL_STYLE) and ES_READONLY <> 0;

Примечание. Я дал ответ Мги, поскольку он проделал большую работу и его ответ правильный, но я реализовал более простое решение, которое я сам добавил в качестве ответа

person mghie    schedule 08.12.2009
comment
mghie: Спасибо за подробный анализ. Я час работаю, пытаясь проверить то, что вы говорите. Я делаю то, что вы говорите в первых 4 абзацах, до вашей горизонтальной линии. Вы правы, что для ComboBox на главной форме ярлыки не работают. Однако, если вы разместите две кнопки на главной форме, которые откроют модальную и немодальную форму. И на каждой форме разместите ComboBox. Затем при открытии поля со списком в модальной форме ярлыки ДЕЙСТВУЮТ, а в немодальной - нет. Так что, похоже, это все еще имеет какое-то отношение к модальным / немодальным формам. - person lkessler; 09.12.2009
comment
Да, я предполагаю, что из-за того, что модальные диалоги отключают все другие формы в приложении, их компоненты действий не используются для определения того, какие ключевые события будут сопоставлены командам. Если ключевые события переходят в исходные окна без изменений, ярлыки по-прежнему работают. Однако, если вы поместите компоненты действия и меню в модальное диалоговое окно, это тоже не сработает. Вот почему я сказал, что модальность на самом деле не имеет значения, просто она влияет на то, какие списки действий используются для поиска потенциальных ярлыков. Побочный эффект, если хотите. - person mghie; 09.12.2009
comment
Итак, теперь я вставил ваши обработчики EditPaste1Execute (а также Copy1 и Cut1). Я впечатлен вашими знаниями об этом. Спасибо. Мне потребовалось бы много времени, чтобы понять это. Но даже с этим у меня все еще есть проблема. Сделайте следующее: в главной форме нажмите кнопку, чтобы открыть диалоговое окно поиска. Введите строку поиска, выделите ее часть и нажмите Ctrl-X. Теперь это работает! Но вернемся к основной форме. Нажмите кнопку, чтобы открыть немодальную форму с полем со списком. Сделайте Ctrl-X из поля со списком. НЕ РАБОТАЕТ. Снова откройте программу. Сначала сделайте поле со списком (работает), а затем найдите (не работает). - person lkessler; 09.12.2009
comment
Должно быть что-то еще. Я сделал все эти тесты, и все у меня сработало. Это с полностью пропатченным Delphi 2009. - person mghie; 09.12.2009
comment
mghie: Сегодня вечером я размещу свою простую тестовую программу на своем веб-сайте (не могу сделать это прямо сейчас - меня нет дома), и, возможно, тогда эта последняя проблема обнаружится для вас. Когда все будет готово, я опубликую еще один комментарий со ссылкой на тестовую программу. - person lkessler; 09.12.2009
comment
Хорошо, я разместил zip-файл с моим тестовым проектом Delphi 2009 по адресу: beholdgenealogy.com/cutcopypastetest. zip ... но, поскольку я тестирую его сейчас с помощью примера работает / не работает, который я привел выше, не работает не происходит. Все работает. Я либо схожу с ума, либо здесь что-то действительно не так. Меня это не очень обнадеживает. Нам всем нужны программы, которые можно проверить, работают постоянно, а не несколько раз. Но, пожалуйста, попробуйте. Может быть, это случится с тобой. В противном случае я затрудняюсь найти какое-либо объяснение. - person lkessler; 10.12.2009
comment
mghie: Ваше исправление не сработало полностью, когда я попробовал его в первый раз (см. 4 комментария назад), но теперь кажется, что он работает (см. 1 комментарий назад), и это очень странно. Но, к сожалению, когда я вставляю его в свою программу, он вообще не работает. - person lkessler; 11.12.2009
comment
Что ж, твой код работает в моей программе и, похоже, решает проблему. См. Мои последние правки по вопросу выше. - person lkessler; 11.12.2009

Я разместил ссылку на этот вопрос в своем блоге и получил предложение от Уве Молжан, которого нет на StackOverflow. Uwe раньше запускал DelphiPool. Он указал мне на эту ветку на borland.public.delphi.objectpascal:

Список действий (неправильное) поведение .

Том Александр, задавший первоначальный вопрос в этой ветке, даже сказал:

Такое поведение наблюдается обычно, но не всегда. Иногда после серии вышеуказанных ошибок поведение начинает действовать так, как я ожидал.

это именно то странное поведение, которое у меня было, что сделало эту проблему почти невозможной для отслеживания.

Питер Белов ответил в этой беседе, что если есть конфликтующие ярлыки, вы должны предпринять шаги, чтобы активный элемент управления первым взломал ярлык.

Взяв его код (который был написан для проблемы с фреймами), мне просто пришлось изменить «ctrl is TCustomFrame» на «ctrl is TControl», и он отлично работает. Итак, вот что было необходимо:

public
Function IsShortcut( var Message: TWMKey): Boolean; override;

Function TMyform.IsShortcut( var Message: TWMKey): Boolean; 
Var 
  ctrl: TWinControl; 
  comp: TComponent; 
  i: Integer; 
Begin 
  ctrl := ActiveControl; 
  If ctrl <> Nil Then Begin 
    Repeat 
      ctrl := ctrl.Parent 
    Until (ctrl = nil) or (ctrl Is TControl); 
    If ctrl <> nil Then Begin 
      For i:= 0 To ctrl.componentcount-1 Do Begin 
        comp:= ctrl.Components[i]; 
        If comp Is TCustomActionList Then Begin 
          result := TCustomActionList(comp).IsShortcut( message ); 
          If result Then 
            Exit; 
        End; 
      End;   
    End; 
  End; 
//  inherited; { Originally I had this, but it caused multiple executions }
End;   

Пока что это работает для меня во всех случаях.

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

Также обратите внимание, что Питер включает в свой ответ превосходное резюме сложных шагов обработки ключей, которое стоит знать.

Но теперь я хочу проверить правку Мги на его ответ и посмотреть, является ли это также решением.

person lkessler    schedule 11.12.2009
comment
Это интересное решение, имеющее небольшую проблему, заключающуюся в том, что оно исправляет проблему ярлыка только. Если, например, есть панель инструментов с кнопками для «Вырезать», «Копировать» и «Вставить», они будут отключены, если окно редактирования с фокусом не является TCustomEdit, в то время как ярлыки для этих действий редактирования будут работать, из-за чего пользовательский интерфейс будет казаться непоследовательным. Но это решает проблему в вашем вопросе, поэтому +1. - person mghie; 11.12.2009

Я создал очень простой пример с двумя формами в Delphi 2009 (установлены обновления 3 и 4), работающих в 64-разрядной версии Vista. Вторая форма, Form2, отображается в немодальном режиме (Form2.Show;). У меня есть TMemo на Form2. Ctrl-X, Ctrl-V и Ctrl-C работают нормально.

Это было до того, как я разместил TMainMenu в Form2.

Итак, я поместил TMainMenu в форму и добавил TActionList. Я создаю пункты меню «Правка» и добавляю элементы подменю «Копировать», «Вырезать», «Вставить». Я подключил их к стандартным действиям EditCopy, EditCut и EditPaste. Тем не менее, все работает нормально, как и раньше. Я могу использовать либо пункты меню, либо комбинации клавиш Ctrl-C, Ctrl-X и Ctrl-V.

Здесь должно быть что-то еще.

person Cary Jensen    schedule 05.12.2009
comment
Вы правы, что этот простой пример сработал. Мне придется либо построить простой пример, либо разбить мою программу, чтобы увидеть, что меня здесь блокирует. Между моим вопросом и вашим ответом я нашел других людей с той же проблемой на forum.codegear .com / thread.jspa? threadID = 19946, и я попробовал решение Питера Ниже, но оно не сработало для меня. Это добавляет к вашему выводу, что здесь происходит что-то еще. Я буду исследовать дальше. Спасибо за ответ. Вы дали мне повод для продолжения. - person lkessler; 05.12.2009
comment
Кэри, посмотри обновленную информацию по моему вопросу. TMemo's и TEdits не имеют проблем. Пожалуйста, попробуйте свою тестовую программу с TComboBox или TFindDialog. - person lkessler; 06.12.2009
comment
Любопытный. Я разместил компоненты TFindDialog и TComboBox в своей основной форме, а также в немодальной форме. Вырезание и вставка работали с TFindDialog на обеих формах. Вырезание и вставка работали с TComboBox в основной форме, но не в немодальной форме. - person Cary Jensen; 06.12.2009
comment
То, что вы видели на TComboBox, - это проблема, которую я пытаюсь решить. Для меня и моей версии вашего простого теста TFindDialog также неправильно выполняет Ctrl-X, Ctrl-C, Ctrl-V, когда он вызывается либо из основной формы, либо из немодальной формы, но работает из модальной формы. Форма. Так что я не знаю, почему с вами этого не происходит. - person lkessler; 07.12.2009
comment
После того, как я начал экспериментировать с этим комментарием, он внезапно сработал в немодальной форме. Еще немного поигравшись, он продолжил работать. Я закрыл программу и перестроил ее. Он все еще продолжал работать. Для меня это сейчас очень странно. Понятия не имею, что это могло быть? Но Кэри, ты по крайней мере свидетель того, что происходит что-то странное. - person lkessler; 07.12.2009
comment
... но я возвращаюсь к своей программе, и проблема определенно существует. - person lkessler; 07.12.2009
comment
Странный! Может быть, в вашем небольшом примере изменился порядок создания компонентов? Есть ли у вас проблемы с другими HotKey? Я видел случай, когда добавление ярлыка в меню где-то нарушало Ctrl-A в априори несвязанной форме. Также проверьте KeyPreview ... - person Francesca; 07.12.2009
comment
Франсуа: Интересная мысль. Я рассмотрю некоторые другие возможные взаимодействия горячих клавиш вырезания, копирования и вставки. Но такой простой пример, который предложил Кэри, не оставляет места для внешних влияний. Прежде всего, для этого требуется ActionList и немодальная форма. - person lkessler; 07.12.2009