2010-08-04 29 views
11

Reconozco que esta es una pregunta popular, pero no pude encontrar nada que respondiera exactamente, pero me disculpo si me perdí algo en mis búsquedas.Controles de medición creados en tiempo de ejecución en WPF

Estoy tratando de crear y luego medir un control en tiempo de ejecución usando el siguiente código (las mediciones a continuación, serán utilizados para marquesina de estilo desplazarse los controles - cada control tiene un tamaño diferente):

Label lb = new Label(); 
lb.DataContext= task; 
Style style = (Style)FindResource("taskStyle"); 
lb.Style = style; 

cnvMain.Children.Insert(0,lb); 

width = lb.RenderSize.Width; 
width = lb.ActualWidth; 
width = lb.Width; 

El código crea un control Label y le aplica un estilo. El estilo contiene mi plantilla de control que se une al objeto "tarea". Cuando creo el elemento que aparece a la perfección, sin embargo cuando intento para medir el control de uso de cualquiera de las propiedades antes consigo los resultados siguientes (me paso a través de e inspeccionar cada propiedad a su vez):

lb.Width = NaN 
lb.RenderSize.Width = 0 
lb.ActualWidth = 0 

¿Hay alguna forma de obtener la altura y el ancho representados de un control que creas en el tiempo de ejecución?

ACTUALIZACIÓN:

Lo siento por anular la selección de sus soluciones como respuestas. Trabajan en un sistema de ejemplo básico que configuré, pero aparentemente no con mi solución completa.

Creo que puede tener algo que ver con el estilo, lo siento por el desastre, pero estoy pegando todo aquí.

En primer lugar, los recursos:

<Storyboard x:Key="mouseOverGlowLeave"> 
     <DoubleAnimation From="8" To="0" Duration="0:0:1" BeginTime="0:0:2" Storyboard.TargetProperty="GlowSize" Storyboard.TargetName="Glow"/> 
    </Storyboard> 

    <Storyboard x:Key="mouseOverTextLeave"> 
     <ColorAnimation From="{StaticResource buttonLitColour}" To="Gray" Duration="0:0:3" Storyboard.TargetProperty="Color" Storyboard.TargetName="ForeColour"/> 
    </Storyboard> 

    <Color x:Key="buttonLitColour" R="30" G="144" B="255" A="255" /> 

    <Storyboard x:Key="mouseOverText"> 
     <ColorAnimation From="Gray" To="{StaticResource buttonLitColour}" Duration="0:0:1" Storyboard.TargetProperty="Color" Storyboard.TargetName="ForeColour"/> 
    </Storyboard> 

Y el estilo propio:

<Style x:Key="taskStyle" TargetType="Label"> 
     <Setter Property="Template"> 
      <Setter.Value> 
       <ControlTemplate> 
        <Canvas x:Name="cnvCanvas"> 
         <Border Margin="4" BorderBrush="Black" BorderThickness="3" CornerRadius="16"> 
          <Border.Background> 
           <LinearGradientBrush StartPoint="0,0" EndPoint="0,1"> 
            <GradientStop Offset="1" Color="SteelBlue"/> 
            <GradientStop Offset="0" Color="dodgerBlue"/> 
           </LinearGradientBrush> 
          </Border.Background> 
          <DockPanel Margin="8"> 
           <DockPanel.Resources> 
            <Style TargetType="TextBlock"> 
             <Setter Property="FontFamily" Value="corbel"/> 
             <Setter Property="FontSize" Value="40"/> 
            </Style> 
           </DockPanel.Resources> 
           <TextBlock VerticalAlignment="Center" Margin="4" Text="{Binding Path=Priority}"/> 
           <DockPanel DockPanel.Dock="Bottom"> 
            <Border Margin="4" BorderBrush="SteelBlue" BorderThickness="1" CornerRadius="4"> 
             <TextBlock Margin="4" DockPanel.Dock="Right" Text="{Binding Path=Estimate}"/> 
            </Border> 
            <Border Margin="4" BorderBrush="SteelBlue" BorderThickness="1" CornerRadius="4"> 
             <TextBlock Margin="4" DockPanel.Dock="Left" Text="{Binding Path=Due}"/> 
            </Border> 
           </DockPanel> 
           <DockPanel DockPanel.Dock="Top"> 
            <Border Margin="4" BorderBrush="SteelBlue" BorderThickness="1" CornerRadius="4"> 
             <TextBlock Margin="4" Foreground="LightGray" DockPanel.Dock="Left" Text="{Binding Path=List}"/> 
            </Border> 
            <Border Margin="4" BorderBrush="SteelBlue" BorderThickness="1" CornerRadius="4"> 
             <TextBlock Margin="4" Foreground="White" DockPanel.Dock="Left" Text="{Binding Path=Name}"/> 
            </Border> 
           </DockPanel> 
          </DockPanel> 
         </Border> 
        </Canvas> 
       </ControlTemplate> 
      </Setter.Value> 
     </Setter> 
    </Style> 

