Рендеринг в пользовательский DrawingContext
Я хотел бы захватить обычный рендеринг WPF, чтобы разделить элементы управления на примитивы, сделать управление макетами, применить привязки и т.д. для меня.
Насколько я понимаю, весь рендеринг в WPF сводится к рендерингу примитивов (текст, изображение, линия, кривая) в местах, рассчитанных менеджером компоновки со значениями, определенными системой свойств зависимостей. Если бы я мог предоставить свою логику примитивного рендеринга, я бы смог отобразить, например. к типу пользовательского документа, переносите примитивы для реального рендеринга по сети и т.д.
Мой план выглядит следующим образом:
- Внедрение пользовательского
DrawingContext
. DrawingContext
является абстрактным классом, который определяет кучу таких методов, как DrawEllipse
, DrawText
, DrawImage
и т.д. - мне нужно будет предоставить собственную реализацию для этой функции.
- Создайте WPF
UserControl
и заставьте его визуализировать в заданный DrawingContext
.
Однако я столкнулся со следующими проблемами:
-
DrawingContext
содержит абстрактные внутренние методы void PushGuidelineY1(double coordinate)
и void PushGuidelineY2(double leadingCoordinate, double offsetToDrivenCoordinate)
, которые я не могу легко переопределить. (Возможно, есть какой-то трюк, чтобы преодолеть это?)
- Кажется, нет способа визуализации всего изображения на
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, или мне нужно искать другую точку перехвата?
Ответы
Ответ 1
Возможно, вам придется обратиться к этой проблеме с противоположного направления. Вместо того чтобы стремиться предоставить свой собственный DrawingContext
, вы можете вместо этого попросить WPF предоставить вам Drawing
. Таким образом, это более "подтягивающий" подход, чем подход "push", к которому вы стремитесь, но он должен позволять добраться до того же места: если у вас есть Drawing
, что является полным представлением о внешности части визуального дерева, что структура данных вы можете пройти и обнаружить все, что вы обнаружили бы из вызовов пользовательского DrawingContext
.
Я считаю, что это тот же базовый подход, что и документ XPS, который упоминает Себастьян, использует внутренне. Но использование его непосредственно непосредственно - это более прямой подход, чем использование его через API XPS.
В основе лежит нечто довольно простое: VisualTreeHelper.GetDrawing
. Это возвращает a DrawingGroup
. (Drawing
- абстрактный базовый класс.) Эта страница документации показывает вам, как пройти через дерево, которое вы вернетесь. К сожалению, это не делает всю работу: она просто предоставляет визуальные эффекты для любого node, с которым вы его вызываете, и если у этого node есть дети, они не будут включены.
Таким образом, вам, к сожалению, все равно придется писать что-то, что повторяет визуальное дерево, как вы уже планировали. И вам также нужно будет обрабатывать любые маски непрозрачности, непрозрачность на маской, области клипов, эффекты и преобразования, которые привязаны к визуальному, чтобы получить правильные результаты; вам пришлось бы все это сделать, чтобы ваш предлагаемый подход работал правильно, поэтому здесь ничего не меняется. (Одно из потенциальных преимуществ использования XPS API, как предлагает Себастьян, состоит в том, что он делает все это для вас. Однако тогда ваша проблема заключается в извлечении информации из документа XPS в желаемой форме, и это может привести к потере информации, которую вы возможно, захочет сохранить.)
Ответ 2
Я думаю, что ваш подход не сработает, потому что (как говорили другие) вы не можете предоставить свою собственную реализацию DrawingContext
.
Я предлагаю следующее: Чтобы "сгладить" рендеринг WPF, WPF экспортирует ваши визуальные изображения в документ XPS. Во время этого процесса весь рендеринг в основном перечисляется как простые примитивы рендеринга, и все, что вам осталось, это Canvas
s, основные фигуры, глифы и другие примитивы рисования.
Затем перебираем визуальные изображения на страницах документа. Насколько я знаю, полученный визуальный будет состоять только из примитивов, поэтому нет необходимости называть OnRender
. Вместо этого вы можете внешне визуализировать визуальные экземпляры (используя instanceof
-каскады и чтение/интерпретацию свойств). Это еще очень много работы, потому что вам нужно интерпретировать свойства так же, как это делает WPF, но, насколько я мог видеть, это должно работать, по крайней мере, для многих основных случаев использования.
Ответ 3
Я попытался сделать подобное, чтобы создать FlowDocumentViewer для winRT. Но поскольку WinRT гораздо менее зрелый по сравнению с WPF, он также слишком много передает на собственный слой (через поток рендеринга), которого я не мог получить нигде. Но это то, чему я научился, и надеюсь, что хорошо объясню это.
WPF использует аппаратную ускоренную графическую визуализацию. Таким образом, в упрощенном виде WPF LayoutEngine создает логическое дерево визуализации, которое затем преобразуется в команды рендеринга, которые затем отправляются на аппаратное обеспечение Graphics для выполнения или рендеринга.
DrawingContext - это нетривиальный класс, он взаимодействует с базовой графической системой для рендеринга, управляет масштабированием, кэшированием и т.д. Время выполнения WPF поставляется с реализацией по умолчанию, которая выполняет визуализацию всех визуальных эффектов. IMO, причина, по которой она превратилась в абстрактный класс, поэтому Microsoft может предоставить различные реализации для Silverlight и т.д. Но это означало, что мы переопределены нами.
Если вы должны заменить рендеринг WPF, лучше всего создать UserControl, переопределить Arrange и Measure calls и визуализировать каждый элемент DrawingVisual с помощью DrawingVisual.RenderOpen() и упорядочить их и т.д. из вашего кода. Управление уведомлениями о передаче данных будет еще одной вещью, которую вам придется сделать самостоятельно.
Кажется, очень интересный проект. Удачи!
Ответ 4
Вместо того, чтобы пытаться написать собственный DrawingContext
, возможно, вы можете создать класс, который происходит от FrameworkElement
или UIElement
или даже Visual
, который выполняет ваши действия в своем методе OnRender
. Вы все равно должны использовать данные реализации Draw[Something]
, но вы будете контролировать аргументы и порядок операций. Вы все еще можете анализировать примитивы и инструкции из вторичного источника, а ваш UIElement/FrameworkElement может составлять инструкции во время выполнения.