Home > OS >  Update bound property immediately from UI thread
Update bound property immediately from UI thread

Time:01-18

Most questions I see about this are asking the opposite - how to update the UI from a non-UI-thread, or a background thread.

In my case, I want the opposite. Is there anything I can call or do in the command running on the UI thread to say Update the UI now? I think this would be something like DoEvents used to be. Trying to keep the question a bit generic so it can hopefully help someone else sometime.

Things I've tried...

I've simplified my command function, just to see if I can get the ProgressText updates to work. None of this works. After the entire function executes, only the last text shows up, which is Progress Update 5. I wanted to see it go through each update text in turn.

Private Sub MoveOrRenameSongs()

    System.Threading.Thread.Sleep(2000)
    ProgressText = "Progress update 1"
    Application.Current.MainWindow.UpdateLayout()
    System.Threading.Thread.Sleep(2000)
    ProgressText = "Progress update 2"
    Application.Current.MainWindow.UpdateLayout()
    System.Threading.Thread.Sleep(2000)
    ProgressText = "Progress update 3"
    Application.Current.MainWindow.UpdateLayout()
    System.Threading.Thread.Sleep(2000)
    ProgressText = "Progress update 4"
    Application.Current.MainWindow.UpdateLayout()
    System.Threading.Thread.Sleep(2000)
    ProgressText = "Progress update 5"
    Application.Current.MainWindow.UpdateLayout()

    Dim myWindow As MainWindow = Application.Current.MainWindow
    System.Threading.Thread.Sleep(2000)
    ProgressText = "Progress update 1"
    myWindow.UpdateLayout()
    System.Threading.Thread.Sleep(2000)
    ProgressText = "Progress update 2"
    myWindow.UpdateLayout()
    System.Threading.Thread.Sleep(2000)
    ProgressText = "Progress update 3"
    myWindow.UpdateLayout()
    System.Threading.Thread.Sleep(2000)
    ProgressText = "Progress update 4"
    myWindow.UpdateLayout()
    System.Threading.Thread.Sleep(2000)
    ProgressText = "Progress update 5"
    myWindow.UpdateLayout()
End Sub

More Background and details...

I have a Command that can run for a bit longer, and I'd like to update some progress text as it goes along. However, because the command function is running in the UI thread, it's blocking the UI, and the progress text in the UI cannot update. I get that.

ProgressText is bound to a dependency property, that works fine in other command functions that run instantly.

<StatusBar DockPanel.Dock="Bottom" Height="26">
    <StatusBarItem HorizontalAlignment="Left" >
        <TextBlock Text="{Binding StatusText}" Width="Auto"></TextBlock>
    </StatusBarItem>
    <StatusBarItem HorizontalContentAlignment="Stretch">
        <TextBlock Text="{Binding ProgressText}"></TextBlock>
    </StatusBarItem>
</StatusBar>
Private _progressText As String
Public Property ProgressText() As String
    Get
        Return _progressText
    End Get
    Set(ByVal value As String)
        _progressText = value
        RaisePropertyChanged("ProgressText")
    End Set
End Property

I have tried putting the processing to a background thread, but then I run into other issues. I will list a few here, but they are beside the point.

  • I can call Dispatcher.Invoke to update ProgressText, BUT
  • I can no longer open a window that I need to collect some input; thread needs to be STA
  • I figured out how to make the thread STA, then I can't access the parent to set the position of the input window
  • get past all that, and my bound list no longer updates in the UI when I update it at the end, because the code is all written for being on the UI thread, so those updates don't work

As a result I would like to explore staying on the UI thread. This is a personal application that I'm writing as a hobby. I don't care if the UI is locked for a bit, but would like to see the progress as it does the work.

What worked in the end

Each of the 5 progress texts appears in succession as expected.

Private Async Function MoveOrRenameSongs() As System.Threading.Tasks.Task
    System.Threading.Thread.Sleep(1000)
    ProgressText = "Progress update 1"
    Await System.Threading.Tasks.Task.Delay(100)
    System.Threading.Thread.Sleep(1000)
    ProgressText = "Progress update 2"
    Await System.Threading.Tasks.Task.Delay(100)
    System.Threading.Thread.Sleep(1000)
    ProgressText = "Progress update 3"
    Await System.Threading.Tasks.Task.Delay(100)
    System.Threading.Thread.Sleep(1000)
    ProgressText = "Progress update 4"
    Await System.Threading.Tasks.Task.Delay(100)
    System.Threading.Thread.Sleep(1000)
    ProgressText = "Progress update 5"
    Await System.Threading.Tasks.Task.Delay(100)
End Function

CodePudding user response:

You can just free up the ui thread briefly and give it a bit of time to do things.

Make your command async. Exactly how easy that is depends on how you do you icommands.

With the community mvvm toolkit you can create a relaycommand with an async task:

    [RelayCommand]
    private async Task SaveTransaction()
    {
        // Expensive code
        await Task.Delay(100);
        // Expensive code
    }

Awaiting that task.delay compiles into a timer and your code is split so it resumes after 100 ms. It'll pause briefly BUT not block the UI thread like a thread.sleep would. Whatever has pile up in the dispatcher queue will start processing. Then the code resumes.

If you have no such async friendly command implementation you could consider:

https://johnthiriet.com/mvvm-going-async-with-async-command/

  • Related