绑定和异步操作

6

我创建了一个窗口,里面有一个TextBlock。我绑定了Text属性,一切正常。

但是

当我在Task中更改绑定的属性时,什么也不起作用!!

你知道为什么吗?

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从未显示。如果我动态更改文本块的文本(例如: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绑定到属性

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个回答

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>

希望这段代码对你有用。

你需要创建一个任务来调用分派程序,这意味着它并不是“真正”的异步(考虑到我的函数需要大约1-2分钟才能完成)。此外,根据文档,当我们调用PropertyChange("something")时,它会检查是否需要调用控件。我觉得这并不能满足我的要求。感谢您的时间! - Emmanouil Chountasis

1
根据此页面(重点是我的):
现在,这可能会让您避开C#异步语言特性,因为它使它们看起来很慢,但这不是一个公平的测试。之所以需要更长时间,是因为我们给程序做了更多的工作。当简单的同步版本在UI线程上运行时,WPF对于我们添加到LogUris集合中的每个项几乎没有立即的工作要做。数据绑定将检测到更改 - 我们已将ListBox绑定到该集合,因此它将正在寻找更改通知事件 - 但是WPF不会完全处理这些更改,直到我们的代码完成与UI线程的交互。数据绑定推迟其工作,直到调度程序线程没有更高优先级的工作要做。 这可能就是如果您通过调度程序更新属性的原因。您是否尝试强制通过GetBindingExpression(Property).UpdateTarget()2 更新目标?

我还没有尝试过,我会试一下并告诉你的!谢谢你的时间。 - Emmanouil Chountasis

1

当我尝试设置文本属性时,我没有任何错误。此外,我使用的是Task,而不是Dispatcher或BackgroundWorker来处理UI调用。如果我强制更改TextBlock的文本,例如m_oView.txtLoadingText.Text =“something”,那么它就会更改! - Emmanouil Chountasis

1
您需要在您的视图模型类型上实现INotifyPropertyChanged,并让LoadingText的setter方法触发该事件。

我已经做过了。像 LoadingText = "text 1" 这样的任务外代码运行得很完美。但是任务内的代码不起作用!! - Emmanouil Chountasis
我怀疑 StartProgress 是作为初始化的一部分被调用的。如果是这样的话,绑定将在你设置 LoadingText 之后生效。当你的 async 方法稍后恢复时,更新 将失败。这完全表明了一个不正确的 INotifyPropertyChanged 实现。 - Stephen Cleary
我已经编辑了我的帖子,以便查看我如何实现INotifyPropertyChanged和如何调用StartProgress。你能看一下吗? - Emmanouil Chountasis
LoadingText 如何调用 OnPropertyChanged 方法? - Stephen Cleary
我已经重新编辑了我的帖子。感谢您花费时间在此! - Emmanouil Chountasis

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);                
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接