c# - Rendering en un DrawingContext personalizado
wpf (4)
Me gustaría secuestrar la representación WPF habitual para dividir los controles en primitivos, hacer la gestión del diseño, aplicar los enlaces, etc.
Según tengo entendido, toda la representación en WPF se reduce a la representación de primitivas (texto, imagen, línea, curva) en las ubicaciones calculadas por el administrador de diseño con los valores definidos por el sistema de propiedades de dependencia. Si pudiera proporcionar mi propia lógica de representación de primitivas, podría procesar, por ejemplo, a un tipo de documento personalizado, transferir las primitivas para la representación real a través de la red, etc.
Mi plan es el siguiente:
- Implementar un
DrawingContext
personalizado.DrawingContext
es una clase abstracta, que define un montón de métodos comoDrawEllipse
,DrawText
,DrawImage
, etc. - Necesitaré proporcionar mi propia implementación para esta funcionalidad. - Cree un
UserControl
WPF y haga que se convierta en unDrawingContext
dado.
Sin embargo he encontrado los siguientes problemas:
-
DrawingContext
contiene métodos internos abstractos,void PushGuidelineY1(double coordinate)
yvoid PushGuidelineY2(double leadingCoordinate, double offsetToDrivenCoordinate)
, que no puedo anular fácilmente. (Tal vez hay algún truco para superar esto?) - Parece que no hay ningún método para representar todo el visual en un
DrawingContext
? ¿Por qué?
Puedo hacer algo como
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);
}
- pero me pregunto si hay una forma directa de representar un UIElement
. (Por supuesto, este problema es menor, pero al no tener infraestructura, me pregunto si esta es la forma correcta).
Entonces, ¿ DrawingContext
no está diseñado para ser heredado? ¿Es la idea general de proporcionar un DrawingContext
personalizado un paso en la dirección correcta, o necesito repensar la estrategia? ¿Se soporta dibujar en un contexto personalizado en WPF o necesito buscar un punto de intercepción diferente?
Creo que su enfoque no funcionará porque (como han mencionado otros) no puede proporcionar su propia implementación de DrawingContext
.
En su lugar, sugiero lo siguiente: Para "aplanar" la representación de WPF, haga que WPF exporte sus imágenes a un documento XPS. Durante ese proceso, todas las representaciones se enumeran básicamente como simples primitivas de representación y todo lo que queda es el Canvas
, las formas básicas, los glifos y otras primitivas de dibujo.
Luego itere sobre las imágenes en las páginas del documento. Por lo que sé, el resultado visual solo consistirá en primitivos, por lo que no es necesario llamar a OnRender
. En su lugar, esto le permite realizar una introspección externa de las instancias visuales (utilizando instanceof
-cascades y leyendo / interpretando las propiedades). Todavía es bastante trabajo, porque necesita interpretar las propiedades como lo hace WPF, pero por lo que puedo ver, esto debería funcionar al menos para los muchos casos de uso principales.
En lugar de intentar escribir su propio DrawingContext
, puede crear una clase que se derive de FrameworkElement
o UIElement
o incluso Visual
que realice sus actividades en su método OnRender
. Aún tiene que usar las implementaciones dadas de Draw[Something]
pero tendrá el control de los argumentos y el orden de las operaciones. Aún podría analizar primitivas e instrucciones de una fuente secundaria y su único UIElement / FrameworkElement podría componer las instrucciones en tiempo de ejecución.
Es posible que deba abordar este problema desde la dirección opuesta. En lugar de tratar de proporcionar su propio DrawingContext
, puede pedirle a WPF que le proporcione un Drawing
. Por lo tanto, es más un enfoque de "tirón" que el de "empujar" al que está apuntando, pero debería permitirle llegar al mismo lugar: si tiene un Drawing
que es una representación completa de la apariencia de la pieza del árbol visual, es una estructura de datos que puedes recorrer y descubrir todo lo que habrías descubierto desde las llamadas a un DrawingContext
personalizado.
Creo que este es el mismo enfoque básico que el documento de exportación a XPS que menciona Sebastian utiliza internamente. Pero usarlo directamente usted mismo es un enfoque más directo que usarlo a través de las API de XPS
En el corazón hay algo bastante simple: VisualTreeHelper.GetDrawing
. Esto devuelve un DrawingGroup
. (El Drawing
es una clase base abstracta). Esa página de documentación le muestra cómo caminar a través del árbol que recupera. Desafortunadamente, esto no hace todo el trabajo: solo proporciona los elementos visuales para cualquier nodo que lo llame, y si ese nodo tiene hijos, no se incluirán.
Así que, desafortunadamente, todavía tendrás que escribir algo que recurra al árbol visual, como si ya estuvieras planeando. Y también deberá manejar cualquier máscara de opacidad, opacidad no basada en máscara, regiones de clip, efectos y transformaciones que se adjuntan a lo visual para obtener los resultados correctos; habría tenido que hacer todo eso también para que su enfoque propuesto funcione correctamente, por lo que nada cambia realmente aquí. (Una ventaja potencial de usar la API de XPS como sugiere Sebastian) es que hace todo esto por usted. Sin embargo, es su problema extraer la información del documento XPS en el formulario que desea, y eso puede terminar perdiendo la información que usted desea. podría querer preservar.)
Intenté hacer algo similar para crear un FlowDocumentViewer para winRT. Pero como WinRT ha madurado mucho menos en comparación con WPF, también se delega demasiado a la capa nativa (a través del subproceso de procesamiento) que no pude llegar a ninguna parte. Pero esto es lo que he aprendido y espero que lo explique bien.
WPF utiliza la representación gráfica acelerada por hardware. Entonces, en términos simplistas, el LayoutEngine de WPF construye un árbol visual lógico que luego se traduce en instrucciones de representación que luego se envían al hardware de Gráficos para ejecutar o procesar.
DrawingContext es una clase no trivial, interactúa con el sistema de gráficos subyacente para la representación, gestiona el escalado, el almacenamiento en caché, etc. El tiempo de ejecución de WPF viene con una implementación predeterminada que realiza la representación de todos los elementos visuales. OMI, la razón por la que se convirtió en una clase abstracta para que Microsoft pueda proporcionar diferentes implementaciones, por ejemplo, para Silverlight, etc. Pero estamos destinados a ser anulados por nosotros.
Si debe reemplazar la representación WPF, entonces su mejor apuesta es crear un UserControl, anular las llamadas Organizar y Medir y renderizar cada elemento a DrawingVisual usando DrawingVisual.RenderOpen () y ordenarlas, etc. a partir de su código. Administrar las notificaciones de DataBinding será otra cosa que tendrá que hacer usted mismo.
Parece un proyecto muy interesante. ¡Buena suerte!