RenderTargetBitmap
отображает текущее состояние вашего контроля. В вашем случае ваш элемент управления не инициализирован, поэтому он по-прежнему выглядит белым.
Чтобы ваш код правильно инициализировался перед Render (), вам нужно сделать три вещи:
- Убедитесь, что ваш контроль был измерен и организован.
- Если ваш элемент управления использует загруженные события, убедитесь, что вы подключены к PresentationSource.
- Убедитесь, что все события 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, а не передавать его в определенном размере.
Присоединение источника презентаций
Это необходимо, только если ваш элемент управления или элементы внутри него зависят от события Loaded.
using(var source = new HwndSource(new HwndSourceParameters())
{ RootVisual = template })
{
...
}
Когда HwndSource создается, визуальное дерево шаблона уведомляется о том, что оно «загружено». Блок «using» гарантирует, что шаблон «Unloaded» находится в конце инструкции «using» (последняя закрывающая фигурная скобка). Альтернативой использованию using () может быть использование GC.KeepAlive:
GC.KeepAlive(new HwndSource(...) { ... });
Сброс очереди Диспетчера до 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 на уровне <UserControl>
вместо использования 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 нужно только указать текст - все остальное обрабатывается стилем.