¿Cómo puedo decirle manualmente a un control WPF dibujado por el propietario que actualice/vuelva a dibujar sin ejecutar medidas o organizar pases?

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

Pregunta

Estamos haciendo dibujo personalizado en una subclase de control OnRender. Este código de dibujo se basa en un disparador externo y datos. Como tal, cada vez que el disparo se dispara, necesitamos volver a renderizar el control en función de esos datos. Lo que estamos tratando de hacer es averiguar cómo obligar al control a 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 la invalidación del Visual Lo que invalida el diseño que obliga a una nueva medida y organiza pases que es muy costoso, especialmente para árboles visuales muy complejos como lo es el nuestro. Pero de nuevo, el diseño sí no Cambiar, tampoco el VisualTree. Lo único que hace es los datos externos que se representan de manera diferente. Como tal, este es estrictamente un problema de renderizado puro.

Una vez más, solo estamos buscando una manera simple de decirle al control que necesita volver a ejecutar OnRender. He visto un 'hack' en el que creas un nuevo DependencyProperty Y regístrese con 'AffectSrender', que acaba de establecer en algún valor cuando desea actualizar el control, pero estoy más interesado en lo que está sucediendo dentro de la implementación predeterminada para esas propiedades: lo que llaman para afectar ese comportamiento.


Actualizar:

Bueno, parece que no hay ninguna llamada como la AffectsRender La bandera todavía causa un pase de arreglo internamente (según la respuesta de Codenake a continuación), pero he publicado una segunda respuesta que muestra los comportamientos incorporados, así como una solución para suprimir su código de paso de diseño con un tamaño anulable simple como una bandera. Vea abajo.

¿Fue útil?

Solución 2

Ok, estoy respondiendo a 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 al trabajo. Pero en buena Citizenship, sigo marcando la suya como respondida ya que su respuesta me llevó aquí.

ACTUALIZACIÓN: Desde entonces, he movido la respuesta aceptada a aquí por dos razones. Uno, quiero que la gente sepa allí es Una solución a 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 la recuperara! :)

Esto es lo que hice. Para probar esto, creé 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 presioné así (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 cambié el tamaño de 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 'mantenente' (en el evento 'Haga clic' del botón), obtuve esto en su lugar ...

ArrangeOverride called for MainTestPanel.
OnRender called for MainTestPanel.

Observe cómo se llamó a ninguna de las anulaciones de medición, y solo se llamó al control para el control externo.

No es perfecto como si tuvieras un cálculo muy pesado en el interior ArrangeOverride En su subclase (que desafortunadamente lo hacemos) que todavía se ejecuta (re), pero al menos los niños no caen al mismo destino.

Sin embargo, si sabe que ninguno de los controles de los niños tiene una propiedad con el conjunto de bits AffectSparentRange (nuevamente, lo que hacemos), puede ir mejor y usar un anulable Size como una bandera para suprimir la lógica ArrangeOverride del reingreso, excepto cuando sea necesario, como así ...

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 mediroverride), solo obtienes OnRender, y si quieres forzar explícitamente la lógica de arreglos, ¡simplemente anule el tamaño, llame InvalidateVisual y Bob es tu tío! :)

¡Espero que esto ayude!

Otros consejos

Desafortunadamente, debes llamar Invalidatevisual, que llama Invalidatearrange internamente. los OnRender El método se denomina parte de la fase de arreglo, por lo que debe decirle a WPF que reorganice el control (que invalidatearrange hace) y que necesita volver a dibujar (lo que invalidan Visual).

los FrameworkPropertyMetadata.AffectsRender La opción simplemente le dice a WPF que llame InvalidateVisual Cuando cambia la propiedad asociada.

Si tiene un control (llamemos a este control principal) que anule a Onrender y contiene varios controles descendientes, entonces llamar a InvalidateVisual puede requerir que los controles descendientes se reorganicen o incluso se remoase. Pero creo que WPF tiene optimizaciones en el lugar para evitar que los controles descendientes se reorganicen si su espacio disponible no cambia.

Es posible que pueda evitar esto moviendo su lógica de renderizado a un control separado (digamos NestedControl), que sería un niño visual de MainControl. El control principal podría agregar esto como un niño visual automáticamente o como parte de su control de control, pero debería ser el niño más bajo en el orden z. Entonces podrías exponer un InvalidateNestedControl Método de tipo en MainControl que llamaría a InvalidateVisual en NestedControl.

No deberías llamar InvalidateVisual() A menos que cambie el tamaño de su control, e incluso entonces hay otras formas de reiniciar.

Para actualizar eficientemente la visual de un control sin cambiar su tamaño. Utilizar una DrawingGroup. Creas el grupo de dibujo y lo pones en el DrawingContext durante OnRender() y luego en cualquier momento después de eso puedes Open() la DrawingGroup Para cambiar sus comandos de dibujo visual, y WPF lo hará automáticamente y eficientemente Vuelva a renderizar esa parte de la interfaz de usuario. (También puede usar esta técnica con RenderTargetBitmap Si prefiere tener mapa de bits al que puede hacer 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
}

Aquí hay otro truco:http://geekswithblogs.net/newthingsilearned/archive/2008/08/25/refresh-update-wpf-controls.aspx

En resumen, llamas a Invok a un delegado ficticio en prioridad despachador.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top