• 0
Авторизация  
Вадим Смоленский

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

Вопросы

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

20 ответов на этот вопрос

  • 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
5 минут назад, Вадим Смоленский сказал:

Но я не могу понять, как управление перейдет в процедуру WMIMENotify

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

MainForm.Dispatch(Mess);

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 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
8 минут назад, Вадим Смоленский сказал:

Не поленитесь проверить, упадет ли у вас

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
5 минут назад, dnekrasov сказал:

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

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
12 минут назад, Вадим Смоленский сказал:

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

Clip2net_181115213545.gif.a757ff4aa7c7e219a821bfcf79baf41d.gif

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Для публикации сообщений создайте учётную запись или авторизуйтесь

Вы должны быть пользователем, чтобы оставить комментарий

Создать учетную запись

Зарегистрируйте новую учётную запись в нашем сообществе. Это очень просто!

Регистрация нового пользователя

Войти

Уже есть аккаунт? Войти в систему.

Войти
Авторизация  

  • Похожий контент

    • От Вадим Смоленский
      Узнал, что в UWP API включены средства для получения уникального идентификатора компьютера. Это юнит Windows.System.Profile, класс HardwareIdentification, метод getPackageSpecificToken. Существуют ли способы обратиться к этим средствам из Delphi?
      Вообще, я привык считать, что такая идентификация компьютера в принципе невозможна, в отличие от мобильных устройств. Но прогресс, как известно, не остановить.
    • От slav_z
      У меня для приложений написанных на FireMonkey не вызывается меню игры windows. Для VCL все в порядке. XE8. Если у кого есть решение этой проблемы, поделитесь пожалуйста. (комбинация клавиш Win+G при запущенном приложении)
    • От POV
      Винда 10, два компа (рабочий ноут и планшет от мелкософта - у первого 100% экран, у другого 200%).
      Исходя из габаритов разных там компонентов и другой формы, рассчитываю Top и Left интересующей меня формы. На ноуте выводится где и хотел, а на планшете выше и левее. Смещение не кратно никак масштабу. В доступе планшета нет, как отлаживать не придумаю.
      Косяк всё же в в масштабе может быть или иное?
    • От Вадим Смоленский
      В феврале я сетовал, что мое Windows-приложение не хочет нормально запускаться в Linux под Wine 3.0. Но время идет, вышел Wine 3.6 - и теперь один из тестировщиков радостно сообщил, что всё заработало, за вычетом одного досадного момента. А именно: приложение способно сохранять фокус ввода лишь долю секунды, потом теряет. Соответственно, невозможно ничего ввести в текстовые боксы, разве только одну-две буквы. Можно щелкнуть по заголовку приложения, оно опять получит фокус - и через мгновение снова потеряет. Куда именно при этом переходит фокус, непонятно. Тестировщик утверждает, что только мое приложение ведет себя так, все остальные работают нормально.
      Нет ли у кого-нибудь идей? Что нужно проверить?
    • От Вадим Смоленский
      В декабре я задавал здесь вопрос о борьбе с перехватами нажатий клавиш компонентом TWebBrowser. Продвинутый пользователь Kami посоветовал тогда, раз уж меня интересует только Windows, поставить хук на клавиатуру. Поделился полезной ссылкой. Добавил, что можно еще много чего нагуглить. Что-то действительно нагуглилось - но не в том объеме, чтобы я смог четко понять, как это следует делать. Вопросов много. Куда именно должна быть воткнута функция KeyboardProc? Что в ней должно содержаться, чтобы управление передавалось уже написанному обработчику события FormKeyDown? Многие также упоминают о возникающих проблемах с юникодом, и хорошо было бы понять, как уберечься от них.
      Буду очень признателен, если кто-нибудь осветит эту темную для меня материю.
    • От Вадим Смоленский
      Как преобразовать тип HString в обычную строку?
      Нагуглил упоминания о функции TWindowsString.HStringToString, которая должна находиться в System.WinrtHelpers. Но такого юнита в поставке Delphi не наблюдаю. Может, его можно где-нибудь раздобыть? Или существуют иные способы?
    • От Вадим Смоленский
      Пытаясь разобраться с проблемой размещения файлов в UWP-приложениях, пришел к необходимости создать в своем коде объект класса ApplicationData. Этот класс описан на соответствующей странице майкрософтовской документации, где в самом начале обозначено следующее:
      Namespace:  Windows.Storage
      Assemblies:  Windows.Storage.dll, Windows.dll
      Юнита с именем Windows.Storage или Winapi.Windows.Storage я в поставке Delphi не наблюдаю. Неудивительно, что попытки вставить соответствующие идентификаторы в раздел uses ни к чему не ведут. Как в таких случаях поступают? Откуда берут необходимое?
    • От Вадим Смоленский
      Хочу еще раз обратиться к коллективному разуму в надежде все-таки разобраться с тем, как должен быть устроен пакет appx для размещения в Microsoft Store. А именно - как организовать размещение настроек и пользовательских файлов в специально отведенных для этого папках, а не в установочном каталоге, что запрещено.
      Вся информация, которую я смог к сегодняшнему дню накопать, размещена на этой странице майкрософтовской документации и сводится к тому, что при инсталляции пакета appx автоматически создается хранилище пользовательских данных из трех папок. Цитирую: one for local files, one for roaming files, and one for temporary files. В общем-то, это удобно - по крайней мере, не нужно заботиться о деинсталляции: ровно эти же папки при удалении программы и сотрутся.
      Вопрос в том, как к этому хранилищу обратиться. В идеале, конечно же, хотелось бы иметь возможность уже в Deployment Manager обозначить для некоторых файлов, что они должны быть положены в это хранилище, а не в установочный каталог. Но как это можно сделать и можно ли сделать вообще, мне понять не удалось. Списка констант для параметра Remote Path я найти не смог, а отдельные упоминаемые тут и там константы ('res', 'assets', 'classes', 'library') явно относятся к мобильным платформам, а не к Windows.
      Если так поступить нельзя, остается класть всё в установочный каталог, а при первом запуске приложения переносить в нужное место. Но опять же: как приложению заполучить полный адрес этого места? Ведь это вовсе не привычные нам CSIDL_APPDATA и не CSIDL_COMMON_APPDATA, это нечто новое, доселе невиданное, в документации называемое "local app data store". На той же странице есть пример соответствующего кода с использованием класса ApplicationData, но он написан на незнакомом мне C#. Там есть также ссылки на описание класса ApplicationData, но по ссылкам тоже C# и C++. Был бы очень признателен, если бы кто-нибудь показал мне, как заполучить адрес local app data store средствами Delphi.
    • От Вадим Смоленский
      Целый месяц бодаюсь с Microsoft Store, пытаясь разместить у них свое UWP-приложение в виде пакета appx. Получаю отлуп за отлупом. Проблема: приложение должно располагать папкой для пользователя, отличной от установочного каталога, где он мог бы сохранять файлы. Там же должны храниться файлы настроек. Раньше, создавая дистрибутив в InnoSetup, я всегда предусматривал создание такой папки по адресу <user>\AppData\Roaming\MyApp. Теперь всё полагается указывать в разделе Deployment - но как там указать такой адрес? Вроде бы, есть графа "Remote path", и логично существовать каким-то макросам для нее, вроде AppData - но никакой информации на эту тему я найти не смог. Видел лишь упоминания таких макросов, как res, assets, classes, library - однако все они, как я понял, относятся к мобильным платформам.
      Попытки обходных маневров не удались. Сначала я решил всё класть в установочный каталог, а при первом запуске создавать нужную папку и перебрасывать в нее несколько файлов. На моем компьютере это работает, но при тестировании в Microsoft Store отчего-то валится (присылают скриншот с сообщением "Access is denied"). То ли нельзя стирать файлы в установочном каталоге, то ли нельзя в таком режиме создавать новый каталог. Потом я прочитал где-то, что UWP-приложениям всё равно, где лежат файлы, они могут их менять даже в установочном каталоге. Попробовал всё валить в одну кучу и так работать. Увы, опять отрапортовали о падении ("The app tries to create a file under WindowsApps folder").
      Получается, что все-таки нужно как-то заставить appx-дистрибутив создавать при установке папку по адресу <user>\AppData\Roaming\MyApp. Но как?
      Спрашивал на experts-exchange, там знатоков не нашлось. Если и здесь нет, может, кто-нибудь хотя бы подскажет, в каких местах есть смысл спросить?
    • От Вадим Смоленский
      Проект для Windows, автономная БД SQLite, никаких клиент-серверных дел, компоненты и операции самые простые: SQL-запрос в TFDQuery, вызов метода Open. Всё работает нормально, но отдельные пользователи жалуются на эпизодические непредсказуемые падения при поиске. Интересно, что после каждого такого падения всё опять функционирует нормально, но потом приложение не удается нормально закрыть, приходится вызывать диспетчер задач.
      Стабильно воспроизвести не могут ни пользователи, ни я сам. Мне удалось это считанные несколько раз - я лишь смог убедиться через отладчик, что проблема при закрытии связана именно с базой данных: вставлял в обработчик FormCloseQuery оператор TFDConnection.Close - и программа пару раз упала именно на этом операторе.
      Найти корень зла пока не удалось. Показалось только, что проблема возникает лишь тогда, когда поиск приводит к слишком большому (несколько тысяч) числу записей в TFDQuery.
      Может, стоит поменять какие-нибудь установки в TFDConnection или TFDQuery ?
  • Последние посетители   0 пользователей онлайн

    Ни одного зарегистрированного пользователя не просматривает данную страницу