2010-07-07 11 views
7

En una interfaz de usuario WPF tengo nodos conectados por caminos de Bezier, así:actualización WPF PathGeometry es _SLOW_

It might be... atomic http://nv3wrg.blu.livefilestore.com/y1pIGBd33lCC6lF-9H0MqgnL40BdNEoEemZDENzgpEI1IL2j4B-qb3qS3WlxMSys28IjqNngR7mdfvQBnPzerf4cFJQj9VqHBh4/acurve.png?psid=1

Cuando el usuario arrastra un nodo alrededor de, las vías de conexión necesitan ser actualizados en real hora. Sin embargo, he notado cierta desaceleración (especialmente si un nodo está conectado a muchos otros, o múltiples nodos se están arrastrando a la vez). Me perfilado, y el principal problema parece ser aquí:

Proof I actually used a profiler, so please don't be all like "OMG, premature opiumzation; you are DEMON!!" http://nv3wrg.blu.livefilestore.com/y1pjRfQYuN57yei5qdUxW4Dlh4vVCzPy8TcfEzlw_8cUicfOR6BwHCTntcQbQUspRAgBdKcItC0ZcEJbIWMKaYrCtDMOtCBKB4g/profile.png?psid=1

Esta es la función que se llama cada vez que se cambia la propiedad ya sea de origen o destino. La geometría que compone la ruta parece regenerarse internamente cada vez que cambia alguno de los puntos de control. ¿Tal vez si hubiera una manera de evitar que la geometría se regenere hasta después de que se hayan establecido todas las propiedades de dependencia relevantes?

EDIT: solución Mart utilizar StreamGeometry aceleró hacia arriba de manera exponencial; la función no está cerca de un cuello de botella. Un pequeño Reflecting sugiere que PathGeometry usa StreamGeometry internamente, y cada vez que se cambia cualquiera de las propiedades de dependencia, se vuelve a calcular StreamGeometry. Así que de esta manera se corta al intermediario. El resultado final es:

private void onRouteChanged() 
{ 
    Point src = Source; 
    Point dst = Destination; 
    if (!src.X.isValid() || !src.Y.isValid() || !dst.X.isValid() || !dst.Y.isValid()) 
    { 
     _shouldDraw = false; 
     return; 
    } 

    /* 
     * The control points are all laid out along midpoint lines, something like this: 
     * 
     * -------------------------------- 
     * |   |   |   | 
     * | SRC | CP1 |   | 
     * |   |   |   | 
     * -------------------------------- 
     * |   |   |   | 
     * |   | MID |   | 
     * |   |   |   | 
     * ------------------------------- 
     * |   |   |   | 
     * |   | CP2 | DST | 
     * |   |   |   | 
     * -------------------------------- 
     * 
     * This causes it to be horizontal at the endpoints and vertical 
     * at the midpoint. 
     */ 

    double mx = (src.X + dst.X)/2; 
    double my = (src.Y + dst.Y)/2; 
    Point mid = new Point(mx, my); 
    Point cp1 = new Point(mx, src.Y); 
    Point cp2 = new Point(mx, dst.Y); 

    _geometry.Clear(); 
    _shouldDraw = true; 
    using(StreamGeometryContext ctx = _geometry.Open()) 
    { 
     ctx.BeginFigure(src, false, false); 
     ctx.QuadraticBezierTo(cp1, mid, true, false); 
     ctx.QuadraticBezierTo(cp2, dst, true, false); 
    } 
} 

El código fuente completo del proyecto está disponible en http://zeal.codeplex.com para los curiosos.

Respuesta

7

1- Me gustaría tratar de utilizar StreamGeometry:

 StreamGeometry streamGeo = new StreamGeometry(); 
     Stopwatch sw = new Stopwatch(); 
     sw.Start(); 
     for (int i = 0; i < 10000; i++) 
     { 
      streamGeo.Clear(); 
      var ctx = streamGeo.Open(); 
      ctx.BeginFigure(new Point(0, 0), false, false); 
      ctx.QuadraticBezierTo(new Point(10, 10), new Point(10, i), true, true); 
      ctx.Close(); 
     } 
     sw.Stop(); 
     Console.WriteLine(sw.ElapsedMilliseconds); // For 10k it took 30 ms 

Se ve mucho más rápido que PathGeometry + PathFigure.

Cuando establece el Punto para el QuadraticBezierSegment, recalcula todo. Es por eso que es lento. Y más lento cuando ya está agregado a una geometría.

2- Trate de usar solo 1 marco de trabajo para todas sus curvas. Mira esto: Writing More Efficient ItemsControls

+0

Gracias; ¡cambiar a StreamGeometry pareció resolver el problema! –

0

Si no necesita pruebas de impacto, menús contextuales, información sobre herramientas para sus curvas, puede utilizar elementos visuales simples en lugar de elementos de marco.

+0

Gracias! Pero está en un lienzo, por lo que al menos debe ser un UIElement (para estar en un panel). Y dado que los cambios en la propiedad invalidan el procesamiento, la forma más fácil de hacerlo es a través de FrameworkPropertyMetadataOptions.AffectsRender, que requiere un FraworkElement. De todos modos, ¿cómo podría ayudar esto a resolver el problema anterior? –

0

Me imagino que sus problemas de rendimiento vienen de descender de FrameworkElement y tener el motor de renderizado de WPF vuelve a calcular el diseño como la curva se calcula.

Lo que puede considerar es modelar la curva descendiendo desde Freezable y luego usar FrameworkElement (como PathGeometry) para mostrar la geometría real.

+0

El diseño solo se vuelve a calcular cuando cambia el origen o el destino, pero si el usuario está arrastrando un nodo, esto puede ser cada vez que el mouse se mueve por el número de rutas afectadas. La ruta no está congelada, por lo que no puede descender de la congelación. –

+1

Freezable no significa que no puede cambiarlo, simplemente no puede cambiarlo después de que se congela. Me imagino que es posible copiar, mutar la curva, congelar la geometría y actualizar el FrameworkElement que lo contiene mucho más rápido que si hiciera que FrameworkElement lo hiciera todo. En última instancia, creo que no habrá forma de obtener un rendimiento aceptable utilizando FrameworkElement como base. – codekaizen

Cuestiones relacionadas