Перейти к содержанию
Fire Monkey от А до Я
  • 0

Перехват сообщений в Windows


Вадим Смоленский

Вопрос

Для задач, связанных с вводом японского текста, мне нужно научиться перехватывать системное сообщение WM_IME_NOTIFY. Насколько могу судить, в FireMonkey эти вещи делаются (если вообще делаются) принципиально иначе, нежели в VCL. Конкретного ничего не нагуглил. Не поможет ли кто?

Ссылка на комментарий

Рекомендуемые сообщения

  • 0
6 часов назад, Вадим Смоленский сказал:

нужно научиться перехватывать системное сообщение WM_IME_NOTIFY.

Нужно перехватывать в рамках своего окна / приложения / системы вцелом ?

Ссылка на комментарий
  • 2

Делаем как обычно, если надо переопределить какое-то событие окна

interface

uses
  {$IFDEF MSWINDOWS}
  Winapi.Windows, Winapi.Messages,
  FMX.Platform.Win,
  {$ENDIF}

  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs;

type
  TMainForm = class(TForm)
  private
    procedure CreateHandle; override;

    {$IFDEF MSWINDOWS}
    procedure WMIMENotify(var Msg: TMessage); message WM_IME_NOTIFY;
    {$ENDIF}
  public
  end;

var
  MainForm: TMainForm;

implementation

{$R *.fmx}

{$IFDEF MSWINDOWS}
var
  OldWndProc: Pointer = nil;

function NewWndProc(Wnd: HWND; Msg: UINT; WParam: WPARAM; LParam: LPARAM): LRESULT; stdcall;
var
  Mess : TMessage;
begin
    case Msg of
      WM_IME_NOTIFY:
      begin
        Mess.Msg := Msg;
        Mess.WParam := wParam;
        Mess.lParam := lParam;
        Mess.Result := 0;
        MainForm.Dispatch(Mess);
        Result := Mess.Result;
      end;
      else
        Result := CallWindowProc(OldWndProc, Wnd, Msg, WParam, LParam);
    end;
end;

{$ENDIF}

{ TMainForm }

procedure TMainForm.CreateHandle;
begin
  inherited CreateHandle;

  {$IFDEF MSWINDOWS}
  OldWndProc:= Pointer(SetWindowLong(WindowHandleToPlatform(Handle).Wnd, GWL_WNDPROC, Integer(@NewWndProc)));
  {$ENDIF}
end;

{$IFDEF MSWINDOWS}
procedure TMainForm.WMIMENotify(var Msg: TMessage);
begin
  {что-то делаем}
end;
{$ENDIF}

 

Изменено пользователем dnekrasov
Переопределение WndProc перенесено в CreateHandle
Ссылка на комментарий
  • 0
5 часов назад, kami сказал:

Нужно перехватывать в рамках своего окна / приложения / системы вцелом ?

Можно считать, что в рамках окна.

 

2 часа назад, dnekrasov сказал:

Делаем как обычно, если надо переопределить какое-то событие окна

Спасибо, Некрасов-сан. Выглядит интригующе. Но я не могу понять, как управление перейдет в процедуру WMIMENotify. Она ведь у вас вообще нигде не вызывается.

P.S. А, понял. Она вызывается в FMX.Platform.Win:

          WM_IME_NOTIFY:
            begin
              Result := WMImeNotify(LForm, hwnd, uMsg, wParam, lParam);
            end;

 

Изменено пользователем Вадим Смоленский
Ссылка на комментарий
  • 0
20 минут назад, dnekrasov сказал:

За счет строчки

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

Что-то где-то недокручено, такое впечатление...

Ссылка на комментарий
  • 0
19 минут назад, Вадим Смоленский сказал:

Что-то где-то недокручено, такое впечатление..

Это FMX. Оконная процедура устанавливается на конкретный хендл окна. А FMX раз по 100 пересоздает хендл. В результате установка NewWindowProc в качестве WndProc уходит в небытие.

Ссылка на комментарий
  • 0
37 минут назад, Вадим Смоленский сказал:

Во время запуска приложения управление множество раз переходит в NewWndProc

Ну это нормально - теперь все сообщения окну обрабатываются в NewWndProc

37 минут назад, Вадим Смоленский сказал:

Но как только главное окно появилось на экране - больше ни разу, ни с какими сообщениями вообще

А вот это странно - я этот механизм использую еще начиная с XE4 и везде отрабатывал без проблем. Единственное, что мог бы посоветовать - попробуйте перенести переопределение WndProc в CreateHandle.

WinHook.zip

UPD

Поднял старые проекты - именно в CreateHandle я это и делал. 

Изменено пользователем dnekrasov
Ссылка на комментарий
  • 0
6 минут назад, dnekrasov сказал:

Поднял старые проекты - именно в CreateHandle я это и делал. 

Да вот, kami говорит, что в FireMonkey всё по-другому...

Ссылка на комментарий
  • 1
1 минуту назад, Вадим Смоленский сказал:

Да вот, kami говорит, что в FireMonkey всё по-другому...

Именно перенос в CreateHandle и решает проблему, описанную @kami

Ссылка на комментарий
  • 0
3 часа назад, dnekrasov сказал:

Именно перенос в CreateHandle и решает проблему

Да, действительно. Теперь управление в нужные моменты передается WMIMENotify. Спасибо еще раз. Однако, к сожалению, это пока не помогло мне решить исходной проблемы. Не знаю, уместно ли будет изложить эту проблему здесь - все-таки она связана с процедурами IME для ввода японского текста; с этим мало кто сталкивается. И тем не менее: когда включена японская локаль и выбран ввод хираганой (слоговой азбукой), то при печатании в TEdit на экран автоматически выдается маленькое окошко (candidate window) со списком иероглифов или слов, которые могли бы соответствовать набранному чтению. Пользователь может выбрать одного из этих кандидатов. Проблема в том, что когда включено экранное масштабирование (скажем, 125%), candidate window выскакивает не строго под TEdit, как должно, а левее и выше. Похоже, это недоработка FireMonkey - при передаче координат курсора ввода масштабирование не учитывается. Я хотел наладить перехват выдачи candidate window с присвоением ему других координат. Сам перехват теперь получается (при вызове candidate window выдается сообщение WM_IME_NOTIFY с WParam=IMN_SETCANDIDATEPOS). А вот так выглядит у меня обработка этого перехвата в рекомендованной вами процедуре:

uses imm;

procedure TMyForm.WMIMENotify(var Msg: TMessage);
var Imc: HIMC;
    ImeCandidateFormProperties: TCandidateForm;
begin
 Imc:=ImmGetContext(FMXHandleToHWND(LookUp.Handle));
 ImmGetCandidateWindow(Imc,0,@ImeCandidateFormProperties);
 ImeCandidateFormProperties.dwStyle:=CFS_CANDIDATEPOS;
 ImeCandidateFormProperties.ptCurrentPos.X:=100;
 ImeCandidateFormProperties.ptCurrentPos.Y:=100;        
 ImmSetCandidateWindow(Imc,@ImeCandidateFormProperties);
 ImmReleaseContext(FMXHandleToHWND(LookUp.Handle),Imc);
end;

Сколь могу судить, ImmGetCandidateWindow успешно считывает данные о candidate window. Но заменить их на исправленные уже не выходит, при вызове ImmSetCandidateWindow происходит переполнение стека.

Документация о ImmSetCandidateWindow лежит здесь. Опять же: трудно надеяться, что вы сможете помочь - но вдруг?

Ссылка на комментарий
  • 1
1 час назад, Вадим Смоленский сказал:

трудно надеяться, что вы сможете помочь - но вдруг?

Попробовал... Не понял что такое LookUp.Handle - заменил на просто Handle и вызываю только если Msg.WParam = IMN_SETCANDIDATEPOS. Всё работает без ошибок. Вот мой код

procedure TMainForm.WMIMENotify(var Msg: TMessage);
var
  Imc: HIMC;
  ImeCandidateFormProperties: TCandidateForm;
