使用DataBinding将WPF UserControl绘制到图像

Lor*_*nVS 6 data-binding wpf image .net-3.5

所以我正在尝试使用WPF用户控件从数据集中生成大量图像,数据集中的每个项目都会生成图像...

我希望我可以设置它以便我可以使用WPF数据绑定,并且对于数据集中的每个项目,创建我的用户控件的实例,设置与我的数据项对应的依赖项属性,然后绘制用户对图像的控制,但我遇到问题,让它全部工作(不确定数据绑定或绘图是否是我的问题)

对于大规模的代码转储感到抱歉,但我一直试图让它工作几个小时,而WPF只是不喜欢我(虽然在某些方面需要学习......)

我的用户控件如下所示:

<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>
Run Code Online (Sandbox Code Playgroud)

我已经向我的用户控件添加了一个类型为"Booking"的依赖项属性,我希望它将成为数据绑定值的来源:

 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();
  }
 }
Run Code Online (Sandbox Code Playgroud)

我正在使用以下代码来呈现控件:

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

   }
Run Code Online (Sandbox Code Playgroud)

编辑:

我应该补充一点,这个过程没有任何错误,但我最终得到一个充满纯白图像的目录,而不是文本或任何东西......我已经确认使用调试器,我的预订对象正在填充正确的数据...

编辑2:

做了我应该做的事很久以前,在我的画布上设置了一个背景,但这根本没有改变输出图像,所以我的问题绝对与我的绘图代码有关(尽管可能有些错误我的数据绑定也是)

Ray*_*rns 15

RenderTargetBitmap呈现控件的当前状态.在您的情况下,您的控件尚未初始化,因此它仍然显示为白色.

要在Render()之前正确初始化代码,您需要做三件事:

  1. 确保您的控制已经过测量和安排.
  2. 如果您的控件使用Loaded事件,请确保您已附加到PresentationSource.
  3. 确保所有DispatcherPriority.Render及以上事件都已完成.

如果您执行这三项操作,则在RenderTargetBitmap将控件添加到窗口时,控件的显示方式与您的操作方式完全相同.

强制控制/安排你的控制

这很简单:

template.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
template.Arrange(new Rect(template.DesiredSize));
Run Code Online (Sandbox Code Playgroud)

此代码强制衡量/安排.最简单的方法是传递double.PositiveInfinity的宽度和高度,因为它允许UserControl选择自己的宽度和高度.如果您明确设置宽度/高度并不重要,但这样您的UserControl可以选择使用WPF的布局系统在必要时自动增长,如果数据大于预期.出于同样的原因,最好使用template.DesiredSize作为Arrange而不是传递特定的大小.

附加PresentationSource

仅当控件或控件中的元素依赖于Loaded事件时,才需要这样做.

using(var source = new HwndSource(new HwndSourceParameters())
                       { RootVisual = template })
{
  ...
}
Run Code Online (Sandbox Code Playgroud)

创建HwndSource时,会通知模板的可视化树已"加载"."using"块确保模板在"using"语句(最后一个大括号)的末尾处为"Unloaded".using()语句的替代方法是使用GC.KeepAlive:

GC.KeepAlive(new HwndSource(...) { ... });
Run Code Online (Sandbox Code Playgroud)

将Dispatcher队列刷新到DispatcherPriority.Render

只需使用Dispatcher.Invoke:

Dispatcher.Invoke(DispatcherPriority.Loaded, new Action(() => {}));
Run Code Online (Sandbox Code Playgroud)

这会导致在完成所有渲染和更高优先级操作后调用空操作.Dispatcher.Invoke方法处理调度程序队列,直到它为空,直到Loaded级别(位于Render下方).

这是必要的原因是许多WPF UI组件使用Dispatcher队列来延迟处理,直到控件准备好呈现为止.这显着减少了在绑定和其他操作期间不必要的视觉特性的重新计算.

在哪里添加此代码

在设置数据上下文(template.Booking = ...)之后和调用之前,添加所有这三个步骤RenderTargetBitmap.Render.

其他建议

有一种更简单的方法来进行绑定工作.在代码中,只需将预订设置为DataContext即可.这消除了使用ElementName和Booking属性的需要:

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

  ... code from above ...
  ... RenderTargetBitmap code ...
}
Run Code Online (Sandbox Code Playgroud)

通过使用DataContext,TextBox绑定大大简化:

<UserControl ...>
  <Canvas>
    <TextBlock ... Text="{Binding Customer}" />
    <TextBlock ... Text="{Binding Location}" />
    <TextBlock ... Text="{Binding ItemNumber}" />
    <TextBlock ... Text="{Binding Description}" />
Run Code Online (Sandbox Code Playgroud)

如果您有使用Booking DependencyProperty的特殊原因,您仍然可以通过在<UserControl>级别设置DataContext 而不是使用ElementName以下内容来简化绑定:

<UserControl ...
  DataContext="{Binding Booking, RelativeSource={RelativeSource Self}}">
  <Canvas>
    <TextBlock ... Text="{Binding Customer}" />
Run Code Online (Sandbox Code Playgroud)

为了这个目的,我还建议您使用a StackPanel而不是a Canvas,并且还应该考虑使用样式来设置字体,文本大小和间距:

<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>
Run Code Online (Sandbox Code Playgroud)

请注意,给定UserControl大小以及指定的高度和边距,所有布局都由WPF的布局完成.另请注意,TextBlock只需要指定Text - 其他所有内容都由样式处理.

  • 如果我看到它,这是一个来之不易的赏金. (2认同)
  • 我不知道.这个答案有点短,不太清楚.也许如果你修改并扩展它... (2认同)