Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   Windows-Messages sinnvoll verteilen (https://www.delphipraxis.net/182399-windows-messages-sinnvoll-verteilen.html)

TiGü 21. Okt 2014 17:01

Windows-Messages sinnvoll verteilen
 
Liste der Anhänge anzeigen (Anzahl: 1)
Hallo Gemeinde,

in einen alten, von mir betreuten Programm wird sehr viel mit PostMessage gearbeitet, um von Unterformularen oder Threads irgendetwas an das Hauptformular zu schicken/signalisieren.

Da gibt es an ein paar Stellen im Quelltext Notifizierungslisten (normales TList) oder es werden TWinControl-Objekte oder deren Handles per langer Constructor-Kette an den richtigen Ort gereicht, um die PostMessage mit einen Windowhandle zu versorgen.
An einer Stelle gibt es auch eine böse globale Variable, wo die Instanz des Hauptformulars gespeichert wird. :evil:

Alles in allen sehr unschön, durcheinander und irgendwie unnötig.
Viele Programmteile bzw. Units müssen sich kennen, obwohl sie es eigentlich nicht müssten.

Ich spiele seit einiger Zeit mit dem Gedanken den folgenden globalen Verteil-Mechanismus zu implementieren und würde mich über Kritik und Anregungen freuen!
Delphi-Quellcode:
interface

uses
  Messages,
  System.Generics.Collections,
  Winapi.Windows,
  Vcl.Controls,
  System.SyncObjs;

type
  IMessageDistributor = interface
    procedure Subscribe(const Msg : UINT; const Listener : TWinControl); overload;
    procedure Subscribe(const Msgs : array of UINT; const Listener : TWinControl); overload;
    procedure Unsubscribe(const Msg : UINT; const Listener : TWinControl); overload;
    procedure Unsubscribe(const Msgs : array of UINT; const Listener : TWinControl); overload;
    function PostMessageToSubscribers(const Msg : UINT; const WParameter : WPARAM = 0; const LParameter : LPARAM = 0) : Boolean;
  end;

  TMessageDistributor = class(TInterfacedObject, IMessageDistributor)
  strict private
  type
    TListenerList = TList<TWinControl>;
  strict private
    FMessageToListenerMapping : TDictionary<UINT, TListenerList>;
    FCritcalSection : TCriticalSection;
  public
    constructor Create;
    destructor Destroy; override;

    procedure Subscribe(const Msg : UINT; const Listener : TWinControl); overload;
    procedure Subscribe(const Msgs : array of UINT; const Listener : TWinControl); overload;
    procedure Unsubscribe(const Msg : UINT; const Listener : TWinControl); overload;
    procedure Unsubscribe(const Msgs : array of UINT; const Listener : TWinControl); overload;

    function PostMessageToSubscribers(const Msg : UINT; const WParameter : WPARAM = 0; const LParameter : LPARAM = 0) : Boolean;
  end;

function MessageDistributor : IMessageDistributor;

implementation

var
  _MessageDistributor : IMessageDistributor;

function MessageDistributor : IMessageDistributor;
begin
  if not Assigned(_MessageDistributor) then
  begin
    _MessageDistributor := TMessageDistributor.Create;
  end;
  Result := _MessageDistributor;
end;

{ TMessageDistributor }

constructor TMessageDistributor.Create;
begin
  FCritcalSection := TCriticalSection.Create;
  FMessageToListenerMapping := TObjectDictionary<UINT, TListenerList>.Create([doOwnsValues]);
end;

destructor TMessageDistributor.Destroy;
begin
  FMessageToListenerMapping.Free;
  FCritcalSection.Free;
  inherited;
end;

function TMessageDistributor.PostMessageToSubscribers(const Msg : UINT; const WParameter : WPARAM = 0; const LParameter : LPARAM = 0) : Boolean;
var
  Listeners : TListenerList;
  Listener : TWinControl;
begin
  Result := False;
  FCritcalSection.Enter;
  try
    if FMessageToListenerMapping.TryGetValue(Msg, Listeners) then
    begin
      for Listener in Listeners do
      begin
        if Assigned(Listener) then
        begin
          Result := PostMessage(Listener.Handle, Msg, WParameter, LParameter);
        end;
      end;
    end;
  finally
    FCritcalSection.Leave;
  end;
end;

procedure TMessageDistributor.Subscribe(const Msgs : array of UINT; const Listener : TWinControl);
var
  I : Integer;
begin
  for I := Low(Msgs) to High(Msgs) do
  begin
    Subscribe(Msgs[I], Listener);
  end;
end;

procedure TMessageDistributor.Unsubscribe(const Msgs : array of UINT; const Listener : TWinControl);
var
  I : Integer;
begin
  for I := Low(Msgs) to High(Msgs) do
  begin
    Unsubscribe(Msgs[I], Listener);
  end;
end;

procedure TMessageDistributor.Subscribe(const Msg : UINT; const Listener : TWinControl);
var
  Listeners : TListenerList;
begin
  if FMessageToListenerMapping.TryGetValue(Msg, Listeners) then
  begin
    if Assigned(Listeners) then
    begin
      if not Listeners.Contains(Listener) then
        Listeners.Add(Listener);
    end;
  end
  else
  begin
    Listeners := TListenerList.Create;
    Listeners.Add(Listener);
    FMessageToListenerMapping.Add(Msg, Listeners);
  end;
end;

procedure TMessageDistributor.Unsubscribe(const Msg : UINT; const Listener : TWinControl);
var
  Listeners : TListenerList;
begin
  if FMessageToListenerMapping.TryGetValue(Msg, Listeners) then
  begin
    if Assigned(Listeners) then
    begin
      Listeners.Remove(Listener);
    end;
  end;
end;
Hier noch kleines Beispielprojekt (mit XE3 erstellt), was sinnbefreite Beispiele der Anwendung zeigt.

Uwe Raabe 21. Okt 2014 17:08

AW: Windows-Messages sinnvoll verteilen
 
Weiß nicht ob es passt, aber vielleicht gibt es ein paar Anregungen: Event-Driven, Asynchronous Development with Delphi and the LKSL

Sir Rufo 21. Okt 2014 17:57

AW: Windows-Messages sinnvoll verteilen
 
Warum willst du dich überhaupt auf Windows-Messages und auch die Verwendung von
Delphi-Quellcode:
PostMessage
festlegen?

Nimm doch einfach
Delphi-Quellcode:
TThread.Queue
und schon geht das auch auf jeder Plattform.

TiGü 22. Okt 2014 09:28

AW: Windows-Messages sinnvoll verteilen
 
Zitat:

Zitat von Sir Rufo (Beitrag 1276871)
Warum willst du dich überhaupt auf Windows-Messages und auch die Verwendung von
Delphi-Quellcode:
PostMessage
festlegen?

Nun ja, es ist eine über 10 Jahre alte Windows-Desktop-VCL-Anwendung, die wohl bis die Hölle zufriert nicht das Licht der Apfelsonne sehen wird oder gegen Androiden bestehen muss.
Von daher ist die "Einschränkung" auf Windows-Messages in dem Sinne keine.

Zitat:

Zitat von Sir Rufo (Beitrag 1276871)
Nimm doch einfach
Delphi-Quellcode:
TThread.Queue
und schon geht das auch auf jeder Plattform.

Verstehe den Ansatz mit Queue so, dass dadurch von gerade laufenden Threads aus entsprechende Methoden des Threads im Formularkontext/VCL-Main-Thread ausgeführt werden, richtig?
Häufig senden sich in meinem Anwendungsfall aber auch Unterformulare untereinander oder zum Hauptformular Benachrichtigungen.
Wie würde man das mit TThread.Queue lösen?

Des Weiteren besteht ja schon die Botschaftsbehandlung durch entsprechende Message-Handler in den Formularen, so dass ich "nur" relativ schmerzlos das anmelden und senden der Botschaften in diesem Ansatz anpassen müsste.

Ich wollte mit dem Posten des Quelltextes nur sicher gehen, dass ich nicht irgendetwas Elementares übersehen habe.
Unter der Prämisse, dass es halt nur auf Windows läuft, was hältst du denn von diesem Lösungsansatz zum Entkoppeln der einzelnen Units untereinander?

TiGü 22. Okt 2014 09:28

AW: Windows-Messages sinnvoll verteilen
 
Zitat:

Zitat von Uwe Raabe (Beitrag 1276868)
Weiß nicht ob es passt, aber vielleicht gibt es ein paar Anregungen: Event-Driven, Asynchronous Development with Delphi and the LKSL

Vielen Dank, beim ersten Überfliegen sieht das nach interessanter Lektüre aus.
Habe mir ein Lesezeichen gesetzt und werde das am Wochenende mal ausführlicher studieren.


Alle Zeitangaben in WEZ +1. Es ist jetzt 05:34 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz