views:

180

answers:

3

I'm writing an interface that features a large (~50000px width) "canvas"-type area that is used to display a lot of data in a fairly novel way. This involves lots of lines, rectangles, and text. The user can scroll around to explore the entire canvas.

At the moment I'm just using a standard Canvas panel with various Shapes placed on it. This is nice and easy to do: construct a shape, assign some coordinates, and attach it to the Canvas. Unfortunately, it's pretty slow (to construct the children, not to do the actual rendering).

I've looked into some alternatives, it's a bit intimidating. I don't need anything fancy - just the ability to efficiently construct and place objects in a coordinate plane. If all I get are lines, colored rectangles, and text, I'll be happy.

Do I need Geometry instances inside of Geometry Groups inside of GeometryDrawings inside of some Panel container?

Note: I'd like to include text and graphics (i.e. colored rectangles) in the same space, if possible.

+1  A: 

Shapes are fairly heavy-weight. You should probably look into using graphics paths. Those are much more efficient when the user doesn't need to interact with individual parts of the drawing - and sometimes even then.

500 - Internal Server Error
A: 

If you have a large number of Shape instances, you could perhaps construct them asynchronously (on a worker thread) and queue up the actual Add operations via the Dispatcher. The idea here is that the UI won't be complete right away, but the user can start interacting right away, while elements continue loading.


EDIT: The above is incorrect. WPF does require that Visual elements be created on the UI thread. You can still accomplish this sort of 'lazy' visual loading using a pattern like this:

    private Random _random = new Random();

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        Thread testThread = new Thread(TestThread);
        testThread.Start();
    }

    private void TestThread()
    {
        for (int i = 0; i < 1000; i++)
        {
            Dispatcher.BeginInvoke((Action)CreateShape);
        }
    }

    private void CreateShape()
    {
        var shape = new Rectangle();
        shape.Width = _random.Next(10, 50);
        shape.Height = _random.Next(10, 50);
        shape.Fill = new SolidColorBrush(Colors.Red);
        Canvas.SetLeft(shape, _random.Next(0, 400));
        Canvas.SetTop(shape, _random.Next(0, 200));
        LayoutRoot.Children.Add(shape);            
    }

This basically queues up tasks to be run 'asynchronously' on the UI thread (i.e. whenever the message pump is being serviced), so you can maintain responsiveness while performing the 'long' UI update.

Dan Bryant
-1: BASIC windows UI knowledge: UI elements MUST be created in the UI Thread - no worker thread here, dude ;)
TomTom
I stand corrected; I tested this out and creating a Visual does rely on the thread context. I was thinking it might not wire it up to the UI infrastructure until you actually add it to a Visual Tree.
Dan Bryant
That is an old issue going back to active x times and basically maintained for compatibility - you never know whether a sub-element starts putting up a windows form element which may be an active x control ;)
TomTom
The basic concept is still sound, however, even if the implementation details are different. There's no need to lock up the UI thread for a long period when you can allow the UI to populate over time. There's a huge difference between 3 seconds of UI lock while a UI is filled and, say, 5 seconds of time for a large number of elements to 'asynchronously' fully populate. The former can be quite aggravating.
Dan Bryant
A: 

Try not to creating shapes that you do not need, and recycle ones that you already have. Basically no user will see the whole screen, so do NOT have the shapes that are out of sight. Don't create new ones f you can avoid - basically keep shapes falling out in a "ready" list, so you can reuse them.

TomTom