2017-10-31 7 views
8

Ich versuche, meine eigene Zeichnung auf einem Steuerelement zu implementieren, wenn es keinen Fokus hat (Ellipsen in TEdit anzeigen, wenn der Editor seinen Text nicht vollständig anzeigt). So Star I א ed mit diesem Code:TEdit und WM_PAINT Message-Handler merkwürdiges Verhalten

type 
    TEdit = class(StdCtrls.TEdit) 
    private 
    FEllipsis: Boolean; 
    FCanvas: TCanvas; 
    procedure WMPaint(var Message: TWMPaint); message WM_PAINT; 
    public 
    constructor Create(AOwner: TComponent); override; 
    destructor Destroy; override; 
    end; 

constructor TEdit.Create(AOwner: TComponent); 
begin 
    inherited Create(AOwner); 
    FEllipsis := False; 
    FCanvas := TControlCanvas.Create; 
    TControlCanvas(FCanvas).Control := Self; 
end; 

destructor TEdit.Destroy; 
begin 
    FCanvas.Free; 
    inherited; 
end; 

procedure TEdit.WMPaint(var Message: TWMPaint); 
begin 
    if FEllipsis and (not Focused) then 
    begin 
    // Message.Result := 0; 
    // TODO... 
    end 
    else 
    inherited; 
end; 

Beachten Sie, dass, wenn FEllipsis and (not Focused) der Nachrichten-Handler nichts tut.

Nun fiel mir ein TButton und 2 TEdit Steuerelemente auf dem Formular, und hinzugefügt Form OnCreate:

procedure TForm1.FormCreate(Sender: TObject); 
begin 
    Edit2.FEllipsis := True; 
end; 

ich Edit1 normalerweise zu ziehen erwartet und Edit2 nichts in dem Edit-Control zu ziehen.

Stattdessen wurde der Message-Handler endlos verarbeitet, Edit1 wurde auch nicht gezeichnet, und die gesamte Anwendung war erstickt (mit 25% CPU-Auslastung!). Ich habe auch versucht, Message.Result := 0 zurückzukehren - gleicher Effekt.

Jetzt für den "seltsamen" Teil: Wenn ich das Canvas-Handle mit BeginPaint bekomme, funktioniert alles wie erwartet.

procedure TEdit.WMPaint(var Message: TWMPaint); 
var 
    PS: TPaintStruct; 
begin 
    if FEllipsis and (not Focused) then 
    begin  
    if Message.DC = 0 then 
     FCanvas.Handle := BeginPaint(Handle, PS) 
    else 
     FCanvas.Handle := Message.DC; 
    try 
     // paint on FCanvas... 
    finally 
     FCanvas.Handle := 0; 
     if Message.DC = 0 then EndPaint(Handle, PS); 
    end; 
    end 
    else 
    inherited; 
end; 

Beachten Sie, dass ich nicht inherited entweder aufgerufen habe.

Wie wird dieses Verhalten erläutert? Vielen Dank.

Antwort

13

Wenn ein Fenster ungültig wird, wird es aufgefordert, sich beim nächsten Malzyklus gültig zu machen. In der Regel geschieht dies in der Hauptthread-Nachrichtenschleife, wenn GetMessage feststellt, dass die Warteschlange leer ist. An diesem Punkt WM_PAINT Nachrichten werden synthetisiert und an das Fenster gesendet.

Wenn das Fenster diese Nachrichten empfängt, muss es sich selbst malen. Dies wird typischerweise mit Aufrufen zu BeginPaint und dann EndPaint getan. Der Aufruf an BeginPaint validiert das Client-Rect des Fensters. Dies ist die kritische Information, die Ihnen fehlt.

Nun, in Ihrem Code, haben Sie nicht inherited anrufen und so nichts gemalt, und nicht BeginPaint/ anrufen. Da Sie BeginPaint nicht aufgerufen haben, bleibt das Fenster ungültig. Und so wird ein endloser Strom von WM_PAINT Nachrichten erzeugt. Gebiet

Die Beginpaint Funktion überprüft automatisch den gesamten Client:

Die entsprechende Dokumentation kann here finden.

+1

Vielen Dank! Die Dokumentation machte es klar: * "Das System generiert weiterhin WM_PAINT-Nachrichten, bis der aktuelle Aktualisierungsbereich validiert ist" * – zig

+1

Dies muss die beste kurze Beschreibung des Windows-Nachricht-Malprozesses sein, den ich bisher gelesen habe. –