Además, el código que estoy usando:

Label lb = new Label(); //the element I'm styling and adding 
    lb.DataContext= task; //set the data context to a custom class 
    Style style = (Style)FindResource("taskStyle"); //find the above style 
    lb.Style = style; 

    cnvMain.Children.Insert(0,lb); //add the style to my canvas at the top, so other overlays work 
    lb.UpdateLayout(); //attempt a full layout update - didn't work 
    Dispatcher.Invoke(DispatcherPriority.Render, new Action(() => 
    { 
     //Synchronize on control rendering 
     double width = lb.RenderSize.Width; 
     width = lb.ActualWidth; 
     width = lb.Width; 
    })); //this didn't work either 
    lb.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); //nor did this 
    double testHeight = lb.DesiredSize.Height; 
    double testWidth = lb.RenderSize.Width; //nor did this 
    width2 = lb.ActualWidth; //or this 
    width2 = lb.Width; //or this 

//positioning for my marquee code - position off-screen to start with 
    Canvas.SetTop(lb, 20); 
    Canvas.SetTop(lb, -999); 

//tried it here too, still didn't work 
    lb.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); 
    testHeight = lb.DesiredSize.Height; 
//add my newly-created label to a list which I access later to operate the marquee 
    taskElements.AddLast(lb); 
//still didn't work 
    lb.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); 
    testHeight = lb.DesiredSize.Height; 
//tried a mass-measure to see if that would work - it didn't 
    foreach (UIElement element in taskElements) 
    { 
     element.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); 
    } 

Cada vez que alguno de Se llama el valor devuelto como 0 o no es un número, sin embargo, cuando se representa la etiqueta, claramente tiene un tamaño tal como es visible. He intentado ejecutar este código desde la pulsación de un botón cuando la etiqueta está visible en la pantalla, pero eso produce los mismos resultados.

¿Es por el estilo que estoy usando? Databindings tal vez? ¿Es obvio que lo que hice mal es obvio o que los Gremlins de WPF solo realmente me odian?

Segundo informe de actualización:

Después se busca más que he descubierto que la Medida() sólo se aplica al tamaño de los elementos originales. Si una plantilla de control modifica esto agregando controles, entonces el mejor método sería medir cada uno, pero admito que es más que un poco desordenado.

El compilador debe tener alguna manera de medir todos los contenidos de un control, ya que debe usar eso para colocar elementos en, por ejemplo, paneles de pila. Debe haber alguna forma de acceder a él, pero por ahora estoy completamente sin ideas.

+0

¿Dónde está ejecutando este código? En el constructor de la ventana? Si es así, debe esperar hasta que se hayan procesado los controles. – Carlo

Respuesta

1

¡Solucionado!

El problema estaba en mi XAML. En el nivel más alto de la plantilla de mi etiqueta, había un lienzo principal que no tenía un campo de alto o ancho. Como esto no tuvo que modificar su tamaño para sus hijos, se estableció constantemente en 0,0. Al eliminarlo y reemplazar el nodo raíz por un borde, que debe cambiar de tamaño para ajustarse a sus elementos secundarios, los campos de altura y ancho se actualizan y propagan de nuevo a mi código en las llamadas a Measure().

