Revitalizing the user interface

    image

    The application does not respond ?!


    Many of those who program WPF applications have probably written a view construct thousands of times:
    {Binding Items}

    If receiving items from the Items collection is done in the main application thread and takes some time, we get a “dead” user interface. The application will not draw state changes for some time and will not respond to user input. And if the processing time exceeds a certain time limit defined in the Windows window system, the system will mark this window as not responding: a white mask will be applied to the image of the last successful window rendering and a special marker (Not responding) will be added to the title ( (Not responding) to Russian localization ):
    image

    Yes, you yourself have probably watched a similar picture many times when an application “goes into itself” as a result of a failure or synchronous operation. And, of course, you know how annoying the user is. Most will fight with hysteria, breaking the keyboard on the monitor, will look at the screen with incomprehension, trying to understand what is happening with the program. More “advanced” users, without waiting for the end of a long operation, will close the “delinquent” window using the task manager.

    Solution # 1: Asynchronous ObjectDataProvider


    The solution is very simple and ideal for those who use ObjectDataProvider in their current projects as a data source.

    Step # 1: We implement a simple static data provider

    A provider is a regular static class with one method:
    // Emulates a long items getting process using some delay of getting of each item

    public static class AsyncDataProvider

    {    

        private const int _DefaultDelayTime = 300;

     

        public static ReadOnlyCollection GetItems()

        {

            return GetItems(_DefaultDelayTime);

        }

     

        public static ReadOnlyCollection GetItems(int delayTime)

        {            

            List items = new List();            

            foreach (string item in Enum.GetNames(typeof(AttributeTargets)).OrderBy(item => item.ToLower()))

            {

                items.Add(item);

                // Syntetic delay to emulate a long items getting process

                Thread.Sleep(delayTime);

            }

     

            return items.AsReadOnly();

        }        

    }


    Step # 2: declare an asynchronous data source in XAML



        
            IsAsynchronous="True"

            ObjectType="Providers:AsyncDataProvider" 

            MethodName="GetItems" />

        



    The NullToBooleanConverter converter is just an auxiliary object, the purpose of which can be read in the name ( its implementation can be found in the project attached to the topic ). All the magic is in the IsAsynchronous = "True" attribute of the ObjectDataProvider . This attribute is responsible for controlling the method of receiving data - if this attribute is set to "True", the WPF core will create a Dispatcher background object to obtain the value of this property and, thus, the binding will be performed in the background thread, without interfering with the main application thread to process user input.

    Step # 3: use the data provider in the code


                ItemsSource="{Binding Source={StaticResource AsyncDataSource}, IsAsync=True}">        

        

            

                

                    

                        

                    

                


            

        




    Please note - a trigger is used for the list, which allows you to visualize the process of obtaining data for the user. This is very important - if you do not inform the user about some lengthy operation, he will think that the application simply does not work, since no action on the list will be available ( if their states are processed correctly, of course, more on that later ).

    Step # 4: Do not forget to handle the availability of actions


            Content="Edit"                     

            Width="70"                    

            IsEnabled="{Binding SelectedItem, ElementName=ItemsListBox, Converter={StaticResource NullToBooleanConverter}}"

            Click="EditButton_Click"/>

     


    Step # 5: In action

    Here's what the main window looks like after launch with all our changes:
    Asynchronous items loading in action

    And here it looks after the data is received:
    Asynchronous items loading completed

    The Edit button is attached to the selected element through a simple converter. If the selected item is not in the main list of ItemsListBox , the button will be unavailable. And it will be possible to select an element only after the asynchronous data provider AsyncDataSource fills the list with elements. Close buttonAdded to visualize the ability to control the application - nothing prevents you from clicking on it during the process of receiving data and closing the main window. At the same time, the application will correctly respond to our request and close, which would not have happened if our data source were synchronous.

    Solution # 2 Asynchronous Binding


    The second solution to this problem uses the MV-VM (Model-View-ViewModel) pattern, probably one of the most popular modular application building patterns for WPF and Silverlight now. Discussion of this pattern is beyond the scope of this article - if you wish, you can easily find a lot of information about it on the network ( if you are too lazy to look for it, look at the Links section at the end of the article ).

    Step # 1:

    Create a view model for the main application window:
    public class MainViewModel

    {

        private ICommand _commandClose;

     

        private ICommand _commandEdit;

     

        private ReadOnlyCollection _items;

     

        public ReadOnlyCollection Items

        {

            get

            {

                if (_items == null)

                {

                    _items = AsyncDataProvider.GetItems();                    

                }

     

                return _items;

            }

        }

     

        public ICommand CommandClose

        {

            get

            {

                if (_commandClose == null)

                {

                    _commandClose = new RelayCommand(p => OnClose());

                }

     

                return _commandClose;

            }

        }

     

        public ICommand CommandEdit

        {

            get

            {

                if (_commandEdit == null)

                {

                    _commandEdit = new RelayCommand(p => OnEdit(p), p => CanEdit);

                }

     

                return _commandEdit;

            }

        }

     

        public string SelectedItem

        {

            get;

            set;

        }

     

        private void OnClose()

        {

            App.Current.Shutdown();

        }

     

        private void OnEdit(object parameter)

        {

            MessageBox.Show(String.Format("Edtiting item: {0}", 

                parameter != null ? parameter.ToString() : "Not selected"));

        }

     

        private bool CanEdit

        {

            get

            {

                return SelectedItem != null;

            }            

        }

    }


    Step # 2: Slightly modify the binding declaration in the XAML code of the main view


                Grid.Row="0" 

                ItemsSource="{Binding Items, IsAsync=True}"

                SelectedItem="{Binding SelectedItem}">

        

            

                

                    

                        

                    

                


            

        




    In this scenario, the attribute specified in the binding markup is responsible for asynchrony: "{Binding Items, IsAsync = True}". As in the ObjectDataProvider example, the WPF core will create a separate background dispatcher to get the binding value in a separate asynchronous context.

    Separately, it is worth noting that in this scenario we do not need to resort to coding the rules for visibility of controls in the XAML code of the main window view. In the above view model code , the MainViewModel.CanEdit property , which is part of the MainViewModel.CommandEdit command, is responsible for the visibility of the Edit button on the form . You can learn more about the Command pattern by looking at the sectionReferences . Here it will be appropriate to notice only that we do not have to do anything manually - the CommandManager class will take care of everything . All that is required of us is the correct implementation of the ICommand contract , which the RelayCommand class provides (you can familiarize yourself with the implementation of this class in the attached project).

    Homework


    Performed at will - I won’t check, don’t even ask :) You can slightly improve the WaitControlTemplate template by turning it into a full-fledged control inherited from the Border class or from its ancestor Decorator , if you want to make a full-fledged control according to all the rules. The behavior and logic of this element will be simple:
    • Inside an element, you can add only ItemsControl or any of its descendants ( ListBox, ListView, TreeView etc ) - it is advisable to control this at the most rigid level, up to throwing an exception if the Content property is not an ItemsControl
    • When the Content property is changed, the control tries to cast the content to the ItemsControl and get the data binding value of the ItemsSource property
    • If the previous step was successful - put the binding in asynchronous mode
    • Visualization of the control may be partially built on the logic pattern Assistant (Proxy) - until data asynchronously loading control shows its contents (and spinning load indicator inscription with the request wait), displays the contents of the properties after the download Content


    Summary


    The main customers of developers are users. And it is useful for each developer from time to time to put himself in the place of a simple user, conduct a full cycle of testing his application and try to analyze what exactly he (as a user) annoys and what he (as a user) would like to improve.

    Source


    AsyncBinding.zip

    References


    WPF applications with a design model model-view-model of representation
    General information about the
    Asynchronous Data Binding command system in Windows Presentation Foundation
    Asynchronous WPF
    The main image for the topic is taken from here

    Also popular now: