【问题标题】:WinRT 8.1 Asynchronous DataBinding not updating UIWinRT 8.1 异步数据绑定不更新 UI
【发布时间】:2015-01-14 05:59:14
【问题描述】:

我的问题与我读过的其他一些问题相似,但我无法找到具体问题的答案。

注意: 在提问之前我已经阅读了以下问题:

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&lt;Payee&gt;_payees 是一个属性,设置时调用OnPropertyChanged()

我的问题是,有没有办法在LoadPayees() 完成数据加载后更新 UI 线程?我还在某处读到使用 Task 对 UI 也没有好处。我的静态方法FileService.ReadFromFile() 返回一个Task&lt;string&gt;

编辑:

这是我的方法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:

这是属性的代码,ViewOnNavigatedTo() 应要求提供。

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

【问题讨论】:

  • 我正在学习 PluralSight 的一个名为“Windows 8 - 从设计到交付使用 C# 和 XAML”的教程。这些课程都是基于 Windows 8 构建的,因此不会反映 8.1 的特定更改。
  • 这些代码 sn-ps 对我来说看起来不错。可以添加属性、视图和 OnNavigatedTo 代码吗?
  • 当await返回时,无论工作在哪里完成,它都会返回到它被调用的线程上下文,这就是为什么您可以在调用并返回调用的等待方法之后实际更新UI元素用户界面线程。 LoadPayees() 被调用到 UI 线程,所以你应该没问题。我们需要查看 XAML 并正确设置页面的 DataContext,以便数据绑定正常工作。
  • 我已经添加了属性、视图和 OnNavigatedTo() 代码。我剪掉的网格只是几个文本块/文本框,用于输入保存到收款人的数据。该部分工作正常,只是加载不起作用。

标签: c# asynchronous data-binding windows-runtime windows-8.1


【解决方案1】:

我认为问题在于您将DataContext 设置了两次。

<StackPanel Orientation="Horizontal"
            DataContext="{Binding Path=CurrentPayee}"
            Grid.Row="1">

DataContext = _vm;

ListBox 是来自外部 StackPanel 的子级,带有 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。你不应该有一个公共的列表设置器。

private readonly ObservableCollection<Payee> _payees = new ObservableCollection<Payee>();

public ObservableCollection<Payee> Payees
{
    get { return _payees; }
}

比循环并将项目添加到集合中。现在,通知视图。

foreach (var item in payeesFromJson)
{
    Payees.Add(item);
}

【讨论】:

  • 我从 StackPanel 中删除了 DataContext 并将其留在了代码隐藏中。进行了您建议的更改,因此 OnNavigatedTo() 是异步的,但仍然没有显示任何内容。 LoadPayees 肯定会填充 ObservableCollection,但我的绑定肯定还是有问题。
  • 我正在努力寻找另一个错误。你能把你的项目上传到某个地方吗?
  • 我将它上传到:absoluteparallax.com/stackoverflow/MyMoney.zip 我还发现,DataContext 是在模板的页面级别设置的:DataContext="{Binding DefaultViewModel,RelativeSource={RelativeSource Self}}" 我删除了那个,但结果还是一样。
  • 如果您在保存数据时遇到任何问题,我已将代码从 WriteToFile 复制到 Save_Click 事件处理程序,以便它会提示我保存收款人的初始位置。 json.
  • 我现在在上班,回家后我会试一试。所以 ObservableCollection 即使在 setter 中调用 OnPropertyChanged() 也不会通知视图?
猜你喜欢
  • 2015-03-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-25
  • 1970-01-01
  • 2012-11-16
  • 2014-12-02
相关资源
最近更新 更多