如何解决异步数据调用上的空 UI 绑定?
How to resolve empty UI binding on async data call?
我已将绑定添加到 IEnumerable 集合,该集合是通过 async
方法调用填充的。数据从远程数据库检索,然后添加到 CustomerOrders
列表。
但是在 运行 应用程序之后,我的 UI 绑定没有显示在视图中。该视图未显示任何数据。
为了调试问题,我检查了以下内容:
通过绑定到静态数据列表检查绑定和数据上下文。
在数据调用之后调试了 CustomerOrders 列表,该列表显示为在方法 returns 之后填充。
线程名我也查了,显示是"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);
}
我已将绑定添加到 IEnumerable 集合,该集合是通过 async
方法调用填充的。数据从远程数据库检索,然后添加到 CustomerOrders
列表。
但是在 运行 应用程序之后,我的 UI 绑定没有显示在视图中。该视图未显示任何数据。
为了调试问题,我检查了以下内容:
通过绑定到静态数据列表检查绑定和数据上下文。
在数据调用之后调试了 CustomerOrders 列表,该列表显示为在方法 returns 之后填充。
线程名我也查了,显示是"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);
}