显示BusyIndi​​cator控件初始化时用户控件(WPF + MVVM + DataTemplate中的应用)

问题描述:

现状显示BusyIndi​​cator控件初始化时用户控件(WPF + MVVM + DataTemplate中的应用)

我用下面的方法来解决一个匹配视图模型视图。 (简体)

<Window.Resources> 
    <ResourceDictionary> 
     <DataTemplate DataType="{x:Type local:DemoVm2}"> 
      <local:DemoViewTwo /> 
     </DataTemplate> 
     <DataTemplate DataType="{x:Type local:DemoVm}"> 
      <local:DemoView /> 
     </DataTemplate> 
    </ResourceDictionary> 
</Window.Resources> 

<DockPanel LastChildFill="True"> 
    <Button Content="Switch To VmOne" Click="ButtonBase_OnClick"></Button> 
    <Button Content="Switch To VmTwo" Click="ButtonBase_OnClick2"></Button> 

    <ContentPresenter Content="{Binding CurrentContent}" /> 
</DockPanel> 

在切换ContentPresenter中的ViewModel后,视图会自动由WPF解析。

当使用复杂的View可能需要2-4秒的初始化我想显示一个BusyIndi​​cator。由于视觉数据量非常大,它们需要2-4秒。

问题

当查看的已经完成了他们的初始化/加载过程中,我不知道,因为我只能访问到当前视图模型。

我的方法

我的想法是一个行为附加到可设置一个布尔值的InitializeComponent后其附视图模型(IsBusy =假)()成品或处理其LoadedEvent每个用户控件。该属性可以绑定到其他地方的BusyIndi​​cator。

我对此解决方案并不满意,因为我需要将此行为附加到每个Usercontrol/view。

有没有人有这种问题的另一种解决方案?我想我不是唯一一个想要隐藏用户GUI加载过程的人!

我最近遇到这个线程http://blogs.msdn.com/b/dwayneneed/archive/2007/04/26/multithreaded-ui-hostvisual.aspx。但是,从2007年开始,可能会有一些更好/更方便的方式来实现我的目标?

+0

有人会纠正我,如果我错了,但我认为你在这里是一个不好的地方。繁忙指标将不得不在UI线程上运行,但控制初始化也在UI线程上运行。我不认为你的繁忙指标会更新2到4秒钟来初始化你的视图。 –

+0

我不知道DevExpress人员如何处理这种情况,但我看到他们的WPF LoadingDecorator做了类似的事情。它装饰每个ChildControl,只要它们是加载。实现这样一个装饰器也将工作,但我想我必须将其插入每个Datatemplate内。 – KroaX

+0

另类:http://*.com/questions/3601125/wpf-tabcontrol-preventing-unload-on-tab-changeI我也有缓慢的视觉树加载问题,但切换到缓存ContentPresenter后,我很高兴。 – Peter

另一种方法是先将用户控件隐藏并将IsBusy设置为true。在Application.Dispatcher的一个单独的线程中开始加载。最后的陈述是IsBusy = false; UserControl.Visibility = Visibility.Visible;

+0

WPF可视化树不能从不同的线程初始化。你会得到InvalidOperationException –

这个问题没有简单而通用的解决方案。 在每个具体情况下,您都应该编写非阻塞可视树初始化的自定义逻辑。

下面是一个示例,介绍如何使用初始化指标实现ListView的非阻塞初始化。

用户控件包含ListView和初始化指示符:

XAML:

<UserControl x:Class="WpfApplication1.AsyncListUserControl" 
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
      xmlns:local="clr-namespace:WpfApplication1" 
      mc:Ignorable="d" 
      d:DesignHeight="300" d:DesignWidth="300"> 
    <Grid Margin="5" Grid.Row="1"> 
     <ListView x:Name="listView"/> 
     <Label x:Name="itemsLoadingIndicator" Visibility="Collapsed" Background="Red" HorizontalAlignment="Center" VerticalAlignment="Center">Loading...</Label> 
    </Grid> 
</UserControl> 

CS:

public partial class AsyncListUserControl : UserControl 
{ 
    public static DependencyProperty ItemsProperty = DependencyProperty.Register("Items", typeof(IEnumerable), typeof(AsyncListUserControl), new PropertyMetadata(null, OnItemsChanged)); 

    private static void OnItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    { 
     AsyncListUserControl control = d as AsyncListUserControl; 
     control.InitializeItemsAsync(e.NewValue as IEnumerable); 
    } 

    private CancellationTokenSource _itemsLoadiog = new CancellationTokenSource(); 
    private readonly object _itemsLoadingLock = new object(); 

    public IEnumerable Items 
    { 
     get 
     { 
      return (IEnumerable)this.GetValue(ItemsProperty); 
     } 
     set 
     { 
      this.SetValue(ItemsProperty, value); 
     } 
    } 

    public AsyncListUserControl() 
    { 
     InitializeComponent(); 
    } 

