Hello.

Created a control derived from Canvas . I draw in it like this:

 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 is a large collection (10000-50000 elements), it is filled in the ViewModel in the asynchronous method. At> 20,000, the UI hangs hard and long.

How to be? Is there a relatively simple option to draw asynchronously? Already tried a lot of things, nothing happens.

The speed is no longer important, the main thing is that the UI does not hang, even though the loading animation in the Canvas will hang, so that the rest of it does not hang.

    3 answers 3

    The rendering process in WPF is already quite complicated, in particular, the main drawing stage is already asynchronous - so it’s unlikely that the whole algorithm can be transferred to another stream directly.

    You can try using DrawingVisual instead of Canvas.

    If a simple transition does not correct the situation - you can rasterize the vector image in the RenderTargetBitmap and display it in Image

    As far as I understand, the rasterization process itself can be conducted in a separate thread, and the result (RenderTargetBitmap) can be transferred to the UI stream.

    • Drawing Visual gives the same result. Probably too big a cycle. Image not suitable, the vector is necessary. - Salnik
    • @Salnik who needs something? - Pavel Mayorov
    • @Salnik you really think that the user will see the difference? - Pavel Mayorov
    • Yes. This design should be very strong. The printer also "sees." - Salnik
    • and for some reason, DrawingVisual gave an error in another thread, that I was trying to pass an “not correct” object, I don’t remember exactly the wording. - Salnik

    All this is strange. Until you ask a question, thoughts do not climb into your head.

    So, no solution was found. I accidentally stumbled upon one implementation, and based on it solved the problem.

    What did I do.

    Constructor control that draws:

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

    When changing the character-point collection, I return the standard values ​​of the variables, clearing the Children from the control. I start the timer. According to a tic, I get pieces of a given length from the main Map collection. Then, as you can see, I draw in Realize() . Also along the way, I check that the length of the part does not go beyond, and drawing ends.

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

    I draw, and add to Children as VisualHost :

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

    Result (100 characters every 10ms):

    enter image description here

    Well, when you increase:

    enter image description here

    Of course, maybe these are crutches and / or Indian code, but I have no other option. And I got about what I wanted.

    • Yes, it is also possible. The approach is somewhat outdated, but working. The main problem of this approach is that the operating values ​​of the constants (the interval of the timer and the display unit) can be found only by fitting, the optimal values ​​depending on the iron. In other words, on weaker computers, the UI will still hang, and on stronger ones, the processor will be idle. - Pavel Mayorov
    • Do not forget to accept your answer in 3 days. - Pavel Mayorov
    • @Pavel Mayorov I made the timer interval and the number of characters per tick as DepencyProperty , and now they can be put into the UI or into the Xaml designer. Yes, and the program is written for their needs. - Salnik

    If it doesn't work with asynchrony, then it's time to optimize the algorithm.

    For a start, you need to go through the hierarchy of parent elements to the root, convert their boundaries into the coordinate system of your element, cross - thereby obtaining visible boundaries, and then restrict ourselves to displaying only those labels that fall into these same boundaries.

    After that, based on the RenderTransform, it is necessary to determine the current level of scaling, then discard those labels that are too small.

    Or instead of dropping, clustering should be performed.

    Most likely, you will need a special data structure to quickly perform these operations.

    • There were such thoughts. But all would be nothing if not one but. Very often, especially on screens larger than 1280 * 720, the picture is displayed in whole or almost entirely. Those turns out in almost every case, you need to render everything. Also for printing you will need to render everything. - Salnik
    • @Salnik something I can not believe that 20,000 text elements will fit on an A4 sheet. In any case - you can wait while printing - Pavel Mayorov
    • this is not necessarily A4. And not necessarily paper (PDF). The fact is that everything is printed. There are several layers. The root layer goes to print with all the nested and through the Paginator will already be cut into several parts. - Salnik
    • @Salnik Anyway - you can wait while printing - Pavel Mayorov
    • I agree. But as I said, most often you need to render everything. You can wait until it is rendered, but so that at least there was a load / animation wait, which is not possible due to the block. The maximum that I can do is change the cursor for this time, but this is not the way out. - Salnik