Рендеринг в пользовательский DrawingContext

Я хотел бы использовать обычный рендеринг WPF, чтобы разделить элементы управления на примитивы, сделать управление макетом, применить привязки и т. Д. Для меня.

Насколько я понимаю, весь рендеринг в WPF сводится к рендерингу примитивов (текст, изображение, линия, кривая) в местах, рассчитанных менеджером макета со значениями, определенными системой свойств зависимостей. Если бы я мог предоставить свою собственную логику рендеринга примитивов, я мог бы рендерить, например, на пользовательский тип документа, передавать примитивы для реального рендеринга по сети и т. Д.

Мой план следующий:

  1. Реализовать кастом DrawingContext, DrawingContext абстрактный класс, который определяет кучу методов, таких как DrawEllipse, DrawText, DrawImage и т.д. - мне нужно будет предоставить собственную реализацию для этой функциональности.
  2. Создать WPF UserControl и заставить его сделать в данном DrawingContext,

Однако я столкнулся со следующими проблемами:

  1. DrawingContext содержит абстрактные внутренние методы void PushGuidelineY1(double coordinate) а также void PushGuidelineY2(double leadingCoordinate, double offsetToDrivenCoordinate), который я не могу легко переопределить. (Возможно, есть какой-то трюк, чтобы преодолеть это?)
  2. Кажется, нет способа визуализировать весь визуал на DrawingContext? Зачем?

Я могу сделать что-то вроде

void RenderRecursively(UIElement e, DrawingContext ctx)
{
    e.OnRender(ctx);
    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(e); i++)
        RenderRecursively((UIElement)VisualTreeHelper.GetChild(e, i), ctx);
}

- но мне интересно, есть ли прямой способ сделать UIElement, (Конечно, эта проблема незначительна, но отсутствие инфраструктуры для нее заставляет задуматься, правильно ли это.)

Итак, это DrawingContext не предназначен для наследования от? Это вся идея поставки на заказ DrawingContext шаг в правильном направлении, или мне нужно переосмыслить стратегию? Поддерживается ли рисование в пользовательском контексте в WPF или мне нужно искать другую точку перехвата?

4 ответа

Решение

Возможно, вам придется подойти к этой проблеме с противоположной стороны. Вместо того, чтобы стремиться предоставить свой собственный DrawingContext вместо этого вы можете попросить WPF предоставить вам Drawing, Так что это скорее подход "тяги", чем подход "толчка", к которому вы стремитесь, но он должен позволить добраться до того же места: если у вас есть Drawing это полное представление о внешнем виде части визуального дерева, это структура данных, которую вы можете просматривать и открывать для себя все, что вы открыли, от вызовов до пользовательских DrawingContext,

Я полагаю, что это тот же самый базовый подход, который Себастьян упоминает при экспорте документов XPS внутри страны. Но использовать его непосредственно самостоятельно - более прямой подход, чем использовать его через API-интерфейсы XPS.

В основе чего-то достаточно простого: VisualTreeHelper.GetDrawing, Это возвращает DrawingGroup, (Drawing является абстрактным базовым классом.) На этой странице документации показано, как пройти по дереву, которое вы вернули. К сожалению, это не делает всю работу: оно просто предоставляет визуальные эффекты для любого узла, к которому вы обращаетесь, и если у этого узла есть дочерние элементы, они не будут включены.

Так что, к сожалению, вам все равно придется написать что-то, что повторяет визуальное дерево, так же, как вы уже планировали. Вам также потребуется обрабатывать любые маски непрозрачности, непрозрачность, не основанную на маске, области обрезки, эффекты и преобразования, которые прикреплены к визуалу, чтобы получить правильные результаты; вам бы пришлось сделать все это, чтобы ваш предложенный подход работал правильно, так что здесь ничего не меняется. (Одним из потенциальных преимуществ использования API XPS, как предполагает Себастьян, является то, что он делает все это за вас. Однако в этом случае ваша проблема - извлечь информацию из документа XPS в той форме, в которой вы хотите, и это может привести к потере информации, которую вы может захотеть сохранить.)

Я думаю, что ваш подход не будет работать, потому что (как уже упоминали другие) вы не можете предоставить свой собственный DrawingContext реализация.

Вместо этого я предлагаю следующее: Чтобы "сгладить" рендеринг WPF, пусть WPF экспортирует ваши визуальные эффекты в документ XPS. Во время этого процесса весь рендеринг в основном перечисляется как простые примитивы рендеринга, и все, что у вас осталось, это Canvass, основные фигуры, глифы и другие примитивы рисования.

Затем переберите визуальные элементы на страницах документа. Насколько я знаю, получившийся визуал будет состоять только из примитивов, поэтому нет необходимости вызывать OnRender, Вместо этого это позволяет вам внешне анализировать визуальные экземпляры (используя instanceof-каскады и чтение / интерпретация свойств). Это все еще довольно большая работа, потому что вам нужно интерпретировать свойства так же, как это делает WPF, но, насколько я мог видеть, это должно работать, по крайней мере, для многих основных сценариев использования.

Вместо того, чтобы пытаться написать свой собственный DrawingContext может быть, вы могли бы создать класс, производный от FrameworkElement или же UIElement или даже Visual который выполняет вашу деятельность в своем OnRender метод. Вы все еще должны использовать данные реализации Draw[Something] но вы будете контролировать аргументы и порядок операций. Вы все еще могли бы анализировать примитивы и инструкции из вторичного источника, и ваш UIElement/FrameworkElement мог бы составлять инструкции во время выполнения.

Я пытался сделать аналогичную вещь, чтобы создать FlowDocumentViewer для winRT. Но поскольку WinRT гораздо менее зрелый по сравнению с WPF, он также слишком много делегирует нативному слою (через поток рендеринга), которого я нигде не смог получить. Но это то, что я узнал, и я надеюсь, что я объясню это хорошо.

WPF использует аппаратное ускорение рендеринга графики. Таким образом, в упрощенном виде WPF LayoutEngine создает логическое визуальное дерево, которое затем преобразуется в инструкции рендеринга, которые затем отправляются в графическое оборудование для выполнения или рендеринга.

DrawingContext - это нетривиальный класс, он взаимодействует с базовой графической системой для рендеринга, управляет масштабированием, кэшированием и так далее. WPF runtime поставляется с реализацией по умолчанию, которая выполняет рендеринг всех визуальных элементов. IMO, причина в том, что он превратился в абстрактный класс, чтобы Microsoft могла предоставить различные реализации, скажем, для Silverlight и т. Д. Но мы должны это переопределить.

Если вам необходимо заменить рендеринг WPF, то лучше всего создать UserControl, переопределить вызовы Arrange и Measure и отобразить каждый элемент в DrawingVisual с помощью DrawingVisual.RenderOpen() и упорядочить их и т. Д. Из своего кода. Управление уведомлениями DataBinding будет еще одной вещью, которую вам придется сделать самостоятельно.

Похоже, очень интересный проект. Удачи!

Другие вопросы по тегам