显示BusyIndicator控件初始化时用户控件(WPF + MVVM + DataTemplate中的应用)
现状显示BusyIndicator控件初始化时用户控件(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秒的初始化我想显示一个BusyIndicator。由于视觉数据量非常大,它们需要2-4秒。
问题
当查看的已经完成了他们的初始化/加载过程中,我不知道,因为我只能访问到当前视图模型。
我的方法
我的想法是一个行为附加到可设置一个布尔值的InitializeComponent后其附视图模型(IsBusy =假)()成品或处理其LoadedEvent每个用户控件。该属性可以绑定到其他地方的BusyIndicator。
我对此解决方案并不满意,因为我需要将此行为附加到每个Usercontrol/view。
有没有人有这种问题的另一种解决方案?我想我不是唯一一个想要隐藏用户GUI加载过程的人!
我最近遇到这个线程http://blogs.msdn.com/b/dwayneneed/archive/2007/04/26/multithreaded-ui-hostvisual.aspx。但是,从2007年开始,可能会有一些更好/更方便的方式来实现我的目标?
另一种方法是先将用户控件隐藏并将IsBusy设置为true。在Application.Dispatcher的一个单独的线程中开始加载。最后的陈述是IsBusy = false; UserControl.Visibility = Visibility.Visible;
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 初始化数据
自定义依赖属性。
DependencyPropertyChanged回调启动工作线程管理 UI初始化。
工作线程调度执行优先级较低的小动作 进入UI线程,它保持UI负责。
当 初始化程序再次执行而前一个初始化程序尚未完成 时,保持一致状态的附加逻辑。
你在答案中投入了大量工作。可悲的是,它不能解决我的问题。我的问题解决了长时间加载UserControl的指示装饰,它只是在不加载数据项的情况下调用InitializeComponents()。所以只需隐藏渲染过程。 – KroaX
我想知道应该有多大的静态视觉树有这么长的加载时间。 您是否尝试在没有绑定DataContext并测量时间的情况下运行视图? –
有人会纠正我,如果我错了,但我认为你在这里是一个不好的地方。繁忙指标将不得不在UI线程上运行,但控制初始化也在UI线程上运行。我不认为你的繁忙指标会更新2到4秒钟来初始化你的视图。 –
我不知道DevExpress人员如何处理这种情况,但我看到他们的WPF LoadingDecorator做了类似的事情。它装饰每个ChildControl,只要它们是加载。实现这样一个装饰器也将工作,但我想我必须将其插入每个Datatemplate内。 – KroaX
另类:http://stackoverflow.com/questions/3601125/wpf-tabcontrol-preventing-unload-on-tab-changeI我也有缓慢的视觉树加载问题,但切换到缓存ContentPresenter后,我很高兴。 – Peter