Wie kann ich einem von Eigentümer gezeichneten WPF-Steuerelement manuell mitteilen, um zu aktualisieren, ohne Maßnahmen auszuführen oder Pässe zu arrangieren?

StackOverflow https://stackoverflow.com/questions/7801680

Frage

Wir machen benutzerdefinierte Zeichnen in einer Kontrollunterklasse einer Kontrollunterklasse OnRender. Dieser Zeichnungscode basiert auf einem externen Auslöser und Daten. Wenn der Auslöser feuert, müssen wir die Steuerung auf der Grundlage dieser Daten erneut übertragen. Wir versuchen herauszufinden, wie wir die Kontrolle zum erneuten Rendern zwingen können, ohne einen ganzen Layout-Pass zu durchlaufen.

Wie oben erwähnt, drehen sich die meisten Antworten, die ich gesehen habe Visual Dies macht das Layout ungültig, das neue Maßnahmen erzwingt und Pässe anordnet, was sehr teuer ist, insbesondere für sehr komplexe visuelle Bäume wie unsere. Aber das Layout tut es wiederum nicht Veränderung, und der VisualTree auch nicht. Das einzige, was die externen Daten tut, die anders gerendert werden. Daher ist dies ausschließlich ein reine Rendering -Problem.

Auch hier suchen wir nur nach einer einfachen Möglichkeit, die Kontrolle zu sagen, dass sie sich erneut ausführen muss OnRender. Ich habe einen "Hack" gesehen, in dem Sie einen neuen erstellen DependencyProperty und registrieren Sie es mit "Affektrender", den Sie gerade auf einen Wert festlegen, wenn Sie die Kontrolle aktualisieren möchten, aber ich interessiere mich mehr für das, was innerhalb der Standardimplementierung für diese Eigenschaften vor sich geht: was sie nennen, um dieses Verhalten zu beeinflussen.


Aktualisieren:

Nun, es sieht so aus, als ob es einen solchen Anruf wie das gibt AffectsRender Flag führt weiter eine Flagge. Siehe unten.

War es hilfreich?

Lösung 2

OK, ich beantworte dies, um den Leuten zu zeigen, warum die Antwort von Codenaked richtig ist, aber mit einem Sternchen, wenn Sie so wollen, und auch um eine Arbeit zu liefern. Aber in gutem so Staatsbürgerschaft markiere ich immer noch seine wie beantwortet, da mich seine Antwort hierher geführt hat.

UPDATE: Ich habe seitdem die akzeptierte Antwort aus zwei Gründen hier verschoben. Erstens möchte ich, dass die Leute dort wissen ist Eine Lösung dafür (die meisten Leute lesen nur die akzeptierte Antwort und gehen weiter) und zwei, wenn man bedenkt, dass er einen Repräsentanten von 25.000 hat, glaube ich nicht, dass es ihm etwas ausmacht, wenn ich sie zurücknehmen würde! :)

Hier ist, was ich getan habe. Um dies zu testen, habe ich diese Unterklasse erstellt ...

public class TestPanel : DockPanel
{
    protected override Size MeasureOverride(Size constraint)
    {
        System.Console.WriteLine("MeasureOverride called for " + this.Name + ".");
        return base.MeasureOverride(constraint);
    }

    protected override System.Windows.Size ArrangeOverride(System.Windows.Size arrangeSize)
    {
        System.Console.WriteLine("ArrangeOverride called for " + this.Name + ".");
        return base.ArrangeOverride(arrangeSize);
    }

    protected override void OnRender(System.Windows.Media.DrawingContext dc)
    {
        System.Console.WriteLine("OnRender called for " + this.Name + ".");
        base.OnRender(dc);
    }

}

... was ich so dargelegt habe (beachten Sie, dass sie verschachtelt sind):

<l:TestPanel x:Name="MainTestPanel" Background="Yellow">

    <Button Content="Test" Click="Button_Click" DockPanel.Dock="Top" HorizontalAlignment="Left" />

    <l:TestPanel x:Name="InnerPanel" Background="Red" Margin="16" />

</l:TestPanel>

Als ich das Fenster verändert habe, bekam ich das ...

MeasureOverride called for MainTestPanel.
MeasureOverride called for InnerPanel.
ArrangeOverride called for MainTestPanel.
ArrangeOverride called for InnerPanel.
OnRender called for InnerPanel.
OnRender called for MainTestPanel.

Aber als ich anrief InvalidateVisual Auf 'pootestPanel' (im Schaltfläche 'Click' Event) habe ich stattdessen ...

ArrangeOverride called for MainTestPanel.
OnRender called for MainTestPanel.

Beachten Sie, wie keine der Messüberschreibungen aufgerufen wurde, und nur die arrangoverride für die äußere Kontrolle wurde aufgerufen.

Es ist nicht perfekt, als hätten Sie im Inneren eine sehr starke Berechnung ArrangeOverride In Ihrer Unterklasse (was leider wir tun), das immer noch ausgeführt wird, fallen die Kinder jedoch nicht auf dasselbe Schicksal.

