7

We have a WPF application written using the MVVM pattern. Within the application is a TabControl with different UserControls within each tab. Under certain conditions one of the UserControls on a tab can take a significant portion of time to load when switching to the containing tab.

This is NOT because of any performance bottlenecks in the ViewModel. But instead, is due to significant amount of time that the usercontrol takes to bind to the ViewModel, and to create the various UI elements contained within it and initialize them.

When the user clicks on the tab for this usercontrol, the UI becomes completely unresponsive until the control has completed loading. If fact you don't even see the "active tab" switch until everything is loaded.

What strategies could I use to display a "spinner" with some sort of "please wait, loading..." message while waiting for the UI elements to complete loading?

UPDATE with sample code:

The below demonstrates the type of problem I am trying to get around. When you click on the "slow tab". The UI becomes unresponsive until all the items in the slow tab have rendered.

In the below, TestVM is the viewmodel for the slow tab. It has a large collection of children objects. Each created with it's own data template.

How could I display a "loading" message while the slow tab finishes loading?

public class MainVM
{
    private TestVM _testVM = new TestVM();
    public TestVM TestVM
    {
        get { return _testVM; }
    }
}

/// <summary>
/// TestVM is the ViewModel for the 'slow tab'. It contains a large collection of children objects that each will use a datatemplate to render. 
/// </summary>
public class TestVM
{
    private IEnumerable<ChildBase> _children;

    public TestVM()
    {
        List<ChildBase> list = new List<ChildBase>();
        for (int i = 0; i < 100; i++)
        {
            if (i % 3 == 0)
            {
                list.Add(new Child1());
            }
            else if (i % 3 == 1)
            {
                list.Add(new Child2());
            }
            else
            {
                list.Add(new Child3());
            }
        }
        _children = list;
    }

    public IEnumerable<ChildBase> Children
    {
        get {  return _children; }
    }
}

/// <summary>
/// Just a base class for a randomly positioned VM
/// </summary>
public abstract class ChildBase
{
    private static Random _rand = new Random(1);

    private int _top = _rand.Next(800);
    private int _left = _rand.Next(800);

    public int Top { get { return _top; } }
    public int Left { get { return _left; } }
}

public class Child1 : ChildBase { }

public class Child2 : ChildBase  { }

public class Child3 : ChildBase { }

<Window x:Class="WpfApplication3.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication3"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>

        <!-- Template for the slow loading tab -->
        <DataTemplate DataType="{x:Type local:TestVM}">
            <ItemsControl ItemsSource="{Binding Children}">
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <Canvas IsItemsHost="True"></Canvas>
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
                <ItemsControl.ItemContainerStyle>
                    <Style TargetType="FrameworkElement">
                        <Setter Property="Canvas.Top" Value="{Binding Top}"></Setter>
                        <Setter Property="Canvas.Left" Value="{Binding Left}"></Setter>
                    </Style>
                </ItemsControl.ItemContainerStyle>
            </ItemsControl>
        </DataTemplate>

        <!-- examples of different child templates contained in the slow rendering tab -->
        <DataTemplate DataType="{x:Type local:Child1}">
            <DataGrid></DataGrid><!--simply an example of a complex control-->
        </DataTemplate>

        <DataTemplate DataType="{x:Type local:Child2}">
            <RichTextBox Width="30" Height="30">
                <!--simply an example of a complex control-->
            </RichTextBox>
        </DataTemplate>

        <DataTemplate DataType="{x:Type local:Child3}">
            <Calendar Height="10" Width="15"></Calendar>
        </DataTemplate>

    </Window.Resources>
    <Grid>
        <TabControl>
            <TabItem Header="Fast Loading tab">
                <TextBlock Text="Not Much Here"></TextBlock>
            </TabItem>
            <TabItem Header="Slow Tab">
                <ContentControl Content="{Binding TestVM}"></ContentControl>
            </TabItem>
        </TabControl>
    </Grid>
</Window>
4

3 回答 3

1

你需要什么在这里

