cook
cook

Reputation: 55

Updating TextBlock at start of code block in MainWindow asynchronously

I have a problem that's been bugging me for days, I've tried every option and I'm now resulting in posting my own question to find some specific help from you guys.

I need to update a TextBlock at the start of code block, which is run on a simple button click.

Here's my code:

private void NewProject(bool blnCopy = false, string strFileName = null)
    {
      if (App.ApplicationBusy == false)
      {
        App.ApplicationBusy = true;

        try
        {
          Action action = delegate()
          {
            Cursor = Cursors.Wait;
            lblStatus.Text = "Opening Project...";
          };

          Dispatcher.Invoke(DispatcherPriority.Send, action);

          if (blnCopy == false) { Project = new GSProject((App.RecentProjectCount + 1)); }

          if (Project != null)
          {
            Projects.Add(Project);

            if (blnCopy == false)
            {
              if (strFileName == null)
              {
                Project.ProjectName = string.Format("GSProject{0}", Projects.Count.ToString());
                Project.ProjectDescription = string.Format("{0} - HW GS Project", Project.ProjectName);
                Project.LoadResource();
              }
              else
              {
                Project.Load(strFileName);
              }
            }
            else
            {
              Project = Project.Copy();
            }

            p_objNewPane = objDocker.AddDocument(Project.ProjectDisplayName, Project);

            if (p_objNewPane != null)
            {
              p_objNewPane.DataContext = Project;
              BindingOperations.SetBinding(p_objNewPane, ContentPane.HeaderProperty, new Binding("ProjectDisplayName") { Source = Project });
              p_objNewPane.Closing += new EventHandler<PaneClosingEventArgs>(ContentPane_Closing);
            }

            if (Project.CalculationExists == true)
            {
              InitializeCalculation(true);
            }
          }
          tabStartPage.Visibility = Visibility.Collapsed;
          objDocumentTabs.SelectedIndex = 0;
        }
        catch (Exception ex)
        {
          ModernDialog.ShowMessage(string.Format("An error has occurred:{0}{0}{1}", Environment.NewLine, ex.Message), "Error", MessageBoxButton.OK, Application.Current.MainWindow);
        }
        finally
        {
          App.ApplicationBusy = false;
          Cursor = Cursors.Arrow;
          AppStatus = "Ready";
          p_objNewPane = null;
        }
      }
    }

At the start of the try block, I need to update the TextBlock (lblStatus) to say what's going on. The void itself, NewProject, is on the MainWindow, and is called by a button click.

Can someone please give me an idea of where I'm going wrong? I've tried countless potential solutions, so please don't be offended if I get back to you saying I've tried it.

Regards, Tom.

Upvotes: 0

Views: 77

Answers (3)

cook
cook

Reputation: 55

After a few painful days I managed to get this working. I was barking up the wrong tree completely by looking into Task Scheduling, etc. Instead all that was needed was a DependencyProperty.

XAML (Main Window):

    <TextBlock x:Name="lblStatus"
           Text="{Binding AppStatus, IsAsync=True}"
           Grid.Column="0"
           HorizontalAlignment="Left"
           VerticalAlignment="Center"
           FontFamily="Segoe UI"
           FontSize="12"
           Foreground="White"
           Margin="5, 0, 0, 0" />

C# (Main Window):

public string AppStatus
    {
      get { return (string)GetValue(AppStatusProperty); }
      set { SetValue(AppStatusProperty, value); }
    }
    public static readonly DependencyProperty AppStatusProperty =
        DependencyProperty.Register("AppStatus", typeof(string), typeof(MainWindow), new PropertyMetadata(null));

public void StatusBarUpdate(string strMainMessage)
    {
      Dispatcher.BeginInvoke((Action)(() => { AppStatus = strMainMessage; }));
    }

I can then call the StatusBarUpdate method at any time and it will asynchronously update the UI.

Upvotes: 1

Yogesh
Yogesh

Reputation: 14608

You haven't mentioned what exactly is the problem, but looking at the code I can guess that the textbox shows "Opening Project..." only after completion of your code or shows "Ready" if AppStatus is doing the same thing.

If yes, the problem is that you are doing everything on the UI thread. Although you changed the text of the textbox, it will be rendered after your work is done, hence the problem. To fix this, you need to run the code from if (blnCopy == false) to objDocumentTabs.SelectedIndex = 0; on a worker thread. That will fix your problem.

You can use TPL for that and can also you .ContinueWith with TaskScheduler.FromCurrentSynchronizationContext and TaskContinuationOptions.OnlyOnRanToCompletion to execute the finally block.

EDIT: As you are not using MVVM, you will need a lot of Invoke to make your code work. As I can see p_objNewPane is also an UI element just like Project is a view property, it will be difficult to translate all that to TPL. You can leave code from p_objNewPane = ... as it is (i.e. outside worker thread). That doesn't seem to be very cpu intensive except maybe InitializeCalculation which you can run in another worker thread.

A better approach is that you use await/async methods to wait for all heavy lifting methods.

Upvotes: 0

Manuel Amstutz
Manuel Amstutz

Reputation: 1388

You are using WPF, Therefore implement

INotifyPropertyChanged 

and use proper data binding.

private void NotifyPropertyChanged(String info)
{
    if (PropertyChanged != null)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(info));
    }
}
public string StatusText
{
    get {return this.m_statusText;}

    set
    {
        if (value != this.m_statusText)
        {
            this.m_statusText= value;
            NotifyPropertyChanged("StatusText");
        }
    }
}

And in XAML

<TextBox Text="{Binding Path=StatusText}"/>

Upvotes: 0

Related Questions