Click here to Skip to main content
15,885,128 members
Articles / Desktop Programming / WPF

WPF - Don't Block your UI

Rate me:
Please Sign up or sign in to vote.
4.92/5 (11 votes)
7 Jul 2011CPOL5 min read 70K   3.5K   40   7
Using .NET 4 features to create a non-blocking user interface

img1.png

Fig. 1. Loading user list

Introduction

Disclaimer: This article is more concept than production ready tips'n'tricks (though they are here too but not in reliable state). I advice bringing the solution's classes to some adequate state before using them in production.

UPD: seems like the disclaimer misleads - the attached solution is working, ready to be built, and functioning - but what I was trying to say is it's not perfect and the classes need a serious touch to be used in other projects.

I don't like when an application hangs during long operations leaving me in doubts. In the modern era (most of you, dear readers, have two and more cores in your processor), it's just impolite. But on the other hand, being a software developer, I understand that it's quite tricky to make reliable software in a non-blocking style.

Due its architectural legacy Windowstm OS family still has some problems with its UI updates from a "not main" thread. But as time goes on, it's 2011 and we have .NET 4 at our disposal. So let's look at how to achieve our goal.

Go, Go, Go

In the sample download, you will find a VS 2010 solution containing a WPF project with the same name. I strongly recommend downloading it and giving it a try; play with it for a minute. Its idea is simple - we have some set of users and are able to edit it and its elements.

Now look into the code, find a class called UsersProvider, its methods are:

C#
public User CreateUser()
{
    Thread.Sleep(1500);//Some serious business
    var user = new User();
    return user;
}
public void RemoveUser(User usr)
{
    Thread.Sleep(1500);//And another one
}

As you can see, this class is useless and does almost nothing but slows down execution of methods in a thread. Now imagine that the user creation and removing uses some distant Web-Service or some old-fashioned crappy database. :) And it takes a lot of time (not substantial in machine-feel time, but rather in human feelings) to accomplish operations.

We are modern and object-oriented - just like our favorite language, aren't we? So using the MVVM paradigm in the application is just what we need. A lot of mambo-jambo takes place in different parts of the app, but the main logic is kept in MainWindowController. As you can see, we have a collection Users (of guess what - right, users) which is populated during controller instantiation:

C#
if (!WPFHelper.IsInDesignMode)
{
    var tsk = Task.Factory.StartNew(InitialStart);
    tsk.ContinueWith(t => { MessageBox.Show(t.Exception.InnerException.Message); }, 
       CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, 
       TaskScheduler.FromCurrentSynchronizationContext());
}

That's it - here comes the creation of the Task - which is continued in the case of emergency (TaskContinuationOptions.OnlyOnFaulted) in the dispatcher thread ( TaskScheduler.FromCurrentSynchronizationContext()) by showing an exception message. FYI, you can freely use t.Exception.InnerException because of the Task class' nature - AggregateException as Task.Exception always pops up when an exception occurs. WPFHelper.IsInDesignMode is a static property used only to ensure that we are really running an app (if you like designing in Expression or in the built-in VS visual designer, that'll come in handy - otherwise it's just a politeness for your team members who do like it). That's all for a start - now let's look into the MainWindowController.InitialStart method:

C#
void InitialStart()
{
    try
    {
        State = StateEnum.Busy;
        User user = _provider.CreateUser();
        user.FullName = "John Smith";
        .. //User props init goes here
        Users.Add(user);

        user = _provider.CreateUser();
        ..
        Users.Add(user);
    }
    finally
    {
        State = StateEnum.Idle;
    }
}

There are two points of interest here - first, I just remembered that in Task.Factory.StartNew, it's better to set TaskScheduler.Default in other way you're risking to run on dispatcher thread (depending from place where code is called) loosing all the "magic" I'm showing. One day I'll update the sources but now, at 3 a.m. I'm too sleepy to do it. Next - bring your attention to StateEnum and State property. They are our link to UI - they help us to give UI a signal: "We are busy at the moment" and our XAML reacts to it in a following way:

XML
<ListBox.Style>
    <Style TargetType="ListBox">
        <Setter Property="Visibility" Value="Visible"/>
        <Style.Triggers>
            <DataTrigger Binding="{Binding State}" Value="Busy">
                <Setter Property="Visibility" Value="Hidden"/>
            </DataTrigger>
        </Style.Triggers>
    </Style>
</ListBox.Style>

