I\'m creating some UI programmatically which involves some heavy processing. Basically what i want is to run a loader animation while my UI is being built/added to the windo
I don't see any heavy work there, aside from maybe loading images.
Ideally, you should create all of the UI elements (border/grid/etc) on the ui thread, and only do the image loading in another thread.
Or better yet, instead of creating all that UI programmatically, you should be using some form of ItemsControl
and DataTemplate
to generate all the ui, and load all of your images async in some kind of view model like thing.
Ok. Delete all your code and start all over.
This is how you do that in WPF:
<Window x:Class="WpfApplication14.ItemsControlSample2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ItemsControlSample2" WindowState="Maximized">
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Background="DarkGray" BorderBrush="Black" BorderThickness="1" CornerRadius="5"
Width="100" Height="100" Margin="10" >
<Grid>
<Image x:Name="img" Source="{Binding ImageSource}" Margin="2"/>
<TextBlock x:Name="txt" Text="Loading..." FontWeight="Bold"
VerticalAlignment="Center" HorizontalAlignment="Center"
Visibility="Collapsed" Foreground="AliceBlue"/>
</Grid>
</Border>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsLoading}" Value="True">
<Setter TargetName="img" Property="Source" Value="{x:Null}"/>
<Setter TargetName="txt" Property="Visibility" Value="Visible"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.Template>
<ControlTemplate TargetType="ItemsControl">
<ScrollViewer>
<WrapPanel IsItemsHost="True"/>
</ScrollViewer>
</ControlTemplate>
</ItemsControl.Template>
</ItemsControl>
</Window>
Code Behind:
public partial class ItemsControlSample2 : Window
{
public ItemsControlSample2()
{
InitializeComponent();
//Make sure you change this path to a valid path in your PC where you have JPG files
var path = @"F:\Media\Images\My Drums";
var images = Directory.GetFiles(path,"*.jpg")
.Select(x => new ImageViewModel()
{
Path = x,
});
DataContext = images.ToList();
}
}
Data Item:
public class ImageViewModel : INotifyPropertyChanged
{
private bool _isLoading;
public bool IsLoading
{
get { return _isLoading; }
set
{
_isLoading = value;
OnPropertyChanged("IsLoading");
}
}
private ImageSource _imageSource;
public ImageSource ImageSource
{
get { return _imageSource; }
set
{
_imageSource = value;
OnPropertyChanged("ImageSource");
}
}
private string _path;
public string Path
{
get { return _path; }
set
{
_path = value;
OnPropertyChanged("Path");
LoadImageAsync();
}
}
private void LoadImageAsync()
{
IsLoading = true;
var UIScheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() =>
{
var bmp = new BitmapImage();
bmp.BeginInit();
bmp.UriSource = new Uri(Path, UriKind.Relative);
bmp.CacheOption = BitmapCacheOption.OnLoad;
bmp.EndInit();
bmp.Freeze();
return bmp;
}).ContinueWith(x =>
{
ImageSource = x.Result;
IsLoading = false;
},UIScheduler);
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
Result:
Notice how I'm declaratively defining the UI in XAML as opposed to procedurally creating it in C# code. This is a much cleaner approach because it lets WPF do it's job
I'm using an ItemsControl which is the appropiate approach for all "items"-based UIs in WPF. Regardless of their visual appearance.
Also notice how I'm leveraging DataBinding in order to populate the UI with actual data and also DataTriggers in order to create a basic stateful behavior.
IsLoading == true
), the Image.Source
is null and the TextBlock
is shown.IsLoading == false
), the Image.Source
is bound to the ViewModel data and the TextBlock
is hidden.See how I'm using a WrapPanel for layout instead of manually placing the items. This gives you an "Explorer-like" behavior. Try resizing the Window to see the results.
I strongly suggest you read the above linked documentation, mostly the ItemsControl stuff and also Rachel's WPF Mentality post.
WPF Rocks. Just copy and paste my code in a File -> New Project -> WPF Application
and see the results for yourself.
I'm Using C# 4.0 and .Net 4.0, so I don't have async/await
. You should be able to remove all the Task
based code and replace that by this newer, cleaner async approach.
Let me know if you need further help.