begin
  if Msg.WParam = IMN_SETCANDIDATEPOS then
  begin
    Imc:=ImmGetContext(FMXHandleToHWND(Handle));
    ImmGetCandidateWindow(Imc,0,@ImeCandidateFormProperties);
    ImeCandidateFormProperties.dwStyle:=CFS_CANDIDATEPOS;
    ImeCandidateFormProperties.ptCurrentPos.X := 100;
    ImeCandidateFormProperties.ptCurrentPos.Y := 100;
    ImmSetCandidateWindow(Imc,@ImeCandidateFormProperties);
    ImmReleaseContext(FMXHandleToHWND(Handle),Imc);
  end;
end;

 

Ссылка на комментарий
  • 0
4 минуты назад, dnekrasov сказал:

Не понял что такое LookUp.Handle

LookUp - это название моей формы, которое я забыл поменять на MyForm. Но можно и без него, конечно. Однако же, у меня и в таком виде всё равно валится. Что еще тут стоит проверить?..

Ссылка на комментарий
  • 0
56 минут назад, Вадим Смоленский сказал:

Что еще тут стоит проверить?

Сложно что-то посоветовать, когда не можешь повторить ошибку... Попробуйте задать ImeCandidateFormProperties.dwIndex  и ImeCandidateFormProperties.rcArea. Зачем вообще вызывать ImmGetCandidateWindow(Imc,0,@ImeCandidateFormProperties)? Попробуйте поиграться со значением ImeCandidateFormProperties.dwStyle 

Ссылка на комментарий
  • 0
1 час назад, dnekrasov сказал:

Сложно что-то посоветовать, когда не можешь повторить ошибку...

Вынес всё, касающееся этой проблемы, в отдельный проект. У меня продолжает падать и в таком виде. Не поленитесь проверить, упадет ли у вас.

Файл FMX.Platform.Win.pas я в свое время чуть изменил из-за каких-то проблем (сходу и не вспомню, каких), и с тех пор держу его в одной папке с проектом. Может, с ним что не так?

ImeProject.zip

Ссылка на комментарий
  • 0
5 минут назад, dnekrasov сказал:

Проверил - всё прекрасно работает

И что - прямо заводите японский текст, и выскакивает candidate window, и располагается на точке (100,100)?

Ссылка на комментарий
  • 1

Попробуйте ещё так сделать:

  if not FSet and (Msg.WParam = IMN_SETCANDIDATEPOS) then
  begin
    FSet := True;
    Imc:=ImmGetContext(FMXHandleToHWND(Handle));
//    ImmGetCandidateWindow(Imc,0,@ImeCandidateFormProperties);
    ImeCandidateFormProperties.dwIndex:=0;
    ImeCandidateFormProperties.dwStyle:=CFS_CANDIDATEPOS;
    ImeCandidateFormProperties.ptCurrentPos.X:=100;
    ImeCandidateFormProperties.ptCurrentPos.Y:=100;
    ImeCandidateFormProperties.rcArea:=Rect(0,0,100,100);
    Msg.Result := LResult(ImmSetCandidateWindow(Imc,@ImeCandidateFormProperties));
    ImmReleaseContext(FMXHandleToHWND(Handle),Imc);
    FSet := False;
  end;

где FSet это глобальное поле типа Boolean. Намного меньше раз заходит в процедуру и более правильно отрабатывает - нет рекурсии вызова.

Изменено пользователем dnekrasov
Ссылка на комментарий
  • 0
4 часа назад, dnekrasov сказал:

Попробуйте ещё так сделать

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

Очень и очень признателен. Желаю всяческих благ - и отдельно успехов дочери на поприще японистики!

Ссылка на комментарий
  • 0
4 часа назад, Вадим Смоленский сказал:

С таким булевым заграждением всё получилось!

В обработке сообщения производить действия, которые способны генерировать кучу сообщений, которые приведут к обработке того же сообщения, в котором... , имхо, не есть хорошо.

Вместо вот такого экранирования я бы попробовал использовать TThread.ForceQueue(procedure begin код, завязанный на IME end);

Изменено пользователем kami
Ссылка на комментарий

Присоединяйтесь к обсуждению

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

Гость
Ответить на вопрос...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...