Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I update WPF StatusBar text before making the user wait?

I've got a WPF application with a status bar.

<StatusBar Grid.Row="1"
           Height="23"
           Name="StatusBar1"
           VerticalAlignment="Bottom">
    <TextBlock Name="TextBlockStatus" />
</StatusBar>

I'd like to display text there and switch to the hourglass Wait cursor when I do a small amount of work.

This code will update the cursor, but the StatusBar text does not update...

Cursor = Cursors.Wait
TextBlockStatus.Text = "Loading..."
System.Threading.Thread.Sleep(New TimeSpan(0, 0, 3))
TextBlockStatus.Text = String.Empty
Cursor = Cursors.Arrow

Update

Inspired by Alexandra's answer...

It works if I do it this way, but I'm not at all happy with this solution. Is there a simpler way?

Delegate Sub Load1()
Sub Load2()
    System.Threading.Thread.Sleep(New TimeSpan(0, 0, 3))
End Sub
Dim Load3 As Load1 = AddressOf Load2

Sub Load()
    Cursor = Cursors.Wait
    TextBlockStatus.Text = "Loading..."
    Dispatcher.Invoke(DispatcherPriority.Background, Load3)
    TextBlockStatus.Text = String.Empty
    Cursor = Cursors.Arrow
End Sub

I'd rather it instead looked something like this...

Sub Load()
    Cursor = Cursors.Wait
    TextBlockStatus.Text = "Loading..."

    'somehow put all the Dispatcher, Invoke, Delegate,
     AddressOf, and method definition stuff here'

    TextBlockStatus.Text = String.Empty
    Cursor = Cursors.Arrow
End Sub

Or even better...

Sub Load()
    Cursor = Cursors.Wait
    ForceStatus("Loading...")
    System.Threading.Thread.Sleep(New TimeSpan(0, 0, 3))
    ForceStatus(String.Empty)
    Cursor = Cursors.Arrow
End Sub

Sub ForceStatus(ByVal Text As String)
    TextBlockStatus.Text = Text
    'perform magic'
End Sub

Update

I've also tried to bind the TextBlock to a public property and implement INotifyPropertyChanged as IanGilham suggested. This does not work.

XAML:

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

Visual Basic:

Imports System.ComponentModel
Partial Public Class Window1
    Implements INotifyPropertyChanged

    Private _StatusText As String = String.Empty
    Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

    Property StatusText() As String
        Get
            Return _StatusText
        End Get
        Set(ByVal value As String)
            _StatusText = value
            OnPropertyChanged("StatusText")
        End Set
    End Property

    Shadows Sub OnPropertyChanged(ByVal name As String)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(name))
    End Sub
    ...

    Sub Load()
        ...
        Cursor = Cursors.Wait
        StatusText = "Loading..."
        System.Threading.Thread.Sleep(New TimeSpan(0, 0, 3))
        StatusText = String.Empty
        Cursor = Cursors.Arrow
        ...
    End Sub
...
like image 366
Zack Peterson Avatar asked Dec 23 '22 11:12

Zack Peterson


2 Answers

You should use BackgroundWorker. The work will take place an a separate thread, meaning your UI thread will be free and your application will still be responsive.

It's not going to be an incredibly compact solution code-wise, but it's the most robust and friendly to your users.

like image 137
RandomEngy Avatar answered Jan 30 '23 07:01

RandomEngy


You can easily call

TextBlockStatus.UpdateLayout();

right after you change the Text property which should refresh the control and change the text on the screen.

I also use

this.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(delegate {
    /* your code here */
}));

to (try to) make sure that my task runs after the refreshing is done.

I have to admit, it works ~90% - 95% of the time (there are times when text changes only after the task has finished or it changes with a slight delay) but I couldn't find anything better.

EDIT for the question's edit:

I'm not an expert in VB but if it doesn't support anonymous inline methods then your second way is the one that should work. Try calling UpdateLayout() before calling the dispatcher

Cursor = Cursors.Wait
TextBlockStatus.Text = "Loading..."
TextBlockStatus.UpdateLayout(); //include the update before calling dispatcher
Dispatcher.Invoke(DispatcherPriority.Background, Load3)
TextBlockStatus.Text = String.Empty
Cursor = Cursors.Arrow
like image 37
Alexandra Avatar answered Jan 30 '23 08:01

Alexandra