Рендеринг в пользовательский 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 может составлять инструкции во время выполнения.