2010-07-07 9 views
7

In WPF UI Ho nodi connessi da percorsi bezier, in questo modo:aggiornamento WPF PathGeometry è _SLOW_

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

Quando l'utente trascina un nodo attorno, i percorsi di collegamento devono essere aggiornati in reale tempo. Tuttavia, ho notato un rallentamento (specialmente se un nodo è collegato a molti altri, o più nodi vengono trascinati contemporaneamente). Ho fatto il profilo, e il problema principale sembra essere qui:

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

Questa è la funzione che viene chiamata ogni volta che sia la proprietà di origine o di destinazione è cambiato. La geometria che costituisce il percorso sembra essere rigenerata internamente ogni volta che cambia uno dei punti di controllo. Forse se ci fosse un modo per impedire la rigenerazione della geometria fino a quando tutte le proprietà di dipendenza rilevanti sono state impostate?

EDIT: soluzione del Mart di utilizzare StreamGeometry accelerato in su in modo esponenziale; la funzione è lontana da un collo di bottiglia. Un po 'di riflessione suggerisce che PathGeometry utilizza StreamGeometry internamente e ogni volta che viene modificata una delle proprietà di dipendenza, viene ricalcolato StreamGeometry. Quindi in questo modo si elimina l'intermediario. Il risultato finale è:

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); 
    } 
} 

Il codice sorgente completo del progetto è disponibile presso http://zeal.codeplex.com per i curiosi.

risposta

7

1- Vorrei provare a utilizzare 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 

Sembra molto più veloce di PathGeometry + PathFigure.

Quando si imposta il Punto per il Quadrato QuadraticBezierSegment, viene ricalcolato tutto. Ecco perché è lento. E più lento quando è già aggiunto a una geometria.

2- Provare a utilizzare solo 1 framework per tutte le curve. Verificare: Writing More Efficient ItemsControls

+0

Grazie; passare a StreamGeometry sembrava risolvere il problema! –

0

Se non è necessario eseguire test di impatto, menu contestuali, suggerimenti per le curve, è possibile utilizzare elementi visivi semplici anziché elementi di framework.

+0

Grazie! Ma è su una tela, quindi almeno deve essere un UIElement (per essere su un pannello). E poiché le modifiche alle proprietà invalidano il rendering, il modo più semplice per farlo è tramite FrameworkPropertyMetadataOptions.AffectsRender, che richiede un FraworkElement. Ad ogni modo, come potrebbe aiutare a risolvere il problema sopra riportato? –

0

Immagino che i problemi di prestazioni derivino dalla discesa dallo FrameworkElement e che il motore di layout WPF ricalcoli il layout mentre viene calcolata la curva.

Ciò che si può considerare è modellare la curva discendendo da Freezable e quindi utilizzare uno FrameworkElement (come PathGeometry) per visualizzare la geometria effettiva.

+0

Il layout viene effettivamente ricalcolato solo quando cambia la sorgente o la destinazione, ma se l'utente sta trascinando un nodo, ciò può accadere ogni volta che il mouse si sposta per il numero di percorsi interessati. Il percorso non è congelato, quindi non può discendere da freezable. –

+1

Freezable non significa che non puoi cambiarlo: non puoi cambiarlo dopo che è stato congelato. Immagino che sia possibile copiare, mutare la curva, congelare la geometria e aggiornare FrameworkElement che lo contiene molto più rapidamente rispetto a quando fai un FrameworkElement a fare tutto. In definitiva, penso che non ci sarà modo di ottenere prestazioni accettabili dall'utilizzo di FrameworkElement come base. – codekaizen