如何解决异步数据调用上的空 UI 绑定?

How to resolve empty UI binding on async data call?

我已将绑定添加到 IEnumerable 集合,该集合是通过 async 方法调用填充的。数据从远程数据库检索,然后添加到 CustomerOrders 列表。

但是在 运行 应用程序之后,我的 UI 绑定没有显示在视图中。该视图未显示任何数据。

为了调试问题,我检查了以下内容:

  1. 通过绑定到静态数据列表检查绑定和数据上下文。

  2. 在数据调用之后调试了 CustomerOrders 列表,该列表显示为在方法 returns 之后填充。

  3. 线程名我也查了,显示是"main thread"。 (不确定这是否可能是原因,因为它是一个不同的线程。) 3.1.I 还在 CustomerOrders 属性 上实现了本机 INPC,并在 set 上设置了一个断点,显示列表已填充。 See snapshot.

有人对这里的问题有什么建议吗?

是对CustomerOrderViewModel的总结,设置如下。一个Task属性,Initialization用于从构造函数中调用初始化代码:

using MongoDBApp.Models;
using MongoDBApp.Services;
using MongoDBApp.Utility;
using PropertyChanged;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MongoDBApp.Extensions;
using System.Windows.Input;
using MongoDBApp.Common;
using MongoDBApp.Messages;

namespace MongoDBApp.ViewModels
{
    [ImplementPropertyChanged]
    public class CustomerOrdersViewModel : IPageViewModel, INotifyPropertyChanged
    {

        private IDataService<OrderModel> _orderDataService;

        public CustomerOrdersViewModel(IDataService<OrderModel> orderDataService)
        {

            _customerOrders = new List<OrderModel>();
            //{

            //    new OrderModel(){Email = "bvarley@gmail.com", Status = true}
            //};


            this._orderDataService = orderDataService;
            this._dialogService = dialogservice;

            Messenger.Default.Register<ProductModel>(this, OnUpdateProductMessageReceived);

            this.Initialization = InitializeAsync();

        }

        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }

        #region properties

        public string SelectedCustomerEmail { get; set; }

        private IEnumerable<OrderModel> _customerOrders; 
        public IEnumerable<OrderModel> CustomerOrders 
        { 
           get { return this._customerOrders;}

           set 
           {
               _customerOrders = value;
               OnPropertyChanged("CustomerOrders");           
           }
        }

        public OrderModel SelectedOrder { get; set; }

        public Task Initialization { get; set; }

        #endregion

        #region methods

        private async Task InitializeAsync()
        {
            var customer = await AwaitableMessages.NextMessageAsync<CustomerModel>(); 
            SelectedCustomerEmail = customer.Email;
            await LoadCustomerOrdersAsync(SelectedCustomerEmail);
        }

        public async Task LoadCustomerOrdersAsync(string email)
        {
            var ordersResult = await _orderDataService.GetAllByEmailAsync(email);
            CustomerOrders = ordersResult.ToObservableCollection();
        }
        #endregion
    }
}

这也是显示绑定设置的关联视图:

 <UserControl x:Class="MongoDBApp.Views.CustomerOrdersView"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:boolean_converter="clr-namespace:MongoDBApp.Converters"
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
         xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
         d:DesignHeight="300"
         d:DesignWidth="300"
         mc:Ignorable="d">

<UserControl.Resources>
    <boolean_converter:BooleanConverter x:Key="BooleanConverter" />
</UserControl.Resources>

<Viewbox>
    <xctk:BusyIndicator IsBusy="{Binding ButtonEnabled}">

        <Grid>
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="*" />
                    <RowDefinition Height="2*" />
                    <RowDefinition Height="1*" />
                    <RowDefinition Height="1*" />
                    <RowDefinition Height="1*" />
                    <RowDefinition Height="1*" />
                    <RowDefinition Height="1*" />
                    <RowDefinition Height="1*" />
                    <RowDefinition Height="1*" />
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="1*" />
                    <ColumnDefinition Width="1*" />
                    <ColumnDefinition Width="2*" />
                    <ColumnDefinition Width="1*" />
                    <ColumnDefinition Width="2*" />
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>


                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="Loaded">
                        <i:InvokeCommandAction Command="{Binding WindowLoadedCommand}" />
                    </i:EventTrigger>
                </i:Interaction.Triggers>

                <DataGrid x:Name="customersgrid"
                          Grid.Row="0"
                          Grid.RowSpan="3"
                          Grid.Column="1"
                          Grid.ColumnSpan="4"
                          AutoGenerateColumns="False"
                          ItemsSource="{Binding CustomerOrders}"
                          SelectedItem="{Binding SelectedOrder}">
                    <DataGrid.Columns>
                        <DataGridTextColumn Binding="{Binding Email}" Header="Email" />
                        <DataGridTextColumn Binding="{Binding Date}" Header="Date" />
                        <DataGridTextColumn Binding="{Binding Status}" Header="Shipping Status" />
                    </DataGrid.Columns>
                </DataGrid>
                <Label Grid.Row="4"
                       Grid.Column="1"
                       HorizontalAlignment="Left"
                       VerticalAlignment="Top"
                       Content="Date:" />


                <TextBlock Grid.Row="4"
                           Grid.Column="2"
                           HorizontalAlignment="Left"
                           VerticalAlignment="Top"
                           Text="{Binding SelectedOrder.Date}"
                           TextWrapping="Wrap" />
                <Label Grid.Row="4"
                       Grid.Column="3"
                       HorizontalAlignment="Left"
                       VerticalAlignment="Top"
                       Content="Products:" />
                <ComboBox Grid.Row="4"
                          Grid.Column="4"
                          Grid.ColumnSpan="4"
                          Width="120"
                          HorizontalAlignment="Left"
                          VerticalAlignment="Top"
                          DisplayMemberPath="ProductId"
                          ItemsSource="{Binding SelectedOrder.Products}"
                          ScrollViewer.VerticalScrollBarVisibility="Visible"
                          SelectedItem="{Binding SelectedProduct}" />
                <Label Grid.Row="5"
                       Grid.Column="1"
                       HorizontalAlignment="Left"
                       VerticalAlignment="Top"
                       Content="Email:" />


                <TextBlock Grid.Row="5"
                           Grid.Column="2"
                           HorizontalAlignment="Left"
                           VerticalAlignment="Top"
                           Text="{Binding SelectedOrder.Email}"
                           TextWrapping="Wrap" />
                <RadioButton Grid.Row="5"
                             Grid.Column="3"
                             Grid.ColumnSpan="2"
                             HorizontalAlignment="Left"
                             VerticalAlignment="Top"
                             Content="Shipped"
                             IsChecked="{Binding SelectedOrder.Status,
                                                 Converter={StaticResource BooleanConverter},
                                                 ConverterParameter='true',
                                                 Mode=TwoWay}" />

                <RadioButton Grid.Row="5"
                             Grid.Column="4"
                             Grid.ColumnSpan="2"
                             HorizontalAlignment="Left"
                             VerticalAlignment="Top"
                             Content="Processing"
                             IsChecked="{Binding SelectedOrder.Status,
                                                 Converter={StaticResource BooleanConverter},
                                                 ConverterParameter='false',
                                                 Mode=TwoWay}" />
            </Grid>
        </Grid>
    </xctk:BusyIndicator>
