Data binds to the UI after the click has been completely executed in WPF

210 Views Asked by At

I have a class with properties for an employee

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Collections.ObjectModel;

namespace SimpleDatabinding03
{
   public class Employee:INotifyPropertyChanged
    {
     int _employeenumber;
      string _firstname;
      string _lastname;
      string _dept;
      string _title;


       //constructor

       public Employee()
       {

       }


       //properties

       public int EmployeeNum
       {
           get { return _employeenumber; }
           set { _employeenumber = value; NotifyPropertyChanged("EmployeeNum"); }
       }

       public string FirstName
       {
           get { return _firstname; }
           set { _firstname = value; NotifyPropertyChanged("FirstName"); }
       }

       public string LastName
       {
           get { return _lastname; }
           set { _lastname = value; NotifyPropertyChanged("LastName"); }

       }

       public string Dept
       {
           get { return _dept; }
           set { _dept = value; NotifyPropertyChanged("Dept"); }
       }

       public string Title
       {
           get { return _title; }
           set { _title = value; NotifyPropertyChanged("Title"); }
       }


       public event PropertyChangedEventHandler PropertyChanged;

       private void NotifyPropertyChanged(string propertyname)
       {

           if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyname));
       }


       //internal object GetBindingExpression(System.Windows.DependencyProperty dependencyProperty)
       //{
       //    throw new NotImplementedException();
       //}
    }
}

and in the XAML I have bound them to a textbox:

<Grid>
        <Grid.DataContext>
            <m:Employee x:Name="employee"/>
        </Grid.DataContext>


        <Label Grid.Row="0">Employee Number</Label>
        <TextBox Name="EmpNum" Grid.Row="0" Grid.Column="1" Text="{Binding EmployeeNum}" ></TextBox>

        <Label Grid.Row="2" >first name</Label>
        <TextBox Name="Fname" Grid.Row="2" Grid.Column="1" Text="{Binding FirstName}"></TextBox>

        <Label Grid.Row="3" >Last name</Label>
        <TextBox Name="Lname" Grid.Row="3" Grid.Column="1" Text="{Binding LastName}"></TextBox>

        <Label Grid.Row="4" >Dept</Label>
        <TextBox Name="Dept" Grid.Row="4" Grid.Column="1" Text="{Binding Dept}"></TextBox>
    </Grid>

the code behind is:

private void button1_Click(object sender, RoutedEventArgs e)
    {

        employee.EmployeeNum = 123;
        System.Threading.Thread.Sleep(3000);
        employee.FirstName = "John";
        System.Threading.Thread.Sleep(3000);
       employee.LastName = "kepler"; });

    }

Requirement: When the property changes the UI textbox which is bound to the employee instance is not updated. it waits for the button click event to complete and then the UI is updated. I'm looking for a solution where the UI is updated on the fly.

3

There are 3 best solutions below

6
On

Windows UIs in .net are typically single-threaded. You're blocking the UI thread inside of the method. You can use the following to more cleanly handle this:

private async void button1_Click(object sender, RoutedEventArgs e)
{
    employee.EmployeeNum = 123;
    await Task.Delay(3000);
    employee.FirstName = "John";
    await Task.Delay(3000);
    employee.LastName = "kepler"; });
}

Note the use of async, await and Task.Delay. For more information on these, see Asynchronous Programming with Async and Await (C# and Visual Basic)

Edit - .net 3.5 answer:

Since you're on .net 3.5, that complicates things slightly. You generally do not want to update objects bound to the UI from any thread but the UI thread, but, you can do your hard work from another thread, then pass through to the UI thread.

var backgroundThread = new Thread(o => 
    {
        Application.Current.Dispatcher.Invoke(new Action(() => employee.EmployeeNum = 123));
        Thread.Sleep(3000);
        Application.Current.Dispatcher.Invoke(new Action(() => employee.FirstName = "John"));
        Thread.Sleep(3000);
        Application.Current.Dispatcher.Invoke(new Action(() => employee.LastName = "kepler"));
    });
backgroundThread.Start();

The key thing to note here is that the Dispatcher will make sure that the actions invoked will happen on the UI thread, while the 'work' will happen on the background thread.

5
On

Bound text properties default to updating when the control loses focus.

When the property changes ... employee instance is not updated.

Change each of the bindings to report an immediate update such as

="{Binding EmployeeNum, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay }"

See Binding.UpdateSourceTrigger Property for more information.


Update .Net 4+

Your code is holding up the GUI thread in the click event. Execute the changes in a background thread/task.

private void button1_Click(object sender, RoutedEventArgs e)
{

   Task.Factory.StartNew( () => { 

        employee.EmployeeNum = 123;
        System.Threading.Thread.Sleep(3000);
        employee.FirstName = "John";
        System.Threading.Thread.Sleep(3000);
        employee.LastName = "kepler"; }
   );

}

Update for .Net 3.5

 private void button1_Click(object sender, RoutedEventArgs e)
  {
       ThreadPool.QueueUserWorkItem(
            delegate // Anonymous Delegate         {
                employee.EmployeeNum = 123;
                System.Threading.Thread.Sleep(3000);
                employee.FirstName = "John";
                System.Threading.Thread.Sleep(3000);
                employee.LastName = "kepler";
            }
        );
    }

To see more info see my blog article C# MultiThreading Using ThreadPool, Anonymous Delegates and Locks.

0
On

You'll need to use background threads.

By default, all code in a method block completes before the UI gets updated, so you have to wait until all lines of code finish before the UI redraws itself.

If you want to run code in the background, a BackgroundWorker (which actually uses the ThreadPool as OmegaMan's answer mentions) can be used for 3.5.

Depending how your code is meant to run, you probably want something like this :

// run on current thread
employee.EmployeeNum = 123;

// create a separate background worker thread to run this set of code
// once it's completed, also update the FirstName
var bg1 = new BackgroundWorker();
bg1.DoWork += delegate() 
{
    System.Threading.Thread.Sleep(3000);
};
bg1.RunWorkerCompleted += delegate() 
{ 
    employee.FirstName = "John";
};
bg1.RunWorkerAsync();

// create a 2nd background worker to run this set of code.
// Can either start from your click method if it can run at the same time
// as other background worker, or you can start it from inside bg1's
// RunWorkerCompleted delegate if it should run after bg1 runs
var bg2 = new BackgroundWorker();
bg2.DoWork += delegate() 
{
    System.Threading.Thread.Sleep(3000);
};
bg2.RunWorkerCompleted += delegate() 
{ 
    employee.LastName = "kepler";
};
bg2.RunWorkerAsync();

(It should be noted, I probably have the syntax here wrong somewhere. I didn't test it in a compiler and am pretty sure I have the exact delegate syntax incorrect)

The reason you want to update your UI object in the RunWorkerCompleted is because with WPF, objects created on the main UI thread cannot be updated by other threads, such as background worker threads.

If the results of your DoWork method should create the results for your RunWorkerCompleted method, then you can use the .Result property to pass the final value from the DoWork method to the RunWorkerCompleted method.