Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I keep the UI responsive when adding many UIElements to a canvas?

I have up to 70000 Shape derived objects to add to a Canvas. Evidently this causes the UI to be unresponsive, and in my case for around 20 seconds. I have tried using the Dispatcher method to try to overcome this but I still have problems.

The first way I implemented it is as such:

foreach (var shape in ShapeList)
{
    Dispatcher.BeginInvoke(DispatcherPriority.Input, new Action(() =>
            {
                drawingCanvas.Children.Add(shape);
            }));
}

This does make the make the UI responsive but on the downside, it is taking forever (like 10 mins?) for it to completely add all the shapes. It is also immediately displaying the Shape as it is added.

The second way I implemented it is:

Dispatcher.BeginInvoke(DispatcherPriority.Input, new Action(() =>
            {
                foreach (var shape in ShapeList)
                {
                    drawingCanvas.Children.Add(shape);
                }
            }));

And well this seems to have the same effect as not using the dispatcher at all.

I've tried both implementations on a separate thread and there is no difference from the main thread. Also, I have to add them all as separate Shape objects because each of them have to support individual MouseClickEvents hence I'm not using a drawingVisual.

So is there any way to keep the main window responsive while still keeping the adding process relatively quick?

Thanks.

*Edit I am aware of a CanvasVirtualization but it seems a bit complex for me to understand at the moment. Is there any other methods to employ?

like image 303
Enriel Avatar asked Nov 21 '25 00:11

Enriel


1 Answers

The two problems that you mention with using the Dispatcher are fairly easily solved or mitigated.

  1. drastic slowdown (20sec -> 10 mins), and
  2. shapes added piecemeal

For 1), you can speed up the process, while still retaining responsiveness, by adding the items in batches rather than one-at-a-time. For 2), a simple solution is just to hide the canvas, and show it when you're done drawing.

So something like this:

// group shapes into batches
const int batchSize = 100;
var batches = ShapeList.Select((item, index) => new { index = index, item = item })
    .GroupBy(item => item.index / batchSize)
    .ToArray();

// function to create a local closure for each batch
Action<int> addBatch = batchNumber => {
    var batch = batches[batchNumber];
    Dispatcher.BeginInvoke(DispatcherPriority.Input, new Action(() =>
    {
        foreach (var shape in batch)
        {
            drawingCanvas.Children.Add(shape.item);
        };
        if (batchNumber >= batches.Length - 1)
            drawingCanvas.Visibility = Visibility.Visible;
    }));
};

drawingCanvas.Visibility = Visibility.Collapsed;
for (int i=0 ; i < batches.Length ; i++)
{
    addBatch(i);
}
like image 148
McGarnagle Avatar answered Nov 23 '25 13:11

McGarnagle



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!