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?
-
22-10-2019 - |
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.
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.