WinRT 8.1 异步数据绑定未更新 UI
WinRT 8.1 Asynchronous DataBinding not updating UI
我的问题与我读过的其他一些问题类似,但我无法找到我的具体问题的答案。
注:
在提问之前我已经阅读了以下问题:
ListView Data Binding for Windows 8.1 Store Apps
WinRT ViewModel DataBind to async method
也就是说,我正在创建一个 Windows 8.1 应用程序,它异步加载一个文本文件,并将数据绑定到 ListBox
。我确定该问题与非 UI 线程无法更新 UI 有关,因此即使我的数据源实现了 INotifyPropertyChanged
,UI加载数据时未更新。这是我的 LoadPayees()
方法:
public async void LoadPayees()
{
try
{
var json = await FileService.ReadFromFile("folder", "payees.json");
IList<Payee> payeesFromJson = JsonConvert.DeserializeObject<List<Payee>>(json);
var payees = new ObservableCollection<Payee>(payeesFromJson);
_payees = payees;
}
catch (Exception)
{
throw;
}
if (_payees == null)
{
_payees = new ObservableCollection<Payee>();
}
}
LoadPayees()
在我页面的 OnNavigatedTo()
事件中被调用。我可以通过断点看到正在调用该方法,并且正在将收款人加载到 ObservableCollection<Payee>
中。 _payees
是一个 属性,它在设置时调用 OnPropertyChanged()
。
我的问题是,有没有办法让 UI 线程在 LoadPayees()
完成加载数据后更新?我还在某处读到,使用 Task
对 UI 也没有好处。我的静态方法 FileService.ReadFromFile()
returns a Task<string>
.
编辑:
这是我的方法 ReadFromFile()
,它也调用 OpenFile()
:
public static async Task<string> ReadFromFile(string subFolderName, string fileName)
{
SetupFolder();
var file = await OpenFile(subFolderName, fileName);
var fileContents = string.Empty;
if (file != null)
{
fileContents = await FileIO.ReadTextAsync(file);
}
return fileContents;
}
public static async Task<StorageFile> OpenFile(string subFolderName, string fileName)
{
if (_currentFolder != null)
{
var folder = await _currentFolder.CreateFolderAsync(subFolderName, CreationCollisionOption.OpenIfExists);
return await folder.GetFileAsync(fileName);
}
else
{
return null;
}
}
编辑 2:
这是请求的属性 View
和 OnNavigatedTo()
的代码。
-- ViewModel 的属性--
private ObservableCollection<Payee> _payees;
private Payee _currentPayee;
public PayeesViewModel()
{
_currentPayee = new Payee();
_payees = new ObservableCollection<Payee>();
}
public ObservableCollection<Payee> Payees
{
get { return _payees; }
set
{
_payees = value;
OnPropertyChanged();
}
}
public Payee CurrentPayee
{
get { return _currentPayee; }
set
{
_currentPayee = value;
OnPropertyChanged();
}
}
-- 查看--
<StackPanel Orientation="Horizontal"
DataContext="{Binding Path=CurrentPayee}"
Grid.Row="1">
<Grid>
<!-- snip unrelated Grid code -->
</Grid>
<ListBox x:Name="PayeesListBox"
Margin="50,0,50,0"
Width="300"
ItemsSource="{Binding Path=Payees}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=CompanyName}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
-- 后面的代码 --
private PayeesViewModel _vm = new PayeesViewModel();
public PayeesPage()
{
this.InitializeComponent();
this._navigationHelper = new NavigationHelper(this);
this._navigationHelper.LoadState += navigationHelper_LoadState;
this._navigationHelper.SaveState += navigationHelper_SaveState;
DataContext = _vm;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
_navigationHelper.OnNavigatedTo(e);
_vm.LoadPayees();
}
我认为问题是您设置了两次 DataContext
。
<StackPanel Orientation="Horizontal"
DataContext="{Binding Path=CurrentPayee}"
Grid.Row="1">
和
DataContext = _vm;
ListBox
是来自外层 StackPanel
的 child 和 DataContext CurrentPayee。在 CurrentPayee 上,您没有收款人。您不应多次设置 DataContext。
顺便说一句。像下面这样更改您的代码:
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
_navigationHelper.OnNavigatedTo(e);
await _vm.LoadPayees();
}
public async Task LoadPayees()
{
try
{
var json = await FileService.ReadFromFile("folder", "payees.json");
IList<Payee> payeesFromJson = JsonConvert.DeserializeObject<List<Payee>>(json);
var payees = new ObservableCollection<Payee>(payeesFromJson);
_payees = payees;
}
catch (Exception)
{
throw;
}
if (_payees == null)
{
_payees = new ObservableCollection<Payee>();
}
}
你永远不应该为事件处理程序之外的方法编写 async void。
编辑:
更改 ViewModel 中的 ObservableCollection。列表不应该有 public setter。
private readonly ObservableCollection<Payee> _payees = new ObservableCollection<Payee>();
public ObservableCollection<Payee> Payees
{
get { return _payees; }
}
然后循环并将项目添加到集合中。现在,通知视图。
foreach (var item in payeesFromJson)
{
Payees.Add(item);
}
我的问题与我读过的其他一些问题类似,但我无法找到我的具体问题的答案。
注: 在提问之前我已经阅读了以下问题:
ListView Data Binding for Windows 8.1 Store Apps
WinRT ViewModel DataBind to async method
也就是说,我正在创建一个 Windows 8.1 应用程序,它异步加载一个文本文件,并将数据绑定到 ListBox
。我确定该问题与非 UI 线程无法更新 UI 有关,因此即使我的数据源实现了 INotifyPropertyChanged
,UI加载数据时未更新。这是我的 LoadPayees()
方法:
public async void LoadPayees()
{
try
{
var json = await FileService.ReadFromFile("folder", "payees.json");
IList<Payee> payeesFromJson = JsonConvert.DeserializeObject<List<Payee>>(json);
var payees = new ObservableCollection<Payee>(payeesFromJson);
_payees = payees;
}
catch (Exception)
{
throw;
}
if (_payees == null)
{
_payees = new ObservableCollection<Payee>();
}
}
LoadPayees()
在我页面的 OnNavigatedTo()
事件中被调用。我可以通过断点看到正在调用该方法,并且正在将收款人加载到 ObservableCollection<Payee>
中。 _payees
是一个 属性,它在设置时调用 OnPropertyChanged()
。
我的问题是,有没有办法让 UI 线程在 LoadPayees()
完成加载数据后更新?我还在某处读到,使用 Task
对 UI 也没有好处。我的静态方法 FileService.ReadFromFile()
returns a Task<string>
.
编辑:
这是我的方法 ReadFromFile()
,它也调用 OpenFile()
:
public static async Task<string> ReadFromFile(string subFolderName, string fileName)
{
SetupFolder();
var file = await OpenFile(subFolderName, fileName);
var fileContents = string.Empty;
if (file != null)
{
fileContents = await FileIO.ReadTextAsync(file);
}
return fileContents;
}
public static async Task<StorageFile> OpenFile(string subFolderName, string fileName)
{
if (_currentFolder != null)
{
var folder = await _currentFolder.CreateFolderAsync(subFolderName, CreationCollisionOption.OpenIfExists);
return await folder.GetFileAsync(fileName);
}
else
{
return null;
}
}
编辑 2:
这是请求的属性 View
和 OnNavigatedTo()
的代码。
-- ViewModel 的属性--
private ObservableCollection<Payee> _payees;
private Payee _currentPayee;
public PayeesViewModel()
{
_currentPayee = new Payee();
_payees = new ObservableCollection<Payee>();
}
public ObservableCollection<Payee> Payees
{
get { return _payees; }
set
{
_payees = value;
OnPropertyChanged();
}
}
public Payee CurrentPayee
{
get { return _currentPayee; }
set
{
_currentPayee = value;
OnPropertyChanged();
}
}
-- 查看--
<StackPanel Orientation="Horizontal"
DataContext="{Binding Path=CurrentPayee}"
Grid.Row="1">
<Grid>
<!-- snip unrelated Grid code -->
</Grid>
<ListBox x:Name="PayeesListBox"
Margin="50,0,50,0"
Width="300"
ItemsSource="{Binding Path=Payees}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=CompanyName}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
-- 后面的代码 --
private PayeesViewModel _vm = new PayeesViewModel();
public PayeesPage()
{
this.InitializeComponent();
this._navigationHelper = new NavigationHelper(this);
this._navigationHelper.LoadState += navigationHelper_LoadState;
this._navigationHelper.SaveState += navigationHelper_SaveState;
DataContext = _vm;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
_navigationHelper.OnNavigatedTo(e);
_vm.LoadPayees();
}
我认为问题是您设置了两次 DataContext
。
<StackPanel Orientation="Horizontal"
DataContext="{Binding Path=CurrentPayee}"
Grid.Row="1">
和
DataContext = _vm;
ListBox
是来自外层 StackPanel
的 child 和 DataContext CurrentPayee。在 CurrentPayee 上,您没有收款人。您不应多次设置 DataContext。
顺便说一句。像下面这样更改您的代码:
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
_navigationHelper.OnNavigatedTo(e);
await _vm.LoadPayees();
}
public async Task LoadPayees()
{
try
{
var json = await FileService.ReadFromFile("folder", "payees.json");
IList<Payee> payeesFromJson = JsonConvert.DeserializeObject<List<Payee>>(json);
var payees = new ObservableCollection<Payee>(payeesFromJson);
_payees = payees;
}
catch (Exception)
{
throw;
}
if (_payees == null)
{
_payees = new ObservableCollection<Payee>();
}
}
你永远不应该为事件处理程序之外的方法编写 async void。
编辑:
更改 ViewModel 中的 ObservableCollection。列表不应该有 public setter。
private readonly ObservableCollection<Payee> _payees = new ObservableCollection<Payee>();
public ObservableCollection<Payee> Payees
{
get { return _payees; }
}
然后循环并将项目添加到集合中。现在,通知视图。
foreach (var item in payeesFromJson)
{
Payees.Add(item);
}