Как выполнить OnRender асинхронно?

208
09 декабря 2016, 08:49

Всем привет.

Создал контрол, производный от Canvas. Рисую в нем так:

protected override void OnRender(DrawingContext dc)
    {
        base.OnRender(dc);
        if (Map != null)
        {
            if (Map.Count > 0)
            {
                FontFamily family = new FontFamily(new Uri("pack://application:,,,/Resources/Fonts/CSTITCHHD.ttf"), "CrossStitch_TG");
                Brush brush = Brushes.Black;
                foreach (var m in Map)
                {
                    FormattedText text = new FormattedText(m.Key.ToString(),
                        new System.Globalization.CultureInfo("en-US"),
                        FlowDirection.LeftToRight,
                        new Typeface(family, FontStyles.Normal, FontWeights.Normal, FontStretches.Normal), 18, brush);
                    var X = m.Value.X + (BlockSize / 2 - text.Width / 2) + Offset;
                    var Y = m.Value.Y + (BlockSize / 2 - text.Height / 2) + Offset;
                    Point p = new Point(X, Y);
                    dc.DrawText(text, p);
                }
            }
        }
    }

Map - это большая коллекция (10000-50000 элементов), заполняется она во ViewModel в асинхронном методе. При >20000 вешает UI сильно и долго.

Как быть? Есть ли относительно простой вариант рисовать асинхронно? Уже много чего пробовал, ничего не получается.

Скорость уже не важна, главное чтобы UI не вешало, пусть хоть анимация загрузки в Canvas повисит, лишь бы остальная часть не висела.

Answer 1

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

Можно попробовать вместо Canvas использовать DrawingVisual.

Если простой переход не исправит ситуацию - можно растеризовать векторный рисунок в RenderTargetBitmap и вывести его в Image

Насколько я понимаю, сам процесс растеризации можно провести в отдельном потоке, а в UI-поток передать уже результат (RenderTargetBitmap).

Answer 2

Странно все это. Пока не задашь вопрос, мысли в голову не лезут.

Итак, решение не было найдено. Наткнулся я совсем случайно на одну реализацию, и на ее основе решил проблему.

Что я делал.

Конструктор, контрола, который рисует:

public MarkMapper()
{
    _timer = new DispatcherTimer();
    _timer.Interval = RealizationInterval;
    _timer.Tick += _timer_Tick;
}

При изменению колекции "символов-точка" возвращаю стандартные значения переменных, очищаю Children у котрола. Запускаю таймер. По тику, получаю из основной коллекции Map куски заданной длины. Затем, как видно, рисую в Realize(). Также попутно проверяю, чтобы длина части не выходила за рамки, и заканчивалось рисование.

    private void _timer_Tick(object sender, EventArgs e)
    {
        if ((Map.Count - _start) < RealizationCountAtTime)
        {
            if ((Map.Count - _start) <= 0) { Stop(); return; }
            else
                _count = Map.Count - _start;
        }
        _bunch = Map.ToList().GetRange(_start, _count);
        _start += _count;
        Realize();
    }

Рисую, и добавляю в Children как VisualHost:

    private void Realize()
    {            
        //Тут рисуем с помощью DrawingVisual            
        VisualHost host = new VisualHost()
        {
            Visual = drawing,
            IsHitTestVisible = false
        };
        SetTop(host, 0);
        SetLeft(host, 0);
        Children.Add(host);
    }

Результат (100 символов раз в 10мс):

Ну и при увеличении:

Конечно, возможно это костыли и/или индийский код, но другого варианта у меня нет. И получил примерно то, что хотел.

Answer 3

Если не получается с асинхронностью - значит, пора оптимизировать алгоритм.

Для начала, надо пройтись по иерархии родительских элементов до корня, преобразовать их границы в систему координат вашего элемента, пересечь - получив тем самым видимые границы, после чего ограничиться выводом только тех надписей, которые в эти самые границы попадают.

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

Или же вместо отбрасывания следует произвести кластеризацию.

Скорее всего, вам потребуется особая структура данных для быстрого выполнения этих операций.

READ ALSO
Сравнение двух одномерных массивов

Сравнение двух одномерных массивов

Помогите сравнить 2 массиваОба одномерные, надо узнать , в каком массиве меньше единиц

226
ListView FlyoutMenu

ListView FlyoutMenu

Как правильно реализовать вызов контекстного меню на Item в ListViewИ как реализовать событие клика на кнопке удалить в этом меню для удаления...

387
Parallel ForEach заморозка потока

Parallel ForEach заморозка потока

Нужна была параллельная обработка очереди, я её реализовал, но есть проблема в том, что в определенном месте обработчика мне нужно реализовать...

236
Как реализовать нормальный scroll в picturebox?

Как реализовать нормальный scroll в picturebox?

Привет всемПомогите решить проблему с picturebox

384