2010-03-31 15 views
6

Así que estoy tratando de utilizar un control de usuario de WPF para generar una tonelada de imágenes a partir de un conjunto de datos, donde cada elemento en el conjunto de datos podría producir una imagen ...Dibujo de un control de usuario de WPF con DataBinding a una imagen

I Espero poder configurarlo de tal manera que pueda usar el enlace de datos WPF, y para cada elemento en el conjunto de datos, crear una instancia de mi control de usuario, establecer la propiedad de dependencia que corresponde a mi elemento de datos, y luego dibujar el control de usuario a una imagen, pero estoy teniendo problemas para que todo funcione (no estoy seguro de si mi problema es el enlace de datos o el dibujo)

Disculpe por el volcado masivo de código, pero he estado tratando de conseguirlo trabajando por un par de horas, y WPF simplemente no le gusto (que aprender en algún momento, aunque ...)

Mi Control de Usuario se ve así:

<UserControl x:Class="Bleargh.ImageTemplate" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
xmlns:c="clr-namespace:Bleargh" 
x:Name="ImageTemplateContainer" 
    Height="300" Width="300"> 
<Canvas> 
    <TextBlock Canvas.Left="50" Canvas.Top="50" Width="200" Height="25" FontSize="16" FontFamily="Calibri" Text="{Binding Path=Booking.Customer,ElementName=ImageTemplateContainer}" /> 
    <TextBlock Canvas.Left="50" Canvas.Top="100" Width="200" Height="25" FontSize="16" FontFamily="Calibri" Text="{Binding Path=Booking.Location,ElementName=ImageTemplateContainer}" /> 
    <TextBlock Canvas.Left="50" Canvas.Top="150" Width="200" Height="25" FontSize="16" FontFamily="Calibri" Text="{Binding Path=Booking.ItemNumber,ElementName=ImageTemplateContainer}" /> 
    <TextBlock Canvas.Left="50" Canvas.Top="200" Width="200" Height="25" FontSize="16" FontFamily="Calibri" Text="{Binding Path=Booking.Description,ElementName=ImageTemplateContainer}" /> 
</Canvas> 
</UserControl> 

Y he añadido una propiedad de dependencia del tipo de "reserva" para mi control de usuario que estoy esperando que será la fuente de los valores de enlace de datos:

public partial class ImageTemplate : UserControl 
{ 
    public static readonly DependencyProperty BookingProperty = DependencyProperty.Register("Booking", typeof(Booking), typeof(ImageTemplate)); 
    public Booking Booking 
    { 
    get { return (Booking)GetValue(BookingProperty); } 
    set { SetValue(BookingProperty, value); } 
    } 

    public ImageTemplate() 
    { 
    InitializeComponent(); 
    } 
} 

Y estoy usando el siguiente código para representar el control:

List<Booking> bookings = Booking.GetSome(); 
    for(int i = 0; i < bookings.Count; i++) 
    { 
    ImageTemplate template = new ImageTemplate(); 
    template.Booking = bookings[i]; 

    RenderTargetBitmap bitmap = new RenderTargetBitmap(
    (int)template.Width, 
    (int)template.Height, 
    120.0, 
    120.0, 
    PixelFormats.Pbgra32); 
    bitmap.Render(template); 

    BitmapEncoder encoder = new PngBitmapEncoder(); 
    encoder.Frames.Add(BitmapFrame.Create(bitmap)); 

    using (Stream s = File.OpenWrite(@"C:\Code\Bleargh\RawImages\" + i.ToString() + ".png")) 
    { 
    encoder.Save(s); 
    } 

    } 

EDITAR:

Debo añadir que el proceso funciona sin ningún tipo de error, pero termino con un directorio lleno de imágenes en blanco, no texto ni nada ... Y he confirmado el uso del depurador que mi objetos de reserva se llenan con los datos adecuados ...

EDIT 2:

hizo algo que debería haber hecho hace mucho tiempo, estableció un fondo en mi lienzo, pero que no cambiaron la imagen de salida en absoluto, así que mi problema definitivamente tiene que ver con mi código de dibujo (aunque también puede haber algún problema con mi enlace de datos)

Respuesta

14

RenderTargetBitmap representa el estado actual de su control. En su caso, su control no se ha inicializado, por lo que aún aparece en blanco.

para obtener el código para inicializar correctamente antes de Render() que hay que hacer tres cosas:

  1. asegurarse de que su control se ha medido y dispuestos.
  2. Si su control utiliza eventos cargados, asegúrese de estar conectado a un PresentationSource.
  3. Asegúrese de que todos los eventos DispatcherPriority.Render y anteriores se hayan completado.

Si hace estas tres cosas, su RenderTargetBitmap saldrá de manera idéntica a la forma en que aparece el control cuando lo agrega a una ventana.

Forzando una Medida/Organizar en su mando a

Esto es tan simple como:

template.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); 
template.Arrange(new Rect(template.DesiredSize)); 

Esta medida las fuerzas de código/organizar. Es más simple pasar double.PositiveInfinity para el ancho y alto porque le permite a su UserControl elegir su propio ancho y alto. Si configura explícitamente el ancho/alto, no importa demasiado, pero de esta forma su UserControl tiene la opción de usar el sistema de diseño de WPF para crecer automáticamente cuando sea necesario si los datos son más grandes de lo esperado. Por la misma razón, es mejor usar template.DesiredSize para Arrange en lugar de pasar en un tamaño específico.

Colocación de un PresentationSource

Esto sólo es necesario si su control o elementos dentro de su control se basan en el evento Loaded.

using(var source = new HwndSource(new HwndSourceParameters()) 
         { RootVisual = template }) 
{ 
    ... 
} 

Cuando se crea HwndSource, se notifica que el árbol visual de la plantilla ha sido "Cargado". El bloque "using" se asegura de que la plantilla esté "Unloaded" al final de la declaración "using" (última llave de cierre). Una alternativa al uso de una declaración() sería utilizar GC.KeepAlive:

GC.KeepAlive(new HwndSource(...) { ... }); 

Lavado de la cola de Dispatcher a DispatcherPriority.Render

Sólo tiene que utilizar Dispatcher.Invoke:

Dispatcher.Invoke(DispatcherPriority.Loaded, new Action(() => {})); 

Esto provoca que se invoque una acción vacía después de que se hayan completado todas las acciones Render y de mayor prioridad. El método Dispatcher.Invoke procesa la cola del despachador hasta que esté vacía hasta el nivel Cargado (que está justo debajo de Renderizar).

La razón por la que esto es necesario es porque muchos componentes de la interfaz de usuario de WPF usan la cola Dispatcher para retrasar el procesamiento hasta que el control esté listo para procesarse. Esto reduce significativamente la recalculación innecesaria de las propiedades visuales durante la vinculación y otras operaciones.

dónde agregar el código

Añadir los tres de estos pasos después de establecer el contexto de datos (template.Booking = ...) y antes de llamar RenderTargetBitmap.Render.

sugerencias adicionales

hay una manera mucho más fácil de hacer su trabajo de la unión. En el código, simplemente configure la reserva como un DataContext. Esto elimina la necesidad de utilizar ElementName y la propiedad de reserva:

foreach(var booking in Booking.GetSome()) 
{ 
    var template = new ImageTemplate { DataContext = booking }; 

    ... code from above ... 
    ... RenderTargetBitmap code ... 
} 

Al utilizar el DataContext, la unión cuadro de texto se simplifica en gran medida:

<UserControl ...> 
    <Canvas> 
    <TextBlock ... Text="{Binding Customer}" /> 
    <TextBlock ... Text="{Binding Location}" /> 
    <TextBlock ... Text="{Binding ItemNumber}" /> 
    <TextBlock ... Text="{Binding Description}" /> 

Si usted tiene una razón en particular para el uso de la reserva DependencyProperty se todavía puede simplificar sus fijaciones mediante el establecimiento de la DataContext a nivel <UserControl> en lugar de utilizar ElementName:

<UserControl ... 
    DataContext="{Binding Booking, RelativeSource={RelativeSource Self}}"> 
    <Canvas> 
    <TextBlock ... Text="{Binding Customer}" /> 

También recomendaría utiliza un StackPanel en lugar de un Canvas para este propósito, y también se debe considerar el uso de un estilo para establecer la fuente, el tamaño del texto y el espacio:

<UserControl ... 
    Width="300" Height="300"> 

    <UserControl.Resources> 
    <Style TargetType="TextBlock"> 
     <Setter Property="FontSize" Value="16" /> 
     <Setter Property="FontFamily" Value="Calibri" /> 
     <Setter Property="Height" Value="25" /> 
     <Setter Property="Margin" Value="50 25 50 0" /> 
    </Style> 
    </UserControl.Resources> 

    <StackPanel> 
    <TextBlock Text="{Binding Customer}" /> 
    <TextBlock Text="{Binding Location}" /> 
    <TextBlock Text="{Binding ItemNumber}" /> 
    <TextBlock Text="{Binding Description}" /> 
    </StackPanel> 
</UserControl> 

en cuenta que toda la disposición se realiza por Diseño de WPF dado el tamaño UserControl y la altura y el margen especificados. También tenga en cuenta que TextBlock solo necesita especificar el texto; todo lo demás lo maneja el estilo.

+0

Gracias por la información. No estoy usando OnLoad en absoluto, pero los otros dos consejos solucionaron mi problema ... Muchas gracias ... Nuevo, algunos de los XAML fueron totalmente a la hora del almuerzo , pero solo estaba tratando de obtenerlo renderizado, arreglaré parte de eso ahora ... Gracias otra vez – LorenVS

+2

Una recompensa bien ganada si alguna vez lo vi. – RandomEngy

+2

No sé. Esta respuesta es un poco corta y no es así. muy claro. Tal vez f lo revisó y lo extendió un poco ... – Will

0

Creo que el problema está en la encuadernación, como sospecha. En lugar de crear una propiedad Booking, intente configurar el DataContext de la instancia ImageTemplate, luego configure la Ruta en los enlaces solo al nombre de propiedad del objeto de datos que desea usar. Es puede no resuelve su problema, pero es una forma más estándar de hacer el enlace.

<TextBlock ... Text="{Binding Path=Customer}" /> 

debería ser todo lo que necesita para hacer el trabajo vinculante si se establece el contexto de datos a una instancia Booking. Pruébalo y avísanos si funciona.

+0

Lo he configurado de esta manera antes, pero todavía no parece funcionar correctamente ... No recuerdo el xaml exacto para devolverlo :(Creo que todavía estoy haciendo algo mal en la sección de dibujo, ya que he establecido el fondo en el control de usuario en negro y aún así obtengo una imagen vacía ... – LorenVS

+0

Debe ser su sección de dibujo, entonces. Lamentablemente, realmente no sé nada sobre la imagen y lo siento. –

2

Bueno, uno de sus problemas es que debe llamar a Medir y organizar en su UserControl antes de intentar renderizar. Poner esto antes de crear el objeto RenderTargetBitmap:

template.Measure(new Size(template.Width, template.Height)); 
template.Arrange(new Rect(new Size(template.Width, template.Height))); 

que por lo menos conseguir el control de usuario para iniciar la representación.

El segundo problema es el enlace de datos. No he sido capaz de descifrarlo; puede haber algo más que deba hacer para evaluar los enlaces. Sin embargo, puede evitarlo: si configura el contenido de TextBlock directamente en lugar de a través de enlace de datos, funciona.

Cuestiones relacionadas