绘制一个WPF用户控件与数据绑定到图像 [英] Drawing a WPF UserControl with DataBinding to an Image

查看:491
本文介绍了绘制一个WPF用户控件与数据绑定到图像的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

所以我想用一个WPF用户控件生成一吨从数据集图像,其中数据集中的每个项目将产生的图像...

So I'm trying to use a WPF User Control to generate a ton of images from a dataset where each item in the dataset would produce an image...

我希望我可以将它设置的,我可以使用WPF数据绑定这样的方式,并在数据集中的每个项目,创建我的用户控件的实例,设置对应于我的数据项的依赖项属性,然后绘制用户控件的图像,但我有问题,得到它的所有工作(不知道是否绑定或绘图的图像是我的问题)

I'm hoping I can set it up in such a way that I can use WPF databinding, and for each item in the dataset, create an instance of my user control, set the dependency property that corresponds to my data item, and then draw the user control to an image, but I'm having problems getting it all working (not sure whether databinding or drawing to the image is my problem)

抱歉了大量的code场,但我一直在试图让这个工作了几个小时,现在,WPF只是不喜欢我(要学会在某些时候,虽然...)

Sorry for the massive code dump, but I've been trying to get this working for a couple of hours now, and WPF just doesn't like me (have to learn at some point though...)

我的用户控制是这样的:

My User Control looks like this:

<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>

和我添加类型为预订的依赖属性,以我的用户控件,我希望将是源数据绑定值:

And I've added a dependency property of type "Booking" to my user control that I'm hoping will be the source for the databound values:

 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();
  }
 }

和我使用的是下面的code呈现控件:

And I'm using the following code to render the 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);
    }

   }

编辑:

我要补充一点,处理工作不受任何错误,但我结束了一个完整的纯白色图像目录,而不是文本或者什么......而且我已经用我的预订对象填充调试确认用正确的数据...

I should add that the process works without any errors whatsoever, but I end up with a directory full of plain-white images, not text or anything... And I have confirmed using the debugger that my Booking objects are being filled with the proper data...

编辑2:

做了一些我应该做的很久以前,我设置画布上的背景,但这并没有改变输出图像在所有的,所以我的问题是最肯定以某种方式与我的绘画code(虽然有可能是坏了我的数据绑定过)

Did something I should have done a long time ago, set a background on my canvas, but that didn't change the output image at all, so my problem is most definitely somehow to do with my drawing code (although there may be something wrong with my databinding too)

推荐答案

RenderTargetBitmap 渲染控件的当前状态。在你的情况你的控制还没有初始化,所以它仍然显示为白色。

RenderTargetBitmap renders the current state of your control. In your case your control has not initialized so it still appears white.

为了让您的code正确初始化之前渲染(),你需要做三件事情:

To get your code to initialize properly before Render() you need to do three things:

  1. 确保您的控件进行了测试,并安排。
  2. 如果您的控制使用加载事件,请确保您连接到presentationSource。
  3. 确保所有DispatcherPriority.Render与上述事件已经完成。

如果你做的这三样东西你的 RenderTargetBitmap 将相同的出来,当你将它添加到窗口控件的显示方式。

If you do these three things your RenderTargetBitmap will come out identically to the way the control appears when you add it to a Window.

强制措施/排列在你的控制

这是简单的:

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

这code力的措施/安排。这是最简单的传递double.PositiveInfinity的宽度和高度,因为它可以让你的用户控件来选择自己的宽度和高度。如果你明确地设置宽度/高度也没多大关系,但这样一来你的用户控件具有使用WPF的布局系统在必要的时候,如果数据比预期更大自动增长的选项。出于同样的原因,最好是使用template.DesiredSize的安排,而不是在传递一个特定大小。

This code forces measure/arrange. It is simplest to pass in double.PositiveInfinity for the width and height because it allows your UserControl to choose its own Width and Height. If you explicitly set the width/height it doesn't matter much, but this way your UserControl has the option of using WPF's layout system to automatically grow when necessary if the data is larger than expected. By the same token it is better to use template.DesiredSize for the Arrange rather than passing in a specific size.

附加一个presentationSource

这是唯一必要的,如果你的控制或控制范围内的要素依赖于加载的事件。

This is only necessary if your control or elements within your control rely on the Loaded event.

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

在HwndSource创建模板的可视化树通知,它已被装。 使用块确保模板卸载,在使用的声明(昨日收大括号)结束。另一种为用()语句是使用GC.KeepAlive:

When the HwndSource is created the visual tree of the template is notified that it has been "Loaded". The "using" block makes sure the template is "Unloaded" at the end of the "using" statement (last closing curly brace). An alternative to a using() statement would be to use GC.KeepAlive:

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

刷新的调度队列下降到DispatcherPriority.Render

只需使用Dispatcher.Invoke:

Just use Dispatcher.Invoke:

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

这导致要调用一个空的动作毕竟渲染和更高的优先行动已经完成。该Dispatcher.Invoke方法处理调度队列中,直到它是空降至加载水平(这是正确的渲染之下)。

This causes an empty action to be invoked after all Render and higher priority actions have completed. The Dispatcher.Invoke method processes the dispatcher queue until it is empty down to Loaded level (which is right below Render).

这之所以是必要的是,许多WPF UI组件使用调度队列延迟处理,直到控制是准备呈现。这显著减少了不必要的重新计算可视化属性的过程中结合等操作。

The reason this is necessary is that many WPF UI components use the Dispatcher queue to delay processing until the control is ready to render. This significantly cuts down on unnecessary re-computation of visual properties during binding and other operations.

在哪里可以添加此code

将所有这三个步骤后,您将您的数据上下文( template.Booking = ... ),并在打电话之前 RenderTargetBitmap.Render

Add all three of these steps after you set your data context (template.Booking = ...) and before you call RenderTargetBitmap.Render.

其他建议

还有一个更简单的方法,使你的绑定工作。在code,刚刚成立的预订作为一个DataContext。这样,就不再需要使用的ElementName和预订属性:

There is a much easier way to make your binding work. In code, just set the booking as a DataContext. This removes the need to use ElementName and the Booking property:

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

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

通过使用在DataContext,文本框绑定大大简化:

By using the DataContext, the TextBox binding is greatly simplified:

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

如果你有使用预定的DependencyProperty,你仍然可以通过设置的DataContext在&LT简化您的绑定一个特别的原因;用户控件&GT; 水平,而不是使用的ElementName

If you have a particular reason for using the Booking DependencyProperty you can still simplify your bindings by setting the DataContext at the <UserControl> level rather than using ElementName:

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

我也建议你使用的的StackPanel 代替画布为了这个目的,你也应该考虑使用样式设置字体,文字大小和间距:

I would also recommend you use a StackPanel instead of a Canvas for this purpose, and you should also consider using a style to set the font, text size and spacing:

<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>

请注意,所有的布局是由给定的用户控件的大小和指定的高度和余量WPF的布局进行。另外请注意,文本块只需要指定文本 - 一切由风格处理

Note that all the layout is done by WPF's layout given the UserControl size and the specified height and margin. Also note that the TextBlock only needs to specify the Text -- everything else is handled by the style.

这篇关于绘制一个WPF用户控件与数据绑定到图像的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