</Viewbox>

您的代码没有明显的错误;在我尝试复制这个问题时,我成功地做到了,没有任何问题。我在这里发布我的代码,希望能有所帮助。

我的订单模型class;

public class OrderModel
{
    public string Email { get; set; }
    public DateTime Date { get; set; }
    public string Status { get; set; }
}

我的ViewModel(我使用了BaseViewModel;这是可选的。你做的方式没问题)

public class MainWindowViewModel:BaseViewModel
{
    public MainWindowViewModel()
    {
        _customerOrders = new List<OrderModel>();
        _customerOrders.Add(new OrderModel(){Date = DateTime.Now, Email = "mymail@gmail.com", Status = "Active"});
        InitializeAsync();
    }

    private List<OrderModel> _customerOrders;
    private OrderModel _selectedOrder;

    public List<OrderModel> CustomerOrders
    {
        get { return this._customerOrders; }

        set
        {
            _customerOrders = value;
            OnPropertyChanged("CustomerOrders");
        }
    }

    public OrderModel SelectedOrder
    {
        get { return _selectedOrder; }
        set
        {
            _selectedOrder = value;
            OnPropertyChanged("SelectedOrder");
        }
    }

    private async void InitializeAsync()
    {
        CustomerOrders = await LoadCustomerOrdersAsync();
    }

    private async Task<List<OrderModel>> LoadCustomerOrdersAsync()
    {
       return await Task.Run(() => new List<OrderModel>()
       {
           new OrderModel() {Date = DateTime.Now, Email = "mymail1@gmail.com", Status = "Active"},
           new OrderModel() {Date = DateTime.Now, Email = "mymail2@gmail.com", Status = "Active"},
           new OrderModel() {Date = DateTime.Now, Email = "mymail3@gmail.com", Status = "Active"},
           new OrderModel() {Date = DateTime.Now, Email = "mymail4@gmail.com", Status = "Active"},
           new OrderModel() {Date = DateTime.Now, Email = "mymail5@gmail.com", Status = "Active"},
           new OrderModel() {Date = DateTime.Now, Email = "mymail6@gmail.com", Status = "Active"},
       });
    }
}

我的观点

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525">
<Grid>
    <DataGrid HorizontalAlignment="Left" Margin="31,33,0,0" VerticalAlignment="Top" Height="250" Width="455" ItemsSource="{Binding Path=CustomerOrders, Mode=TwoWay}" SelectedItem="{Binding Path=SelectedOrder, Mode=TwoWay}">
        <!--<DataGrid.Columns>
            <DataGridCheckBoxColumn Binding="{x:Null}" ClipboardContentBinding="{x:Null}" Header="Email" HeaderStringFormat="Email"/>
            <DataGridCheckBoxColumn Binding="{x:Null}" ClipboardContentBinding="{x:Null}" Header="Date" HeaderStringFormat="Date"/>
            <DataGridCheckBoxColumn Binding="{x:Null}" ClipboardContentBinding="{x:Null}" Header="Status" HeaderStringFormat="Status"/>
        </DataGrid.Columns>-->
    </DataGrid>

</Grid>

代码隐藏;

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        DataContext = new MainWindowViewModel();
    }
}

我想出了一个解决方案,将异步调用放在 Window 加载的任务中,而不是构造函数中。

解决方案:(消息处理程序首先注册传递过来的值,然后从window加载的任务中调用异步加载方法)

    public CustomerOrdersViewModel(IDataService<OrderModel> orderDataService, IDialogService dialogservice)
    {                  
        this._orderDataService = orderDataService;
        this._dialogService = dialogservice;

        Messenger.Default.Register<CustomerModel>(this, OnUpdateOrderMessageReceived);
        LoadCommands();     
    }

    private void OnUpdateOrderMessageReceived(CustomerModel customer)
    {
        SelectedCustomerEmail = customer.Email;
        IsEnabled = true;                
    }

    private async Task WindowLoadedAsync(object obj)
    {
        await LoadCustomerOrdersAsync(SelectedCustomerEmail);
    }