http://msdn.microsoft.com/en-us/library/ms741870.aspx

 public partial class Window1 : Window
    {
        // Delegates to be used in placking jobs onto the Dispatcher. 
        private delegate void NoArgDelegate();
        private delegate void OneArgDelegate(String arg);

        // Storyboards for the animations. 
        private Storyboard showClockFaceStoryboard;
        private Storyboard hideClockFaceStoryboard;
        private Storyboard showWeatherImageStoryboard;
        private Storyboard hideWeatherImageStoryboard;

        public Window1(): base()
        {
            InitializeComponent();
        }  

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            // Load the storyboard resources.
            showClockFaceStoryboard = 
                (Storyboard)this.Resources["ShowClockFaceStoryboard"];
            hideClockFaceStoryboard = 
                (Storyboard)this.Resources["HideClockFaceStoryboard"];
            showWeatherImageStoryboard = 
                (Storyboard)this.Resources["ShowWeatherImageStoryboard"];
            hideWeatherImageStoryboard = 
                (Storyboard)this.Resources["HideWeatherImageStoryboard"];   
        }

        private void ForecastButtonHandler(object sender, RoutedEventArgs e)
        {
            // Change the status image and start the rotation animation.
            fetchButton.IsEnabled = false;
            fetchButton.Content = "Contacting Server";
            weatherText.Text = "";
            hideWeatherImageStoryboard.Begin(this);

            // Start fetching the weather forecast asynchronously.
            NoArgDelegate fetcher = new NoArgDelegate(
                this.FetchWeatherFromServer);

            fetcher.BeginInvoke(null, null);
        }

        private void FetchWeatherFromServer()
        {
            // Simulate the delay from network access.
            Thread.Sleep(4000);              

            // Tried and true method for weather forecasting - random numbers.
            Random rand = new Random();
            String weather;

            if (rand.Next(2) == 0)
            {
                weather = "rainy";
            }
            else
            {
                weather = "sunny";
            }

            // Schedule the update function in the UI thread.
            tomorrowsWeather.Dispatcher.BeginInvoke(
                System.Windows.Threading.DispatcherPriority.Normal,
                new OneArgDelegate(UpdateUserInterface), 
                weather);
        }

        private void UpdateUserInterface(String weather)
        {    
            //Set the weather image 
            if (weather == "sunny")
            {       
                weatherIndicatorImage.Source = (ImageSource)this.Resources[
                    "SunnyImageSource"];
            }
            else if (weather == "rainy")
            {
                weatherIndicatorImage.Source = (ImageSource)this.Resources[
                    "RainingImageSource"];
            }

            //Stop clock animation
            showClockFaceStoryboard.Stop(this);
            hideClockFaceStoryboard.Begin(this);

            //Update UI text
            fetchButton.IsEnabled = true;
            fetchButton.Content = "Fetch Forecast";
            weatherText.Text = weather;     
        }

        private void HideClockFaceStoryboard_Completed(object sender,
            EventArgs args)
        {         
            showWeatherImageStoryboard.Begin(this);
        }

        private void HideWeatherImageStoryboard_Completed(object sender,
            EventArgs args)
        {           
            showClockFaceStoryboard.Begin(this, true);
        }        
    }

PS也许它也很有用http://tech.pro/tutorial/662/csharp-tutorial-anonymous-delegates-and-scoping并使调度程序示例工作

于 2013-03-21T18:17:52.790 回答
0

原因可能是绑定转换器中的慢代码、强制值回调、属性都可能使绑定看起来很慢。例如,考虑一个源绑定到 URL 的图像。由于网络滞后,这可能会加载缓慢。

还要避免切换到调度程序上下文 - 除非真的需要。例如启动线程、等待 WaitHandles、甚至大/慢同步 I/O 操作等

Sten Petrov 对延迟加载(UI 和数据虚拟化)的建议也很重要。

于 2013-03-23T03:21:56.980 回答
0

使您的控件延迟加载其内容。

为此,请在您的 TestVM 类中公开 ObservableCollection 属性并将事件处理程序附加到 CollectionChanged(也可能是 PropertyChanged)以添加实际的 UI 元素。

在 Window1 中,准备数据以在单独的线程上加载到 TestVM(您是否正在执行任何 Web 查询?),将数据传递到 UI 线程上的 TestVM。

如果 TestVM 子进程控制自己加载缓慢,您也可以将该进程从单独的线程中拆分出来,但这(方式)更难以拉动,所以希望数据加载是缓慢的部分

于 2013-03-21T20:21:15.303 回答