3

Soy bastante nuevo en WPF, pero tengo entendido. Cuando actualiza o crea controles programáticamente, hay un mecanismo no obvio que también funciona (para un principiante como yo, aunque he hecho el procesamiento de mensajes de Windows antes, esto me llamó la atención ...). Actualizaciones y creación de controles de cola de mensajes asociados a la cola de despacho de UI, donde el punto importante es que estos serán procesados ​​en algún momento en el futuro. Ciertas propiedades dependen de que estos mensajes se procesen, p. Actual Ancho Los mensajes en este caso hacen que se represente el control y luego se actualicen las propiedades asociadas a un control renderizado.

No es obvio cuando se crean controles programáticamente que hay un proceso de mensajes asíncronos y que hay que esperar a que se procesen estos mensajes antes de que algunas de las propiedades se hayan actualizado, p. Actual Ancho

Si espera para los mensajes existentes para ser procesados ​​antes de acceder ActualWidth, a continuación, se han actualizado:

//These operations will queue messages on dispatch queue 
    Label lb = new Label(); 
    canvas.Children.Insert(0, lb); 

    //Queue this operation on dispatch queue 
    Dispatcher.Invoke(DispatcherPriority.Render, new Action(() => 
    { 
     //Previous messages associated with creating and adding the control 
     //have been processed, so now this happens after instead of before... 
     double width = lb.RenderSize.Width; 
     width = lb.ActualWidth; 
     width = lb.Width;  
    })); 

actualización

En respuesta a su comentario. Si desea agregar otro código, puede organizar su código de la siguiente manera. La cosa importante es que el distribuidor de llamadas asegura que espere hasta que los controles se han rendido ante su código que depende de ellos siendo ejecuta prestados:

Label lb = new Label(); 
    canvas.Children.Insert(0, lb); 

    Dispatcher.Invoke(DispatcherPriority.Render, new Action(() => 
    { 
     //Synchronize on control rendering 
    })); 

    double width = lb.RenderSize.Width; 
    width = lb.ActualWidth; 
    width = lb.Width; 

    //Other code... 
+0

Ah, eso es excelente gracias. ¿Hay algún tipo de evento o valor booleano que verificar para saber cuándo se procesaron los mensajes existentes? Tengo un código excesivo que también se basa en verificar estos valores que deben ejecutarse de forma secuencial después de la creación del control. Si no, estoy seguro de que puedo encontrar una solución de algún tipo. –

19

El control no tendrá un tamaño de hasta WPF hace un pase diseño . Creo que eso ocurre de manera asincrónica. Si está haciendo esto en su constructor, puede enganchar el evento Loaded - el diseño habrá sucedido para entonces, y cualquier control que haya agregado en el constructor habrá sido dimensionado.

Sin embargo, otra forma es pedirle al control que calcule qué tamaño quiere que sea. Para hacerlo, llame al Measure y páselo por un tamaño sugerido. En este caso, desea pasar un tamaño infinito (es decir, el control puede ser tan grande como le gusta), ya que eso es lo que la lona se va a hacer en el pase diseño:

lb.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); 
Debug.WriteLine(lb.DesiredSize.Width); 

La llamada medir doesn' En realidad, cambia el ancho, el tamaño del renderizado o el ancho real del control. Simplemente le dice a la etiqueta que calcule qué tamaño quiere que sea, y pone ese valor en su DesiredSize (una propiedad cuyo único propósito es mantener el resultado de la última llamada a Measure).

+0

Es una solución muy elegante, pero ambas son viables para diferentes situaciones, así que las mantendré ambas como respuestas. Creo que este se ajusta mejor a mi problema, gracias. –

+1

+1 para esta solución limpia y elegante. –

+0

Tengo un DataContext configurado en mi control que debería hacer que el control sea más alto que su 'tamaño preferido' predeterminado. Tengo un 'UserControl' creado con una altura de' Auto'. Esta respuesta no parece funcionar en esta situación. Tuve que hacer 'UpdateLayout()' después de configurar DataContext para hacer que la altura 'creciera'. –

Cuestiones relacionadas