There is no easy and universal solution for this problem.
In each concrete case you should write custom logic for non blocking visual tree initialization.
Here is an example how to implement non blocking initialization of ListView with initializing indicator.
UserControl that contains ListView and initializing indicator:
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;
}
}
}
Usage example:
<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);
}
}
}
}
}
The main features of this approach:
Custom dependency properties for data that requires much UI
initialization.
DependencyPropertyChanged callback starts worker thread that manages
UI initialization.
Worker thread dispatches small actions with low execution priority
into UI thread which keeps UI responsible.
Additional logic to keep consistent state in case when
initialization executed again while previous initialization is not
completed yet.