[英]Show BusyIndicator while Initializing UserControls (WPF + Mvvm + DataTemplate Application)
現在的情況
我正在使用以下方法來為匹配的ViewModel解析視圖。 (簡體)
<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秒。
問題
我不知道View何時完成其初始化/加載過程,因為我只能訪問當前的ViewModel。
我的方法
我的想法是給每個UserControl附加一個行為,該行為可以在InitializeComponent()完成或處理其LoadedEvent之后為其附加的ViewModel設置一個布爾值(IsBusy = false)。 該屬性可以綁定到其他位置的BusyIndicator。
我對此解決方案並不真正滿意,因為我需要將此行為附加到每個單獨的Usercontrol / view。
有沒有人對這種問題有另一種解決方案? 我想我不是唯一想向用戶隱藏GUI加載過程的人嗎?
我最近遇到了這個線程http://blogs.msdn.com/b/dwayneneed/archive/2007/04/26/multithreaded-ui-hostvisual.aspx 。 但這是從2007年開始的,因此可能會有一些更好/更方便的方法來實現我的目標?
這個問題沒有簡單通用的解決方案。 在每種具體情況下,您都應該編寫自定義邏輯以實現無阻塞的可視化樹初始化。
這是一個如何使用初始化指示器實現ListView的非阻塞初始化的示例。
包含ListView和初始化指示符的UserControl:
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>
ViewModel:
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,然后將IsBusy設置為true。 在Application.Dispatcher上的單獨線程中開始加載。 踏板的最終陳述是IsBusy = false; UserControl.Visibility = Visibility.Visible;
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.