2013-11-20 2 views
2

У меня возникли проблемы с настройкой значка в трее с FMX (XE3, Windows). Я использую тот же код, который можно найти в бесчисленных потоках, но я не получил обработку сообщений для работы значка.FMX - Обработка сообщений Trayicon

Чтобы создать иллюстрацию, я создал testapp, который устанавливает данные TrayIcon в FormCreate и создает его с помощью кнопки. Он покажет правильный значок и правильную подсказку, процедура TrayMessage никогда не будет вызвана.

unit Unit2; 

interface 

uses 
    System.SysUtils, System.Types, System.UITypes, System.Rtti, System.Classes, 
    System.Variants, FMX.Types, FMX.Controls, FMX.Forms, FMX.Dialogs, Messages, 
    Windows, ShellAPI, FMX.Platform.Win; 

const 
    WM_ICONTRAY = WM_USER + 1; 

type 
    TForm2 = class(TForm) 
    Button1: TButton; 
    procedure FormCreate(Sender: TObject); 
    procedure Button1Click(Sender: TObject); 
    private 
    TrayIconData: TNotifyIconData; 
    procedure TrayMessage(var Msg: TMessage); message WM_ICONTRAY; 
    end; 

var 
    Form2: TForm2; 

implementation 

{$R *.fmx} 

procedure TForm2.Button1Click(Sender: TObject); 
begin 
    Shell_NotifyIcon(NIM_ADD, @TrayIconData); 
end; 

procedure TForm2.FormCreate(Sender: TObject); 
begin 
    with TrayIconData do 
    begin 
    cbSize := SizeOf; 
    Wnd := FmxHandleToHWND(self.Handle); 
    uID := 0; 
    uFlags := NIF_MESSAGE + NIF_ICON + NIF_TIP; 
    uCallbackMessage := WM_ICONTRAY; 
    hIcon := GetClassLong(FmxHandleToHWND(self.Handle), GCL_HICONSM); 
    StrPCopy(szTip, 'testapp'); 
    end; 
end; 

procedure TForm2.TrayMessage(var Msg: TMessage); 
begin 
    case Msg.lParam of 
    WM_LBUTTONDOWN: ShowMessage('LBUTTON'); 
    WM_RBUTTONDOWN: ShowMessage('RBUTTON'); 
    end; 
end; 

end. 

Я создал тот же сценарий с VCL, и он работает как ожидалось. Единственное различие заключается в прямом использовании Form2.Handle вместо преобразования FMX (и Application.Handle для загрузки данных значков, но это не является частью проблемы в FMX). Может кто-то указать мне верное направление ?

+0

