Comment puis-je dire manuellement un propriétaire tiré WPF Control pour l'actualisation / redessiner sans exécuter la mesure ou organiser des laissez-passer?

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

Question

Nous faisons le dessin personnalisé dans la sous-classe OnRender d'un contrôle. Ce code de dessin est basé sur un déclencheur externe et les données. En tant que tel, chaque fois que les feux de déclenchement, nous devons re-rendre le contrôle sur la base de ces données. Ce que nous essayons de faire est de savoir comment forcer le contrôle à nouveau rendu, mais sans passer par une passe de mise en page entière.

Comme indiqué ci-dessus, la plupart des réponses que je l'ai vu tourner autour de la Visual qui invalidant invalident la mise en page qui mesure les forces nouvelles et organiser des passes qui est très cher, surtout pour les arbres visuels très complexes comme le nôtre. Mais encore une fois, la mise en page ne pas changement, ni ne le VisualTree. La seule chose qui est fait des données externes qui obtient rendu différemment. En tant que tel, cela est strictement une question purement rendu.

Encore une fois, nous cherchons juste un moyen simple de dire le contrôle qu'il a besoin de ré-exécuter OnRender. Je l'ai vu un « hack » dans lequel vous créez un nouveau DependencyProperty et l'enregistrer avec « AffectsRender » que vous venez de définir une certaine valeur lorsque vous souhaitez actualiser le contrôle, mais je suis plus intéressé par ce qui se passe à l'intérieur de la mise en œuvre par défaut pour ces propriétés. ce qu'ils appellent à influer sur ce comportement


Mise à jour:

Eh bien, on dirait qu'il n'y a pas un tel appel comme même le drapeau de AffectsRender provoque encore UN ARRANGEMENT passer en interne (selon la réponse de CodeNaked ci-dessous), mais je l'ai posté une deuxième réponse qui montre les comportements intégrés et comme un travail autour de supprimer votre code de passe de mise en page de courir avec un simple taille nullable comme un drapeau. Voir ci-dessous.

Était-ce utile?

La solution 2

Ok, je réponds à cela pour montrer aux gens pourquoi la réponse de CodeNaked est correcte, mais avec un astérisque si vous voulez, et aussi de fournir un travail autour. Mais en bon SO-citoyenneté, je marque toujours sa réponse que depuis sa réponse m'a conduit ici.

Mise à jour: Je l'ai depuis déplacé la réponse acceptée ici pour deux raisons. Un, je veux que les gens sachent là une solution à ce (la plupart des gens ne lisent la réponse acceptée et de passer) et deux, étant donné qu'il a un représentant de 25K, je ne pense pas qu'il » d esprit si je l'ai ramené! :)

Voici ce que je faisais. Pour tester cela, j'ai créé cette sous-classe ...

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

}

... que je posai comme ça (note qu'ils sont imbriquées):

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

Quand je redimensionné la fenêtre, je suis arrivé à ...

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

mais quand j'ai appelé InvalidateVisual sur « MainTestPanel » (dans le cas « Cliquez » du bouton), je me suis ce lieu ...

ArrangeOverride called for MainTestPanel.
OnRender called for MainTestPanel.

Notez comment aucun des remplacements de mesure ont été appelés, et seul le ArrangeOverride pour le contrôle externe a été appelé.

Il est pas parfait comme si vous avez un calcul très lourd à l'intérieur ArrangeOverride dans votre sous-classe (qui, malheureusement, nous faisons) qui obtient encore (re) exécuté, mais au moins les enfants ne tombent pas au même sort.

Cependant, si vous connaissez aucun des contrôles enfants ont une propriété avec le AffectsParentArrange bit set (encore une fois, que nous faisons), vous pouvez faire mieux et d'utiliser un Size Nullable comme un drapeau pour supprimer la logique de ArrangeOverride de re- entrée, sauf en cas de besoin, comme si ...

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

}

Maintenant, à moins que quelque chose doit spécifiquement exécuter à nouveau la logique organiser (comme un appel à MeasureOverride ne) vous obtenez seulement OnRender, et si vous voulez forcer explicitement la Arrangez logique, null simplement la taille, appelez InvalidateVisual et Bob votre oncle! :)

Hope this helps!

Autres conseils

Malheureusement, vous devez appeler InvalidateVisual , qui appelle InvalidateArrange en interne. La méthode de OnRender est appelée dans le cadre de l'organiser la phase, de sorte que vous devez dire WPF pour réorganiser le contrôle (qui InvalidateArrange fait) et qu'il a besoin de redessiner (qui InvalidateVisual fait).

L'option FrameworkPropertyMetadata.AffectsRender indique simplement WPF à l'appel InvalidateVisual lorsque les changements de propriété associés.

Si vous avez un contrôle (Appelons cette MainControl) qui outrepasse OnRender et contient plusieurs contrôles descendants, puis en appelant InvalidateVisual peut exiger que les contrôles descendants à réarrangées, ou même réévaluées. Mais je crois que WPF a des optimisations INPLACE pour éviter les contrôles descendants d'être réarrangé si leur espace disponible ne change pas.

Vous pouvez être en mesure de se déplacer en déplaçant votre logique de rendu à une commande séparée (disons NestedControl), ce qui serait un enfant visuel de MainControl. Le MainControl pourrait ajouter cela comme un enfant visuel automatiquement ou dans le cadre de ControlTemplate de, mais il devrait être l'enfant le plus bas dans l'ordre z. Vous pouvez ensuite exposer une méthode de type InvalidateNestedControl sur MainControl qui appellerait InvalidateVisual sur le NestedControl.

Vous ne devriez pas appellerez InvalidateVisual() à moins que la taille de vos changements de contrôle, et même alors, il y a d'autres façons de la cause re-mise en page.

Pour mettre à jour efficacement le visuel d'un contrôle sans changer la taille de lui. Utilisez un DrawingGroup. Vous créez le DrawingGroup et le mettre dans le DrawingContext pendant OnRender() et à tout moment après que vous pouvez Open() le DrawingGroup changer ses commandes de dessin visuel et WPF automatiquement et efficacement réengendrer cette partie de l'interface utilisateur . (Vous pouvez aussi utiliser cette technique avec RenderTargetBitmap si vous préférez avoir bitmap que vous pouvez apporter des modifications supplémentaires, plutôt que de redessiner à chaque fois)

Voici à quoi il ressemble:

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
}

Voici une autre astuce: http://geekswithblogs.net/NewThingsILearned/ archives / 2008/08/25 / rafraîchissement - mise à jour WPF-controls.aspx

En bref, vous appelez invoquer un délégué factice à DispatcherPriority.Render prioritaire, ce qui entraînera quoi que ce soit avec cette priorité ou ci-dessus pour être invoqué aussi, provoquant une rerender.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top