Come posso dire manualmente un controllo WPF proprietario disegnato per aggiornare / ridisegnare senza eseguire misura o organizzare passaggi?

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

Domanda

Stiamo facendo il disegno personalizzato in OnRender di una sottoclasse di controllo. Questo codice di disegno è basato su un trigger esterno e dati. Come tale, ogni volta che i fuochi di innesco, abbiamo bisogno di ri-rendering del controllo in base a tali dati. Quello che stiamo cercando di fare è trovare il modo di forzare il controllo per ri-renderizzare, ma senza passare attraverso un intero passaggio layout.

Come già detto, la maggior parte delle risposte che ho visto ruotano intorno invalidare la Visual che invalida la disposizione che le forze nuova misura e organizzare passaggi che è molto costoso, soprattutto per gli alberi visivi molto complessi come la nostra è. Ma ancora una volta, il layout non il cambiamento, né il VisualTree. L'unica cosa che fa è il dato esterno che viene reso in modo diverso. Come tale, questo è strettamente un problema di rendering puro.

Anche in questo caso, stiamo solo cercando un modo semplice per dire al controllo che ha bisogno di ri-eseguire OnRender. Ho visto un 'trucco', in cui si crea una nuova DependencyProperty e registrarlo con 'AffectsRender', che appena impostato a un valore quando si desidera aggiornare il controllo, ma sono più interessato a quello che sta succedendo all'interno della implementazione di default per quelle proprietà:. quello che chiamano per influenzare il comportamento


Aggiornamento:

Bene, sembra che non ci sia una tale chiamata come anche la bandiera AffectsRender causa ancora una Arrange passare internamente (come da risposta di CodeNaked sotto), ma ho postato una seconda risposta che mostra il built-in comportamenti come pure come un work-around per sopprimere l'codice di layout di esecuzione con una semplice dimensione annullabile come una bandiera. Vedi sotto.

È stato utile?

Soluzione 2

Ok, sto rispondere a questa per mostrare alla gente il motivo per cui la risposta di CodeNaked è corretta, ma con un asterisco se si vuole, e anche per fornire un work-around. Ma in buona SO-cittadinanza, sto ancora segna la sua come risposta dal momento che la sua risposta mi ha portato qui.

Aggiornamento: allora ho spostato la risposta accettata a qui per due motivi. Uno, voglio che la gente sappia ci è una soluzione a questo (la maggior parte delle persone di leggere solo la risposta accettata e andare avanti) e due, considerando che ha un rappresentante di 25K, io non credo che' mente d se ho portato indietro! :)

Ecco quello che ho fatto. Per verificare ciò, ho creato questo sottoclasse ...

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);
    }

}

... che ho disposto in questo modo (si noti che sono annidati):

<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>

Quando ho ridimensionato la finestra, ho ottenuto questo ...

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

ma quando ho chiamato InvalidateVisual su 'MainTestPanel' (in 'Click' evento del pulsante), ho ottenuto questo invece ...

ArrangeOverride called for MainTestPanel.
OnRender called for MainTestPanel.

Si noti come nessuna delle sostituzioni di misura sono stati chiamati, e solo l'ArrangeOverride per il controllo esterno è stato chiamato.

Non è perfetto, come se si dispone di un calcolo molto pesante all'interno ArrangeOverride nella sottoclasse (che purtroppo noi) che ancora si (ri) eseguito, ma almeno i bambini non cadere per lo stesso destino.

Tuttavia, se si conosce nessuno dei controlli figlio hanno una proprietà con l'AffectsParentArrange impostato il bit (ancora una volta, che lo facciamo), si può fare ancora meglio e utilizzare un Size Nullable come una bandiera per sopprimere la logica ArrangeOverride da ri- ingresso tranne quando necessario, in questo modo ...

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);
    }

}

Ora, a meno qualcosa deve specificamente per ri-eseguire la logica di organizzare (come una chiamata a MeasureOverride fa) si ottiene solo OnRender, e se si vuole forzare esplicitamente la logica Arrange, semplicemente nullo fuori le dimensioni, chiamare InvalidateVisual e Bob del tuo zio! :)

Spero che questo aiuti!

Altri suggerimenti

Purtroppo, si deve chiamare InvalidateVisual , che chiama InvalidateArrange internamente. Il metodo OnRender si chiama come parte della fase di organizzare, quindi è necessario dire a WPF per riorganizzare il controllo (che InvalidateArrange fa) e che ha bisogno di ridisegnare (che InvalidateVisual fa).

L'opzione FrameworkPropertyMetadata.AffectsRender dice semplicemente WPF alla chiamata InvalidateVisual quando cambia la proprietà associata.

Se si dispone di un controllo (chiamiamolo questo MainControl) che le sostituzioni OnRender e contiene diversi controlli discendente, quindi chiamando InvalidateVisual può richiedere i controlli discendenti per essere riordinati, o addirittura rideterminati. Ma credo che WPF ha ottimizzazioni Inplace per evitare controlli discendenti di essere risistemato se il loro spazio a disposizione è invariata.

Si può essere in grado di aggirare questo spostando la logica di rendering per un controllo separato (dire NestedControl), che sarebbe un bambino visiva di MainControl. Il MainControl potrebbe aggiungere questo come un bambino visiva automaticamente o come parte di essa di ControlTemplate, ma avrebbe bisogno di essere il bambino più bassi del z-order. È quindi possibile esporre un metodo di tipo InvalidateNestedControl su MainControl che avrebbe chiamato InvalidateVisual sul NestedControl.

Si dovrebbe non essere chiamata InvalidateVisual() meno che la dimensione delle modifiche di controllo, e anche allora ci sono altri modi per provocare ri-layout.

Per aggiornare in modo efficiente il visivo di un controllo senza cambiare la sua dimensione. Utilizzare un DrawingGroup. Si crea il DrawingGroup e metterlo nella DrawingContext durante OnRender() e poi in qualsiasi momento dopo che si può Open() la DrawingGroup di cambiare è comandi di disegno visivi, e WPF verrà automaticamente ed efficiente ri-renderizzare quella parte dell'interfaccia utente . (È anche possibile utilizzare questa tecnica con RenderTargetBitmap se si preferisce avere bitmap che è possibile apportare modifiche incrementali, invece di ridisegnare ogni volta)

Questo è ciò che sembra:

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
}

Ecco un altro trucco: http://geekswithblogs.net/NewThingsILearned/ archive / 2008/08/25 / aggiornamento - update-WPF-controls.aspx

In breve, si chiama invoke qualche delegato manichino DispatcherPriority.Render priorità, che farà sì che qualsiasi cosa con la priorità o superiore ad essere invocate anche, causando un nuovamente sottoposti a rendering.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top