【问题标题】:Keeping a WPF UI responsive while showing a slow loading UserControl在显示加载缓慢的用户控件时保持 WPF UI 响应
【发布时间】:2013-03-11 09:46:44
【问题描述】:

我们有一个使用 MVVM 模式编写的 WPF 应用程序。应用程序中有一个 TabControl,每个选项卡中都有不同的 UserControl。在某些情况下,选项卡上的用户控件之一在切换到包含选项卡时可能需要很长时间才能加载。

这不是因为 ViewModel 中存在任何性能瓶颈。但相反,这是由于用户控件需要花费大量时间来绑定到 ViewModel,并创建其中包含的各种 UI 元素并对其进行初始化。

当用户单击此用户控件的选项卡时,UI 将完全无响应,直到控件完成加载。事实上,在加载所有内容之前,您甚至都看不到“活动选项卡”开关。

在等待 UI 元素完成加载时,我可以使用哪些策略来显示带有某种“请稍候,正在加载...”消息的“微调器”?

更新示例代码:

下面演示了我试图解决的问题类型。当您单击“慢速选项卡”时。在慢速选项卡中的所有项目都呈现之前,UI 变得无响应。

在下面,TestVM 是慢速选项卡的视图模型。它有大量的子对象。每个都使用自己的数据模板创建。

如何在慢速标签完成加载时显示“正在加载”消息?

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>

【问题讨论】:

  • 使用Task和Dispatcher.BeginInvoke(),这样行吗?
  • 如果我在同一个句子中听到 TabControlBindingslow tabs 这两个词,这让我想起 TabControls 的方式存在问题当您将其绑定到 ItemsSource 时,它​​会尝试优化自身。通常您可以通过修复模板来解决速度问题。如果您有兴趣,请尝试使用不允许 TabControl 进行优化的自定义模板进行测试。我会发布一个或两个链接...

标签: wpf mvvm


【解决方案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);
        }        
    }

附:也许http://tech.pro/tutorial/662/csharp-tutorial-anonymous-delegates-and-scopingMake dispatcher example to work也有用

【讨论】:

    【解决方案2】:

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

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

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

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

    【讨论】:

    • 感谢 Sten 的想法,但我试图证明的是,在您单击选项卡之前,所有虚拟机都已创建并已完全初始化其数据。所有 UI 元素本身(与 ViewModel 相比)的创建对性能造成了影响,这也是我试图加快速度的原因。
    • @eoldre 好的,所以你确实有更复杂的问题。加速 UI 并使其具有响应性并不一定是一回事。 UI 控件必须在 UI 线程上处理,即使您从另一个线程启动该过程,UI 仍将锁定。加快速度取决于您,但通常避免构造函数加载,使事情延迟加载,包括子控件。一个(笨拙的)选择是间隔加载子元素并从单独的线程启动它,每个线程之间都有一点睡眠,因此它们按顺序显示,就像动画一样,您的 UI 将大部分是免费的
    【解决方案3】:

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-07-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多