and on the contrary in case of LoadingAnimation, control contained in the same column and row in a Grid (I mean opposite Visibility setting). Actually it's better to use VisualStateManager to control visibility when you need complex behavior. But in my case, triggers are OK.

Users collection itself is not as simple as you can think. ObservableCollection by default does not allow manipulation from threads other than it was created on. That's why I use DispatcherCollection - the idea is simple, make all operations on dispatcher thread but start them from any place you wish:

C#
protected override void OnCollectionChanged(
          System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
 if (Dispatcher != null && !Dispatcher.CheckAccess())
   {
     Dispatcher.BeginInvoke((Action)(() => 
		base.OnCollectionChanged(e)), DispatcherPriority);
   }
  else
  {
   base.OnCollectionChanged(e);
  }
}

After initial loading of users, we want to do something with that list. "Add" and "Remove" are obvious choice of commands - they are both contained in MainWindowController and bound here:

XML
<Button Style="{StaticResource toolButtonStyle}" Command="{Binding AddUserCommand}">
    <StackPanel  Orientation="Horizontal">
        <Image Width="20" Height="20" Source="Images/add_user.png"/>
        <TextBlock Text="Add user"/>
    </StackPanel>
    </Button>
      <Button  Style="{StaticResource toolButtonStyle}" 
             Command="{Binding RemoveUserCommand}">
      ...
</Button>

ActionCommand was implemented in a few minutes so it has a few design issues (getting roots in CommandManager and CanExecute requery) - but it does what I want, it allows me to get rid of annoying RoutedCommands and its bindings. In certain situations, routed commands is a must but my way is quick and clear.

img2.png

Pic 2. Toolbar button representing ActionCommand

And at the end, take a quick look at PropertyChangedHelper and a way all properties in controller are defined - I use snippet and recommend to do so to everybody if you value your time and efforts. The structure goes here:

C#
class User:PropertyChangedHelper  
{ 
 #region FullNameProperty
 public static readonly PropertyChangedEventArgs FullNameArgs = 
        PropertyChangedHelper.CreateArgs<User>(c => c.FullName);
 private string _FullName;
 public string FullName
 {
  get
  {
   return _FullName;
  }
  set
  {
   var oldValue = FullName;
   _FullName = value;
   if (oldValue != value)
   {
    OnFullNameChanged(oldValue, value);
    OnPropertyChanged(FullNameArgs);
   }
  }
}
 protected virtual void OnFullNameChanged(string oldValue, string newValue)
 {
 }
#endregion 
}

PropertyChangedHelper.CreateArgs is great in helping you to keep your property name consistent - you don't have to keep an eye on string literal with your property name anymore, feel free to use automatic rename now.

Many Thanks

I borrowed ideas from a lot of sources. That's why some of the classes are not suitable for production - they are oversimplified (remember just ideas reinterpreted, not ready solution). Exception is LoadingAnimation control - I borrowed it somewhere here, on CodeProject. Pity, but I can't remember the author - if you do send me I'll give him credit. Few ideas was borrowed from Prizm, few from my work projects and colleagues (Artem Sovetnikov is awesome on WPF). This sum of technologies allows fast and reliable creation of responsive UI - feel free to use if you find it handy!

... That's Not All Folks

There are such thing as Reactive framework - and it deserves another article.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Russian Federation Russian Federation
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Questionhow can I resize this control ? Pin
atjbatt1-Sep-19 22:40
atjbatt1-Sep-19 22:40 
GeneralMy vote of 5 Pin
mikipatate9-Mar-16 23:55
mikipatate9-Mar-16 23:55 
Generallove the dispatcherCollection Pin
shaunthasheep13-Sep-14 11:35
shaunthasheep13-Sep-14 11:35 
SuggestionGreat, 5 Points ! Pin
CyborgDE28-Apr-14 18:10
CyborgDE28-Apr-14 18:10 
QuestionI use a very simliar component in most of my WPF apps Pin
Sacha Barber7-Jul-11 4:21
Sacha Barber7-Jul-11 4:21 
AnswerRe: I use a very simliar component in most of my WPF apps Pin
Alexander Gribkov7-Jul-11 5:17
Alexander Gribkov7-Jul-11 5:17 
GeneralRe: I use a very simliar component in most of my WPF apps Pin
Sacha Barber7-Jul-11 5:29
Sacha Barber7-Jul-11 5:29 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.