6

I have created a Window with a TextBlock inside. I have bound the Text property and everything works fine. BUT When I change the bounded property while inside a Task then nothing works!!

Do you know why?

Public Async Sub StartProgress()
    Try
       LoadingText = "text 1" 'Works perfect

       Dim fResult As Boolean = Await LoadModules()

       If Not fResult Then
          MessageBox.Show(Me.Error)
       End If

       m_oView.Close()
    Catch ex As Exception
       Msg_Err(ex)
    End Try
End Sub

Public Async Function LoadModules() As Task(Of Boolean)
    Try
        Await Task.Delay(3000)

        LoadingText = "text 2" 'Nothing Happens

        Await Task.Delay(5000)

        LoadingText = "complete" 'Nothing Happens

        Await Task.Delay(3000)

        Return True
    Catch ex As Exception
        Me.Error = ex.Message
        Return False
    End Try
   End Function

text 2 and 3 are never shown. If I change dynamically the Text of the textblcok(ex : m_oView.txtLoadingText.Text) It works fine(but it's mnot a solution)

EDIT This is the ViewModel Base, every ViewModel implements that Class.

Public Class VM_Base
    Implements IDisposable
    Implements INotifyPropertyChanged

    Private m_oDS As MxDataSet
    Public Property [Error] As String

    Public Event PropertyChanged As PropertyChangedEventHandler _
        Implements INotifyPropertyChanged.PropertyChanged

    Protected Sub New()
        m_oDS = New MxDataSet

    End Sub

    Protected Overrides Sub Finalize()
        Try
            Me.Dispose(False)
            Debug.Fail("Dispose not called on ViewModel class.")
        Finally
            MyBase.Finalize()
        End Try
    End Sub

    Public Sub Dispose() Implements IDisposable.Dispose
        Me.Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub

    Protected Overridable Sub Dispose(disposing As Boolean)
    End Sub

    Protected Overridable Sub OnPropertyChanged(propertyName As String)
        Me.EnsureProperty(propertyName)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
    End Sub

    <Conditional("DEBUG")> _
    Private Sub EnsureProperty(propertyName As String)
        If TypeDescriptor.GetProperties(Me)(propertyName) Is Nothing Then
            Throw New ArgumentException("Property does not exist.", "propertyName")
        End If
    End Sub
End Class

How StartProgress is Called:

<i:Interaction.Triggers>
     <i:EventTrigger EventName="ContentRendered">
         <i:InvokeCommandAction Command="{Binding DataContext.WindowsActivatedCommand,ElementName=fLoading}" />
     </i:EventTrigger>
 </i:Interaction.Triggers>

EDIT Binding TextBlock to Property

Public Property LoadingText As String
     Get
         Return m_sLoadingText
     End Get
     Set(value As String)
         m_sLoadingText = value
         OnPropertyChanged("LoadingText")
     End Set
 End Property
<TextBlock x:Name="txtLoading" Width="450"
             Grid.Row="1" VerticalAlignment="Center" 
             HorizontalAlignment="Left" 
             TextWrapping="Wrap" Text="{Binding LoadingText}">
    </TextBlock>
Athari
  • 33,702
  • 16
  • 105
  • 146

5 Answers5

1

Here's a detailed answer on what you need to do to make sure calls that originate on non-UI threads invoke UI methods properly:

Ensuring that things run on the UI thread in WPF

Community
  • 1
  • 1
jlew
  • 10,491
  • 1
  • 35
  • 58
  • I don't have any Error when I try to set the Text Property. Also I use Task , neither Dispatcher nor BackgroundWorker so as to take care about Invoking the UI. Also if I force TextBlock to change the Text for example m_oView.txtLoadingText.Text = "something" then it changes!! – Emmanouil Chountasis Jun 24 '13 at 12:23
1

You need to implement INotifyPropertyChanged on your view model type, and have the LoadingText setter raise that event.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
1

@ Manolis Xountasis,

I do not know VB.net, but I test code in C#, it is ok. Below is my code:

    public partial class MainWindow : Window
{
    private TestViewModel _tvm = new TestViewModel();

    public MainWindow()
    {
        InitializeComponent();

        this.wndTest.DataContext = _tvm;

        _tvm.TestData = "First Data";

        this.btnAsync.Click += BtnAsyncOnClick;
    }

    private void BtnAsyncOnClick(object sender, RoutedEventArgs routedEventArgs)
    {
        var task = Task.Factory.StartNew(() => this.Dispatcher.Invoke(new Action(() => _tvm.TestData = "changed data")));
    }
}


<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication3" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"
x:Class="WpfApplication3.MainWindow"
x:Name="wndTest"
Title="MainWindow" 
Height="350"
Width="525">
<!--<Window.Resources>
    <local:TestViewModel x:Key="TestViewModelDataSource" d:IsDataSource="True"/>
</Window.Resources>
<Window.DataContext>
    <Binding Mode="OneWay" Source="{StaticResource TestViewModelDataSource}"/>
</Window.DataContext>-->
<Grid>
    <TextBox HorizontalAlignment="Left" TextWrapping="Wrap" VerticalAlignment="Top" Text="{Binding TestData}" />
    <Button x:Name="btnAsync" Content="Change  Async" HorizontalAlignment="Right" VerticalAlignment="Top"/>
</Grid>

Hope this code is useful for you.

  • You make the Task To Invoke the Dispatcher which means that it is not "really" async(think that my function will need about 1-2 minutes to finish). Also by documentation when we call PropertyChange("something") it checks if there is need to Invoke the control or not. I don't feel that it completes my requirements. Thanks for your time!! – Emmanouil Chountasis Jul 05 '13 at 08:16
1

According to this page (emphasis mine):

Now this might scare you off the C# asynchronous language features, because it makes them seem slow, but this is not a fair test. The reason this takes so much longer is that we’ve given the program much more work to do. When the simple, synchronous version runs on the UI thread, WPF does very little immediate work for each item we add to the LogUris collection. Data binding will detect the change—we’ve bound a ListBox to that collection, so it’ll be looking for change notification events—but WPF won’t fully process those changes until our code has finished with the UI thread. Data binding defers its work until the dispatcher thread has no higher priority work to do.

That may be the reason why it works if you update the property via the dispatcher. Have you try to force an update in the target via GetBindingExpression(Property).UpdateTarget()2 ?

Community
  • 1
  • 1
mikehc
  • 999
  • 8
  • 22
0

Nowdays you can do like this

In constructor of the Form/Window

btnAddNewCard.Click += async (s, e) => await BtnAddNewCard_ClickAsync(s, e);


private async Task BtnAddNewCard_ClickAsync(object sender, RoutedEventArgs e)
{
    // Any code like 
    await ShowOKMessageAsync(msg);                
}
NoWar
  • 36,338
  • 80
  • 323
  • 498