绑定和异步操作

时间:2013-06-24 12:05:02

标签: wpf mvvm asynchronous binding .net-4.5

我在里面创建了一个带有TextBlock的Window。我绑定了Text属性,一切正常。 的 BUT 当我在任务中更改有界属性时,没有任何作用!!

你知道为什么吗?

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
永远不会显示

文本2和3。如果我动态更改textblcok的文本(例如:m_oView.txtLoadingText.Text)它工作正常(但它不是解决方案)

修改 这是ViewModel Base,每个ViewModel实现该类。

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

如何调用StartProgress:

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

修改 将TextBlock绑定到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>

5 个答案:

答案 0 :(得分:1)

以下是您需要做的详细解答,以确保源自非UI线程的调用正确调用UI方法:

Ensuring that things run on the UI thread in WPF

答案 1 :(得分:1)

您需要在视图模型类型上实现INotifyPropertyChanged,并让LoadingText setter引发该事件。

答案 2 :(得分:1)

@ Manolis Xountasis,

我不知道VB.net,但我在C#中测试代码,没关系。以下是我的代码:

    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>

希望此代码对您有用。

答案 3 :(得分:1)

根据这个page(强调我的):

  

现在这可能会吓跑你的C#异步语言功能,   因为它让它们看起来很慢,但这不是一个公平的考验。该   之所以需要这么长时间,是因为我们给了这个程序很多   还有更多工作要做。当简单的同步版本在UI上运行时   线程,WPF对我们添加的每个项目都做了很少的即时工作   LogUris系列。 数据绑定将检测到变化 - 我们   将ListBox绑定到该集合,因此它将寻找更改   通知事件 - 但WPF不会完全处理这些更改,直到   我们的代码已经完成了UI线程。数据绑定推迟了它的工作   直到调度程序线程没有更高优先级的工作要做。

如果您通过调度程序更新属性,这可能就是它的原因。您是否尝试通过GetBindingExpression(Property).UpdateTarget() 2强制更新目标?

答案 4 :(得分:0)

现在你可以这样做

表单/窗口

的构造函数中
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);                
}