    private void InitializeItemsAsync(IEnumerable items) 
    { 
     lock(_itemsLoadingLock) 
     { 
      if (_itemsLoadiog!=null) 
      { 
       _itemsLoadiog.Cancel(); 
      } 

      _itemsLoadiog = new CancellationTokenSource(); 
     } 

     listView.IsEnabled = false; 
     itemsLoadingIndicator.Visibility = Visibility.Visible; 
     this.listView.Items.Clear(); 

     ItemsLoadingState state = new ItemsLoadingState(_itemsLoadiog.Token, this.Dispatcher, items); 

     Task.Factory.StartNew(() => 
     { 
      int pendingItems = 0; 
      ManualResetEvent pendingItemsCompleted = new ManualResetEvent(false); 

      foreach(object item in state.Items) 
      { 
       if (state.CancellationToken.IsCancellationRequested) 
       { 
        pendingItemsCompleted.Set(); 
        return; 
       } 

       Interlocked.Increment(ref pendingItems); 
       pendingItemsCompleted.Reset(); 

       state.Dispatcher.BeginInvoke(
        DispatcherPriority.Background, 
        (Action<object>)((i) => 
        { 
         if (state.CancellationToken.IsCancellationRequested) 
         { 
          pendingItemsCompleted.Set(); 
          return; 
         } 

         this.listView.Items.Add(i); 
         if (Interlocked.Decrement(ref pendingItems) == 0) 
         { 
          pendingItemsCompleted.Set(); 
         } 
        }), item); 
      } 

      pendingItemsCompleted.WaitOne(); 
      state.Dispatcher.Invoke(() => 
      { 
       if (state.CancellationToken.IsCancellationRequested) 
       { 
        pendingItemsCompleted.Set(); 
        return; 
       } 

       itemsLoadingIndicator.Visibility = Visibility.Collapsed; 
       listView.IsEnabled = true; 
      }); 
     }); 
    } 

    private class ItemsLoadingState 
    { 
     public CancellationToken CancellationToken { get; private set; } 
     public Dispatcher Dispatcher { get; private set; } 
     public IEnumerable Items { get; private set; } 

     public ItemsLoadingState(CancellationToken cancellationToken, Dispatcher dispatcher, IEnumerable items) 
     { 
      CancellationToken = cancellationToken; 
      Dispatcher = dispatcher; 
      Items = items; 
     } 
    } 
} 

用例:

<Window x:Class="WpfApplication1.MainWindow" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
     xmlns:local="clr-namespace:WpfApplication1" 
     mc:Ignorable="d" 
     Title="MainWindow" Height="350" Width="525"> 
    <Window.DataContext> 
     <local:MainWindowViewModel/> 
    </Window.DataContext> 
    <Grid> 
     <Grid.RowDefinitions> 
      <RowDefinition Height="Auto"/> 
      <RowDefinition/> 
     </Grid.RowDefinitions> 
     <Button Content="Load Items" Command="{Binding LoadItemsCommand}" /> 
     <local:AsyncListUserControl Grid.Row="1" Items="{Binding Items}"/> 
    </Grid> 
</Window> 

视图模型:

using System; 
using System.Collections.Generic; 
using System.ComponentModel; 
using System.Windows.Input; 

namespace WpfApplication1 
{ 
    public class MainWindowViewModel:INotifyPropertyChanged 
    { 
     private readonly ICommand _loadItemsCommand; 
     private IEnumerable<string> _items; 

     public event PropertyChangedEventHandler PropertyChanged; 

     public MainWindowViewModel() 
     { 
      _loadItemsCommand = new DelegateCommand(LoadItemsExecute); 
     } 

     public IEnumerable<string> Items 
     { 
      get { return _items; } 
      set { _items = value; OnPropertyChanged(nameof(Items)); } 
     } 

     public ICommand LoadItemsCommand 
     { 
      get { return _loadItemsCommand; } 
     } 

     private void LoadItemsExecute(object p) 
     { 
      Items = GenerateItems(); 
     } 

     private IEnumerable<string> GenerateItems() 
     { 
      for(int i=0; i<10000; ++i) 
      { 
       yield return "Item " + i; 
      } 
     } 

     private void OnPropertyChanged(string propertyName) 
     { 
      var h = PropertyChanged; 
      if (h!=null) 
      { 
       PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); 
      } 
     } 

     public class DelegateCommand : ICommand 
     { 
      private readonly Predicate<object> _canExecute; 
      private readonly Action<object> _execute; 
      public event EventHandler CanExecuteChanged; 

      public DelegateCommand(Action<object> execute) : this(execute, null) { } 

      public DelegateCommand(Action<object> execute, Predicate<object> canExecute) 
      { 
       _execute = execute; 
       _canExecute = canExecute; 
      } 

      public bool CanExecute(object parameter) 
      { 
       if (_canExecute == null) 
       { 
        return true; 
       } 

       return _canExecute(parameter); 
      } 

      public void Execute(object parameter) 
      { 
       _execute(parameter); 
      } 

      public void RaiseCanExecuteChanged() 
      { 
       if (CanExecuteChanged != null) 
       { 
        CanExecuteChanged(this, EventArgs.Empty); 
       } 
      } 
     } 
    } 
} 

这种方法的主要特点:对于需要大量的UI 初始化数据

  1. 自定义依赖属性。

  2. DependencyPropertyChanged回调启动工作线程管理 UI初始化。

  3. 工作线程调度执行优先级较低的小动作 进入UI线程,它保持UI负责。

  4. 当 初始化程序再次执行而前一个初始化程序尚未完成 时,保持一致状态的附加逻辑。

+0

你在答案中投入了大量工作。可悲的是,它不能解决我的问题。我的问题解决了长时间加载UserControl的指示装饰,它只是在不加载数据项的情况下调用InitializeComponents()。所以只需隐藏渲染过程。 – KroaX

+0

我想知道应该有多大的静态视觉树有这么长的加载时间。 您是否尝试在没有绑定DataContext并测量时间的情况下运行视图? –