简体   繁体   中英

How to initialize a heavy control in another thread?

I heave two user controls. The first one contains one ItemsControl and I have to fill it dynamically with my second user control. The second user control may contains another ÌtemsControl itself recursively so it is quite a heavy control to initialize.

Here is what I did for the SubControl :

public sealed partial class SubControl : UserControl
{
    public SubControl(ModelA item)
    {
        this.InitializeComponent();
        this.DataContext = item;

        //if the current item has child items, add them to the ItemsControl:
        if(item.SubItems != null)
        {
            BuildUI(item.SubItems);
        }
    }

    private void BuildUI(List<AbstractModel> data)
    {
        foreach(var item in data)
        {
            var dataItem = item as ModelA;
            //we only want item of type ModelA in our ItemsControl:
            if(dataItem != null)
            {
                SubElements.Items.Add(new SubControl(dataItem));
            }
        }
    }
}

Now here is what I wrote first for the MainControl whose ItemsControl (called Elements ) will contain a set of these SubControl :

public sealed partial class MainControl : UserControl
{
    public MainControl(List<AbstractModel> data)
    {
        this.InitializeComponent();
        BuildUI(data);
    }

    private void BuildUI(List<AbstractModel> data)
    {
        foreach(var item in data)
        {
            var dataItem = item as ModelA;
            if(dataItem != null)
            {
                Elements.Items.Add(item);
            }
        }
    }
}

I noticed small freezes of the UI while this "tree" was being built, but I currently work on a very powerful computer. I would like the application to run smoothly even on lesser devices such as the Windows Surface RT. So I changed the MainControl code:

public sealed partial class MainControl : UserControl
{
    public MainControl(List<AbstractModel> data)
    {
        this.InitializeComponent();
        BuildUI(data);
    }

    private async void BuildUI(List<AbstractModel> data)
    {
        var list = new List<SubControl>();
        await Task.Run(() =>
        {
            foreach(var item in data)
            {
                var dataItem = item as ModelA;
                if(dataItem != null)
                {
                    list.Add(new SubControl(dataItem));
                }
            }
        });
        foreach(var item in list)
        {
            Elements.Items.Add(item);
        }
    }
}

The idea is to build all the SubControl in a different thread so the UI is not blocked, and when all the user controls have been initialized we would add them to the ItemsControl in the MainControl .

However this does not work because of the marshalling of the data, even though not a single SubControl is actually present on the UI! It crashes while building the SubControl which is really weird, because it does not have any impact on the actual UI; they are just added to a temporary List .

What could be a trick to build these user controls in a background task so the UI does not freeze?

Windows UI is very single-threaded. Each UI control must be created and only used from a single thread. There is no way around this.

So, it's time to think about the solution a bit differently. It's no problem to create dozens of controls; the UI would handle that just fine. You're talking about adding hundreds or thousands of items to a list control, and that's just an unusable UI. So the proper solution is to rethink your UI design. Perhaps you could divide the results into categories or something.

If you've thought about your UI design and are still sure that you want to display hundreds or thousands of items to the user, then the answer is to use virtualization . This is a bit harder to code than just a simple foreach loop, but it is the only way to efficiently display large amounts of data.

There are many ways to do this.

You could use this workflow:

  • Implement a EventHandler, that is raised on initialiazation finish.
  • Show an empty MainControl with saying "loading"
  • Build all UIs in another Thread
  • When built all UIs, raise the finish event
  • A listener to this event will add the SubControls to your MainControl

[...] because it does not have any impact on the actual UI; they are just added to a temporary List

By the way: Your code does crash, because you are adding the Controls in a async method, which is actually a non GUI-thread. So you are wrong with your proposition.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM