¿Cómo puedo decirle manualmente a un Control WPF dibujado por el propietario que se actualice/vuelva a dibujar sin ejecutar un compás o organizar pases?
custom-controls redraw (4)
Aquí hay otro truco: http://geekswithblogs.net/NewThingsILearned/archive/2008/08/25/refresh--update-wpf-controls.aspx
En resumen, se llama a invocar a algún delegado ficticio en la prioridad DispatcherPriority.Render, lo que provocará que cualquier cosa con esa prioridad o superior también se invoque, causando un cambio de dirección.
Estamos haciendo dibujo personalizado en el control de una subclase de OnRender
. Este código de dibujo se basa en un disparador externo y datos. Como tal, siempre que se dispare el disparador, debemos volver a renderizar el control basado en esos datos. Lo que estamos tratando de hacer es descubrir cómo forzar el control para volver a renderizar, pero sin pasar por un pase de diseño completo.
Como se indicó anteriormente, la mayoría de las respuestas que he visto giran en torno a invalidar lo Visual
que invalida el diseño que obliga a tomar nuevas medidas y organizar pases, lo cual es muy costoso, especialmente para árboles visuales muy complejos como el nuestro. Pero, una vez más, el diseño no cambia, ni lo hace VisualTree. Lo único que hace es la información externa que se procesa de manera diferente. Como tal, esto es estrictamente un problema de representación pura.
Nuevamente, solo estamos buscando una forma sencilla de indicar al control que necesita volver a ejecutar OnRender
. He visto un ''hack'' en el que creas una nueva DependencyProperty
y la registras con ''AffectsRender'' que acabas de establecer en algún valor cuando deseas actualizar el control, pero estoy más interesado en lo que está sucediendo dentro de la implementación predeterminada Por esas propiedades: lo que llaman a afectar ese comportamiento.
Actualizar:
Bueno, parece que no hay tal llamada, ya que incluso el indicador AffectsRender
todavía causa un pase de Arreglo interno (según la respuesta de CodeNaked a continuación) pero he publicado una segunda respuesta que muestra los comportamientos incorporados, así como un trabajo -dirigido para evitar que su código de pase de diseño se ejecute con un tamaño anulable simple como una bandera. Vea abajo.
Desafortunadamente, debe llamar a InvalidateVisual , que llama a InvalidateArrange internamente. El método OnRender
se llama como parte de la fase de organización, por lo que necesita decirle a WPF que reorganice el control (lo que hace InvalidateArrange) y que necesita volver a dibujar (lo que hace InvalidateVisual).
La opción FrameworkPropertyMetadata.AffectsRender
simplemente le dice a WPF que llame a InvalidateVisual
cuando cambie la propiedad asociada.
Si tiene un control (llamemos a este MainControl) que reemplaza OnRender y contiene varios controles descendientes, entonces llamar a InvalidateVisual puede requerir que los controles descendientes se reorganicen, o incluso se vuelvan a medir. Pero creo que WPF tiene optimizaciones in situ para evitar que los controles descendientes se reorganicen si su espacio disponible no se modifica.
Es posible que pueda evitar esto moviendo su lógica de representación a un control separado (por ejemplo, NestedControl), que sería un hijo visual de MainControl. MainControl podría agregar esto como un elemento secundario visual automáticamente o como parte de su plantilla ControlTemplate, pero tendría que ser el elemento secundario más bajo en el orden z. Luego podría exponer un método de tipo InvalidateNestedControl
en MainControl que llamaría InvalidateVisual en NestedControl.
No debe llamar a InvalidateVisual()
menos que cambie el tamaño de su control, e incluso entonces hay otras formas de causar un nuevo diseño.
Para actualizar eficientemente la visual de un control sin cambiar su tamaño. Utilice un DrawingGroup
. Crea el Grupo de dibujo y lo pones en el OnRender()
durante OnRender()
y luego, en cualquier momento, puedes Open()
el DrawingGroup
para cambiar sus comandos de dibujo visual, y WPF volverá a representar de manera automática y eficiente esa parte de la IU. (También puede utilizar esta técnica con RenderTargetBitmap
si prefiere tener un mapa de bits en el que puede realizar cambios incrementales, en lugar de volver a dibujar cada vez)
Esto es lo que parece:
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
}
Ok, estoy respondiendo esto para mostrar a las personas por qué la respuesta de CodeNaked es correcta, pero con un asterisco si lo desea, y también para proporcionar una solución alternativa. Pero en una buena ciudadanía, todavía estoy marcando su respuesta, ya que su respuesta me llevó aquí.
Actualización: desde entonces he movido la respuesta aceptada aquí por dos razones. Uno, quiero que la gente sepa que hay una solución para esto (la mayoría de la gente solo lee la respuesta aceptada y sigue adelante) y dos, considerando que tiene un representante de 25K, no creo que le importe si lo recupero. ! :)
Esto es lo que hice. Para probar esto, he creado esta subclase ...
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 puse de esta manera (tenga en cuenta que están anidados):
<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>
Cuando redimensioné la ventana, obtuve esto ...
MeasureOverride called for MainTestPanel.
MeasureOverride called for InnerPanel.
ArrangeOverride called for MainTestPanel.
ArrangeOverride called for InnerPanel.
OnRender called for InnerPanel.
OnRender called for MainTestPanel.
pero cuando llamé InvalidateVisual
en ''MainTestPanel'' (en el evento ''Click'' del botón), obtuve esto en su lugar ...
ArrangeOverride called for MainTestPanel.
OnRender called for MainTestPanel.
Observe cómo no se llamó a ninguna de las anulaciones de medición, y solo se llamó a ArrangeOverride para el control externo.
No es perfecto como si tuviera un cálculo muy pesado dentro de ArrangeOverride
en su subclase (que desafortunadamente tenemos) que todavía se ejecuta (re), pero al menos los niños no caen en el mismo destino.
Sin embargo, si sabe que ninguno de los controles secundarios tiene una propiedad con el conjunto de bits AffectsParentArrange (de nuevo, lo que hacemos), puede ir mejor y usar un Size
Nullable como un indicador para evitar que la lógica ArrangeOverride vuelva a entrar, excepto cuando necesitado, como tal ...
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);
}
}
Ahora, a menos que algo específicamente necesite volver a ejecutar la lógica de organización (como lo hace una llamada a MeasureOverride), solo obtienes OnRender, y si quieres forzar explícitamente la lógica de organización, simplemente anula el tamaño, llama a InvalidateVisual y ¡Bob es tu tío! :)
¡Espero que esto ayude!