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>
You need to implement INotifyPropertyChanged
on your view model type, and have the LoadingText
setter raise that event.
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
@ 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.
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 ?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With