Рисование WPF UserControl с DataBinding к изображению

Таким образом, я пытаюсь использовать Пользовательский элемент управления 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>

И я добавил свойство зависимости типа "Заказ" к моему пользовательскому элементу управления, что я надеюсь, будет источник для связанных с данными значений:

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

И я использую следующий код для рендеринга управления:

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

   }

Править:

Я должен добавить, что процесс работает без любых ошибок вообще, но я заканчиваю с каталогом, полным просто-белых изображений, не текста или чего-либо... И я подтвердил использование отладчика, что мои объекты Заказа заполнены надлежащими данными...

РЕДАКТИРОВАНИЕ 2:

Сделал что-то, что я должен был сделать давным-давно, установить фон на своем холсте, но это не изменило выходное изображение вообще, таким образом, моя проблема совершенно определенно так или иначе относится к моему коду для прорисовки (хотя может быть что-то не так с моей привязкой данных также),

6
задан Benny Jobigan 1 April 2010 в 00:10
поделиться

3 ответа

RenderTargetBitmap отображает текущее состояние вашего элемента управления. В вашем случае ваш элемент управления не инициализирован, поэтому он все еще выглядит белым.

Чтобы ваш код правильно инициализировался перед Render (), вам нужно сделать три вещи:

  1. Убедитесь, что ваш элемент управления измерен и упорядочен.
  2. Если ваш элемент управления использует события Loaded, убедитесь, что вы подключены к PresentationSource.
  3. Убедитесь, что все события DispatcherPriority.Render и выше завершены.

Если вы выполните эти три действия, ваш RenderTargetBitmap будет выглядеть идентично тому, как элемент управления отображается, когда вы добавляете его в окно.

Принудительное измерение / упорядочение на вашем элементе управления

Это очень просто:

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

Этот код принудительно устанавливает размер / упорядочивание. Проще всего передать double.PositiveInfinity для ширины и высоты, потому что это позволяет вашему UserControl выбирать собственные ширину и высоту. Если вы явно устанавливаете ширину / высоту, это не имеет большого значения, но таким образом ваш UserControl имеет возможность использовать систему макета WPF для автоматического увеличения при необходимости, если данные больше, чем ожидалось. Точно так же лучше использовать template.DesiredSize для Arrange, а не передавать определенный размер.

Присоединение PresentationSource

Это необходимо только в том случае, если ваш элемент управления или элементы в нем полагаются на событие Loaded.

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

Когда HwndSource создается, визуальное дерево шаблона уведомляется о том, что он «загружен». Блок «using» гарантирует, что шаблон «выгружен» в конце оператора «using» (последняя закрывающая фигурная скобка).Альтернативой оператору using () было бы использование GC.KeepAlive:

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

Очистка очереди Dispatcher до DispatcherPriority.Render

Просто используйте Dispatcher.Invoke:

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

Это все-таки вызывает пустое действие. Рендеринг и действия с более высоким приоритетом завершены. Метод Dispatcher.Invoke обрабатывает очередь диспетчера до тех пор, пока она не опустеет до уровня Loaded (который находится прямо под Render).

Причина, по которой это необходимо, заключается в том, что многие компоненты пользовательского интерфейса WPF используют очередь 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 ...
}

Используя DataContext, привязка TextBox значительно упрощается:

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

Если у вас есть особая причина для использования Booking DependencyProperty, вы все равно можете упростить свои привязки, установив параметр DataContext на уровне вместо использования ElementName :

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

Я также рекомендую использовать StackPanel вместо 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>

Обратите внимание, что весь макет выполняется макетом WPF с учетом размера UserControl, указанной высоты и поля. Также обратите внимание, что TextBlock должен указывать только текст - все остальное обрабатывается стилем.

15
ответ дан 8 December 2019 в 13:45
поделиться

Думаю, проблема в привязке, как вы и подозреваете. Вместо создания свойства Booking попробуйте установить DataContext экземпляра ImageTemplate , затем установите Path в привязках только на имя свойства объекта данных. вы хотите использовать. Это может не решить вашу проблему, но это более стандартный способ привязки.

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

Должно быть все, что вам нужно для работы привязки, если вы установите контекст данных на экземпляр Booking . Попробуйте и сообщите нам, работает ли оно.

0
ответ дан 8 December 2019 в 13:45
поделиться

Ну, одна из ваших проблем в том, что вам нужно вызвать Measure и Arrange на вашем UserControl перед попыткой рендеринга. Поместите это перед созданием объекта RenderTargetBitmap:

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

Это по крайней мере заставит ваш UserControl начать рендеринг.

Вторая проблема - привязка данных. Я не смог ее решить; возможно, есть что-то дополнительное, что нужно сделать, чтобы привязки оценивались. Однако вы можете обойти эту проблему: Если вы зададите содержимое TextBlock напрямую, а не через привязку данных, это сработает.

2
ответ дан 8 December 2019 в 13:45
поделиться
Другие вопросы по тегам:

Похожие вопросы: