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

Я использую стандартные действия Вырезать, Копировать, Вставить в моем Главном меню. Они имеют комбинации клавиш Ctrl-X , Ctrl-C и Ctrl-V .

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

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

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

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


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

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

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

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

Это все важные немодальные формы в моей программе.

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


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

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

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

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

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

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

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


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

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

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

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

Ответы [ 3 ]

3 голосов
/ 11 декабря 2009

Я разместил ссылку на этот вопрос в своем блоге , и получил предложение от Уве Молжана, который не работает в StackOverflow. Уве раньше запускал 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, которая установила фокус на соответствующую сетку для кадра. Это может означать еще одно альтернативное решение моего вопроса, но мне не нужно исследовать это, поскольку решение Питера работает для меня.

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

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

2 голосов
/ 08 декабря 2009

ОК, первым делом: это не имеет ничего общего с модальными или немодальными формами, это ограничение работы компонентов действия 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;

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

Edit:

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

Edit:

Я мог бы воспроизвести проблему - ключ в том, чтобы каким-то образом отключить действия редактирования. В ретроспективе это очевидно, свойство 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;

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

1 голос
/ 05 декабря 2009

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

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

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

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

...