2010-07-07 4 views
7

В WPF UI У меня есть узлы, соединенные Безье путями, например, так:WPF PathGeometry обновление _SLOW_

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

Когда пользователь перетаскивает узел вокруг, соединительные дорожки должны быть обновлены в реальном масштабе время. Тем не менее, я заметил некоторое замедление (особенно если один узел подключен ко многим другим, или сразу перетаскиваются сразу несколько узлов). Я профилированный, и главная проблема, по-видимому, здесь:

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

Это функция, которая вызывается каждый раз, либо свойство источника или назначения изменяется. Кажется, что геометрия, составляющая путь, восстанавливается внутренне каждый раз, когда изменяется какая-либо из контрольных точек. Возможно, если бы существовал способ предотвратить регенерацию геометрии до тех пор, пока не будут установлены все соответствующие свойства зависимостей?

EDIT: решение Mart, чтобы использовать StreamGeometry ускорили его в геометрической прогрессии; функция нигде не приближается к узкому месту. Небольшое Reflecting предполагает, что PathGeometry использует StreamGeometry внутренне, и каждый раз, когда изменяются какие-либо свойства зависимостей, StreamGeometry пересчитывается. Таким образом, этот способ просто отсекает посредника. Окончательный результат:

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

Полный исходный код проекта доступен на http://zeal.codeplex.com для любопытных.

ответ

7

1- Я хотел бы попробовать использовать 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 

Он выглядит намного быстрее, чем PathGeometry + PathFigure.

Когда вы устанавливаете точку для QuadraticBezierSegment, она пересчитывает все. Вот почему он медленный. И еще медленнее, когда он уже добавлен в геометрию.

2- Попробуйте использовать только 1 элемент для всех ваших кривых. Проверьте это: Writing More Efficient ItemsControls

+0

Thanks; переключение на StreamGeometry, похоже, решило проблему! –

0

Если вам не нужны тесты с ударом, контекстные меню, подсказки для ваших кривых, вы можете использовать простые визуальные элементы вместо элементов структуры.

+0

Спасибо! Но он находится на холсте, поэтому он должен, по крайней мере, быть UIElement (быть на панели). И поскольку свойство изменяет недействительность рендеринга, самый простой способ сделать это - через FrameworkPropertyMetadataOptions.AffectsRender, для которого требуется FraworkElement. В любом случае, как это поможет решить вышеуказанную проблему? –

0

Я бы предположил, что ваши проблемы с производительностью происходят от нисходящего от FrameworkElement и с механизмом компоновки WPF пересчитывают компоновку по мере вычисления кривой.

Что вы можете рассмотреть, это смоделировать кривую по убыванию от Freezable, а затем использовать FrameworkElement (например, PathGeometry) для отображения фактической геометрии.

+0

Макет фактически пересчитывается только при изменении источника или адресата, но если пользователь перетаскивает узел, это может быть каждый раз, когда мышь перемещает количество затронутых путей. Путь не заморожен, поэтому он не может опускаться от замораживания. –

+1

Freezable не означает, что вы не можете его изменить - вы просто не можете изменить его после его замораживания. Я бы предположил, что можно копировать, мутировать кривую, замораживать геометрию и обновлять FrameworkElement, который содержит ее намного быстрее, чем если бы вы сделали FrameworkElement делать все это. В конечном счете, я просто думаю, что не будет возможности получить приемлемую производительность от использования FrameworkElement в качестве базы. – codekaizen