2013-08-12 6 views
16

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

Насколько я понимаю, весь рендеринг в 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, или мне нужно искать другую точку перехвата?

+6

Из примечаний к DrawingContext: 'Вы никогда не создаете экземпляр DrawingContext; вы можете, однако, получить контекст рисования из определенных методов, таких как DrawingGroup.Open и DrawingVisual.RenderOpen.' Для меня это означает, что нет способа действительно * снабжать * пользовательским DrawingContext где-то. – Clemens

+0

@ Clemens: да, я видел это замечание, но я понял это как «вы не должны создавать его сами, мы делаем это для вас внутренне, а для рисования в DrawingVisual просто позвольте DrawingVisual правильно инициализировать его». Во всяком случае, интересно, есть ли допустимые точки перехвата для рисования. – Vlad

+2

Единственный способ рисовать в DrawingVisual - это рисовать в DrawingContext, предоставляемый DrawingVisual.RenderOpen. Нет простого способа связать свой собственный DrawingContext с Visual. Идея бессмысленна. – Clemens

ответ

3

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

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

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

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

+0

В моих тестах это не сработало. Я думаю, вам нужно использовать подход, который я предложил, потому что 'GetDrawing' будет давать' null' для большинства 'Control', потому что визуальные эффекты, которые рисуются во время OnRender, не перечислены этим вспомогательным методом - попробуйте перечислить 'Border' и вы получите 'null'. – Sebastian

+0

Если я вызываю 'GetDrawing' на' Border', который имеет непустую 'BorderBrush' и ненулевое' BorderThickness', я возвращаю ненулевую «DrawingGroup», которая содержит один «GeometryDrawing» с тем же размер как 'Border',' RectangleGeometry' как его 'Geometry' и' Pen', который соответствует настройкам 'Thickness' и' BorderBrush'. Вы случайно сделали свои тесты на: а) границе, которая на самом деле ничего не нарисовала, или б) границе, которая еще не реализовала свои визуальные эффекты? –

+0

Возможно, вы прямо здесь - Граница никогда раньше не была визуализирована. Вероятно, это проблема, хотя я еще не проверял. Извините за ложное обвинение! – Sebastian

2

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

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

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

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

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

+0

На самом деле, существует много внутренних 'DrawingContext', используемых для простых вещей, таких как тестирование на удар. Моя конечная цель - превратить WPF в некоторый тип документа, поэтому пользовательский UserControl не поможет. В любом случае, спасибо за ваш совет! – Vlad

4

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

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

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

+0

Подход с документом XPS кажется хорошим способом. Быстрая проверка содержимого документа XPS показала, что исходные шрифты не сохраняются, а скорее встроены в документ - это, однако, не должно быть проблемой, так как можно использовать встроенный шрифт. Возможная проблема с этим подходом заключается в том, что с WPF я могу контролировать сложность, но для XPS мне может понадобиться проанализировать все функции XPS даже для простого источника WPF. – Vlad

+0

Большое спасибо за ваш ответ. Хотя я и не решил идти так, как вы предлагаете, это, безусловно, очень хорошая идея и заслуживает гораздо большего внимания. – Vlad

1

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

+0

Это будет работать, но только для пользовательских Visual. Моя проблема состоит в том, чтобы поймать то, что, например, TextBlock's OnRender делает. – Vlad

+0

Это интересная проблема. Подумайте о том, чтобы поделиться своим опытом, если найдете ответ. :-) –