Я сомневаюсь, что ваш обработчик 'WM_ICONTRAY' сообщения никогда не будет вызван. Это особая вещь для платформы Windows.Скорее найдите способ создания фиктивного окна с помощью насоса сообщений (если это можно сделать с помощью ['AllocateHWnd'] (http://docwiki.embarcadero.com/Libraries/XE4/en/System.Classes.AllocateHWnd) в FMX я не знаю, хотя). – TLama

+0

Да, обработчик сообщений, не вызываемый, является проблемой. Использование окна, выделенного AllocateHWnd для обработки сообщения, действительно отлично работает, спасибо большое, я добавлю код в качестве ответа, если нет другого способа, вы можете также явно, если хотите. Однако мне все еще остается интересно, почему исходный код не работает, я знаю, что это специфичная платформа, но я не понимаю, почему это проблема в Windows. – DNR

+0

Я предполагаю, что сообщения не отправляются из оконной процедуры, и никто из методов сообщения никогда не будет вызван. Я не удивлюсь, если это так, потому что, с теоретической точки зрения, подумайте о том, сколько методов сообщений вам нужно написать для одной вещи для всех разных платформ (с аналогичными механизмами обмена сообщениями, как у Windows) с использованием разных сообщений , или просто параметры. Это было бы грязно. – TLama

ответ

2

В отличие от VCL, FireMonkey не отправляет сырец окна сообщения FMX управления для пользовательской обработки (что бы поражение цели в рамках кросс-платформенной). FireMonkey имеет единственную функцию WndProc(), реализованную в модуле FMX.Platform.Win, который используется для всех окон HWND, которые создает FireMonkey. Эта реализация обрабатывает определенные оконные сообщения, которые необходимо обрабатывать, вызывая соответствующие различные методы управления (WMPaint(), KeyUp/Down(), MouseUp/Down() и т. Д.), А затем передает необработанные сообщения непосредственно на DefWindowProc() для обработки ОС, не позволяя элементам управления видеть сообщения вообще.

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

  1. создавать свои собственные окна, например, с AllocateHWnd() или CreateWindow/Ex() непосредственно.

  2. подключить к окнам HWND FireMonkey напрямую через Get/SetWindowLong/Ptr(). Поскольку FireMonkey представляет собой кросс-платформенную структуру, а окна HWND - это детализация конкретной платформы, я бы предложил избегать этого подхода.

  3. использовать поточные сообщения крючки через SetWindowsHookEx(). Создавая потоки, вы избегаете писать DLL, чтобы реализовать крючок.

В этой конкретной ситуации # 1 - ваш лучший выбор. Значки в лотке - это особенность, специфичная для Windows, поэтому вам действительно нужно использовать код, специфичный для Windows, который не привязан к FireMonkey для их обработки. Вы можете использовать AllocateHWnd() для использования метода вашего класса Form (или любого класса, если на то пошло) как WndProc() для приема сообщений в лотке, в то же время позволяя классу Form обрабатывать их. Например:

type 
    TForm2 = class(TForm) 
    Button1: TButton; 
    procedure FormCreate(Sender: TObject); 
    procedure FormDestroy(Sender: TObject); 
    procedure Button1Click(Sender: TObject); 
    private 
    {$IFDEF MSWINDOWS} 
    TrayWnd: HWND; 
    TrayIconData: TNotifyIconData; 
    TrayIconAdded: Boolean; 
    procedure TrayWndProc(var Message: TMessage); 
    {$ENDIF} 
    public 
    { Public declarations } 
    end; 

{$IFDEF MSWINDOWS} 
const 
    WM_ICONTRAY = WM_USER + 1; 
{$ENDIF} 

procedure TForm2.FormCreate(Sender: TObject); 
begin 
    {$IFDEF MSWINDOWS} 
    TrayWnd := AllocateHWnd(TrayWndProc); 
    with TrayIconData do 
    begin 
    cbSize := SizeOf(TrayIconData); 
    Wnd := TrayWnd; 
    uID := 1; 
    uFlags := NIF_MESSAGE or NIF_ICON or NIF_TIP; 
    uCallbackMessage := WM_ICONTRAY; 
    hIcon := ... 
    StrPCopy(szTip, 'testapp'); 
    end; 
    {$ENDIF} 
end; 

procedure TForm2.FormDestroy(Sender: TObject); 
begin 
    {$IFDEF MSWINDOWS} 
    if TrayIconAdded then 
    Shell_NotifyIcon(NIM_DELETE, @TrayIconData); 
    DeallocateHWnd(TrayWnd); 
    {$ENDIF} 
end; 

procedure TForm2.Button1Click(Sender: TObject); 
begin 
    {$IFDEF MSWINDOWS} 
    if not TrayIconAdded then 
    TrayIconAdded := Shell_NotifyIcon(NIM_ADD, @TrayIconData); 
    {$ENDIF} 
end; 

{$IFDEF MSWINDOWS} 
procedure TForm2.TrayWndProc(var Message: TMessage); 
begin 
    if Message.MSG = WM_ICONTRAY then 
    begin 
    ... 
    else 
    Message.Result := DefWindowProc(TrayWnd, Message.Msg, Message.WParam, Message.LParam); 
end; 
{$ENDIF} 
+0

Я собирался опубликовать мою версию первоначального предложения @ TLama в качестве решения, но ваш способ более отполирован. Спасибо, также за глубокое мнение по поводу других предложений. – DNR

2

Для обработки сообщений Windows в форме FMX вы можете переопределить WndProc формы с использованием функций GetWindowLong и SetWindowLong.

Попробуйте этот образец

uses 
    System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, 
    FMX.Types, FMX.Graphics, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.StdCtrls, Winapi.Messages, 
    Winapi.Windows, Winapi.ShellAPI, FMX.Platform.Win; 


const 
    WM_ICONTRAY = WM_USER + 1; 

type 
    TForm14 = class(TForm) 
    Button1: TButton; 
    procedure FormCreate(Sender: TObject); 
    procedure Button1Click(Sender: TObject); 
    private 
    OrgWndProc: Pointer; 
    NewWndProc: Pointer; 
    TrayIconData: TNotifyIconData; 
    procedure _WndProc(var Message: TMessage); 
    public 
    { Public declarations } 
    end; 

var 
    Form14: TForm14; 

implementation 


{$R *.fmx} 

procedure TForm14.Button1Click(Sender: TObject); 
begin 
    Shell_NotifyIcon(NIM_ADD, @TrayIconData); 
end; 


procedure TForm14._WndProc(var Message: TMessage); 
begin 
    if Message.MSG=WM_ICONTRAY then 
    begin 
    case Message.LParam of 
     WM_LBUTTONDOWN: ShowMessage('LBUTTON'); 
     WM_RBUTTONDOWN: ShowMessage('RBUTTON'); 
    else 
     Message.Result:=CallWindowProc(OrgWndProc, FmxHandleToHWND(Self.Handle), Message.MSG, Message.WParam, Message.LParam); 
    end; 
    end 
    else 
    Message.Result:=CallWindowProc(OrgWndProc, FmxHandleToHWND(Self.Handle), Message.MSG, Message.WParam, Message.LParam); 
end; 


procedure TForm14.FormCreate(Sender: TObject); 
var 
    LInstance : Pointer; 
begin 
    //get the current WndProc 
    OrgWndProc:= Pointer(GetWindowLong(FmxHandleToHWND(Self.Handle), GWL_WNDPROC)); 
    //Convert the class method to a Pointer 
    LInstance:=MakeObjectInstance(_WndProc); 
    //set the new WndProc 
    NewWndProc:= Pointer(SetWindowLong(FmxHandleToHWND(Self.Handle), GWL_WNDPROC, IntPtr(LInstance))); 

    with TrayIconData do 
    begin 
    cbSize := SizeOf; 
    Wnd := FmxHandleToHWND(self.Handle); 
    uID := 0; 
    uFlags := NIF_MESSAGE + NIF_ICON + NIF_TIP; 
    uCallbackMessage := WM_ICONTRAY; 
    hIcon := GetClassLong(FmxHandleToHWND(self.Handle), GCL_HICONSM); 
    StrPCopy(szTip, 'testapp'); 
    end; 
end; 

end. 
+0

Спасибо, это работает как ожидалось. Является ли одно из двух предложений (другое - использование выделенного окна AllocateHWnd, предложенного TLama) более результативным/общим/общепринятым или это вопрос предпочтения? – DNR

+0

@Heina, таким образом вы перехватываете оконную процедуру формы, которая, кстати, не такая элегантная, как если бы вы, например, обернули это фиктивное окно в свой собственный компонент значка в трее для платформы FMX Windows. – TLama

+0

В качестве опции вы можете использовать оконные крючки вместо переопределения 'WndProc'; вы можете найти несколько примеров в моем болотном сообщении об обработке сообщений Windows в firemonkey, которые я написал неделю назад: http://teran.karelia.pro/articles/item_6148.html на самом деле это на русском языке, но код находится в delphi;) Но использование крючков не так эффективно, как замена 'wndproc' – teran

Смежные вопросы