Wenn Sie jedoch wissen, dass keiner der Kinderkontrollen über eine Eigenschaft mit dem Affect -Sparentarrange -Bit (wieder) verfügt Size als Flagge zur Unterdrückung der arrangoverride-Logik vom Wiedereintritt, außer wenn nötig, so ...

public class TestPanel : DockPanel
{
    Size? arrangeResult;

    protected override Size MeasureOverride(Size constraint)
    {
        arrangeResult = null;
        System.Console.WriteLine("MeasureOverride called for " + this.Name + ".");
        return base.MeasureOverride(constraint);
    }

    protected override System.Windows.Size ArrangeOverride(System.Windows.Size arrangeSize)
    {
        if(!arrangeResult.HasValue)
        {
            System.Console.WriteLine("ArrangeOverride called for " + this.Name + ".");
            // Do your arrange work here
            arrangeResult = base.ArrangeOverride(arrangeSize);
        }

        return arrangeResult.Value;
    }

    protected override void OnRender(System.Windows.Media.DrawingContext dc)
    {
        System.Console.WriteLine("OnRender called for " + this.Name + ".");
        base.OnRender(dc);
    }

}

Wenn Sie nun die Logik der Vereinbarung erneut ausführen müssen (als Aufruf zur MessOverride), erhalten Sie nur Onrender, und wenn Sie die Anordnungslogik ausdrücklich erzwingen möchten, nutzen Sie einfach die Größe, rufen Sie Invalidatevisual an und Bob ist Ihr Onkel! :)

Hoffe das hilft!

Andere Tipps

Leider müssen Sie anrufen Ungültig, was ruft InvalidatearRange im Inneren. Das OnRender Die Methode wird als Teil der Anordnungsphase aufgerufen, sodass Sie WPF anweisen müssen, die Steuerung neu zu ordnen (die InvalidateArRange tut) und dass sie neu zeichnen muss (was Visualvisual tut).

Das FrameworkPropertyMetadata.AffectsRender Die Option fordert WPF einfach an, anzurufen InvalidateVisual Wenn sich die zugehörige Eigenschaft ändert.

Wenn Sie eine Kontrolle haben (nennen wir dieses Hauptcontrol), das Onrender überschreibt und mehrere Nachkommenskontrollen enthält, müssen die Nachkommenskontrollen möglicherweise neu angeordnet oder sogar entfernt werden. Ich glaube jedoch, dass WPF Optimierungen an Ort und Stelle hat, um zu verhindern, dass Nachkommenskontrollen neu angeordnet werden, wenn ihr verfügbarer Raum unverändert bleibt.

Möglicherweise können Sie dies umgehen, indem Sie Ihre Rendering -Logik auf eine separate Kontrolle (z. B. NestedControl) verlagern, die ein visuelles Kind von MainControl wäre. Das MainControl könnte dies als visuelles Kind automatisch oder als Teil der Steuerung von CONTROLTEMplate hinzufügen, aber es müsste das niedrigste Kind in der Z-Ordnung sein. Sie könnten dann a enthüllen InvalidateNestedControl Geben Sie die Methode auf MainControl ein, die auf der NestedControl ungültig als ungültig nennen würde.

Sie sollten nicht anrufen InvalidateVisual() Es sei denn, die Größe Ihrer Steuerung ändert sich und selbst dann gibt es andere Möglichkeiten, um eine erneute Layout zu verursachen.

Um das Visual eines Steuerelements effizient zu aktualisieren, ohne die Größe zu ändern. Verwenden ein DrawingGroup. Sie erstellen die Zeichengruppe und setzen sie in die DrawingContext während OnRender() Und dann können Sie jederzeit danach können Sie Open() das DrawingGroup Um die Befehle für visuelle Zeichnungen zu ändern, wird WPF automatisch und effizient Neuen Sie diesen Teil der Benutzeroberfläche neu. (Sie können diese Technik auch mit verwenden RenderTargetBitmap Wenn Sie es vorziehen, Bitmap zu haben, an denen Sie inkrementelle Änderungen vornehmen können, anstatt jedes Mal neu zu zeichnen)

So sieht es aus:

DrawingGroup backingStore = new DrawingGroup();

protected override void OnRender(DrawingContext drawingContext) {      
    base.OnRender(drawingContext);            

    Render(); // put content into our backingStore
    drawingContext.DrawDrawing(backingStore);
}

// I can call this anytime, and it'll update my visual drawing
// without ever triggering layout or OnRender()
private void Render() {            
    var drawingContext = backingStore.Open();
    Render(drawingContext);
    drawingContext.Close();            
}

private void Render(DrawingContext drawingContext) {
    // put your render code here
}

Hier ist ein weiterer Hack:http://geekswithblogs.net/newthingsilarned/archive/2008/08/25/refresh-update-wpf-controls.aspx

Kurz gesagt, Sie nennen einen Dummy -Delegierten bei Priority DispatcherPriority.render, was dazu führt, dass diese Priorität oder darüber auch aufgerufen wird, was zu einem renender führt.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top