UWP 使用 x:Bind 设置 GridView 项源时出现问题



我正在尝试使用数据虚拟化和编译绑定使用图片库中的照片填充我的网格视图。

我已获取Microsoft UWP(数据虚拟化示例(并使用其 FileSource 作为基础,将其修改为使用我自己的 Picture 对象,并尝试将其应用于我的 UWP 应用。我得到的只是一个空白页,设计师抛出了一个例外。

我想使用 x:Bind 绑定到模型中的数据源对象,因为我使用的是 MVVM 并且不希望代码隐藏。

我无法让它在我的应用程序中工作,所以我编写了一个甚至不是 MVVM 的小型测试应用程序,并尝试将 x:Bind 与我的数据源一起使用作为代码隐藏中的对象,但它也无法绑定到集合。

如果我直接在代码隐藏中设置网格视图的源代码(这是示例正在执行的操作(,则可以使用我的 Picture 对象获得这项工作。

 async void initdata()
 {
    StorageLibrary pictures = await StorageLibrary.GetLibraryAsync(KnownLibraryId.Pictures);
    string path = pictures.SaveFolder.Path;
    FileDataSource ds = await FileDataSource.GetDataSoure(path);
    if (ds.Count > 0)
    {
        PicturesGrid.ItemsSource = ds;
    }
    else
    {
        MainPage.Current.NotifyUser("Error: The pictures folder doesn't contain any files", NotifyType.ErrorMessage);
    }
}

文件数据源的定义如下:

/// <summary>
/// A custom datasource over the file system that supports data virtualization
/// </summary>
public class FileDataSource : INotifyCollectionChanged, System.Collections.IList, IItemsRangeInfo
{
   ...
}

在我的代码中,我创建了图片集作为一个属性:

public sealed partial class MainPage : Page
{
    public FileDataSource _PicturesCollection;
    public FileDataSource PicturesCollection { get; private set; }
    public MainPage()
    {
        this.InitializeComponent();
        PicturesGrid.ContainerContentChanging += PicturesGrid_ContainerContentChanging;
        PicturesCollection = null;
        initdata();
    }
    private void PicturesGrid_ContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args)
    {
        if (!args.InRecycleQueue)
        {
            // Sets a textblock in each item indicating its index
            //FrameworkElement ctr = (FrameworkElement)args.ItemContainer.ContentTemplateRoot;
            //if (ctr != null)
            //{
            //    TextBlock t = (TextBlock)ctr.FindName("idx");
            //    t.Text = args.ItemIndex.ToString();
            //}
        }
    }
    async void initdata()
    {
        StorageLibrary pictures = await StorageLibrary.GetLibraryAsync(KnownLibraryId.Pictures);
        string path = pictures.SaveFolder.Path;
        _PicturesCollection = await FileDataSource.GetDataSoure(path);
        if (_PicturesCollection.Count > 0)
        {
            PicturesCollection = _PicturesCollection;
            //PicturesGrid.ItemsSource = ds;
        }
    }
}

并将其绑定到我的网格视图:

<Grid Grid.Row="1">
    <GridView x:Name="PicturesGrid"
              SelectionMode="Single"
              ShowsScrollingPlaceholders="False"
              ItemsSource="{x:Bind PicturesCollection}">
        <GridView.ItemTemplate>
            <DataTemplate x:DataType="local:Picture" >
                <Grid Width="200" Height="80">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="*" />
                        <RowDefinition Height="Auto" />
                    </Grid.RowDefinitions>
                    <Border Grid.RowSpan="2" Background="DimGray" Opacity="0.8" />
                    <Image Width ="130"
                           HorizontalAlignment="Center"
                           Stretch="Uniform"
                           Source="{x:Bind ImageThumbNail, Converter ={StaticResource StorageItemThumbnailoImageSourceConverter}, Mode=OneWay}" />
                    <TextBlock Grid.Row="1"
                               MaxHeight="30"
                               Text="{x:Bind Name}"
                               Foreground="White"
                               HorizontalAlignment="Center"
                               TextTrimming="CharacterEllipsis"/>
                </Grid>
            </DataTemplate>
        </GridView.ItemTemplate>
    </GridView>
</Grid>

这给了我一个空白页,但是如果我将其设置为代码隐藏,它可以工作。谁能告诉我为什么会这样?我错过了什么?

这里的问题是,当你的页面加载时,你的PicturesCollection属性没有设置,所以你的PicturesGrid ItemsSourcenull的,你可以卖笔记是你的页面。

MainPage 的构造函数中,您正在使用initdata方法来获取所有数据。但是,此方法async void,您没有等待其完成。实际上,我们也不能在构造函数中使用await。因此,当您的页面加载时,await FileDataSource.GetDataSoure(path);的执行可能尚未完成,您PicturesCollection属性仍null此处,但您的PicturesGrid ItemsSource已绑定到您的PicturesCollection属性。因此,ItemsSource为空,您什么也看不到。虽然您的PicturesCollection媒体资源稍后会设置为实际数据,但您没有为PicturesCollection媒体资源实现属性更改通知。而对于x:Bind默认ModeOneTime,所以你的PicturesGrid ItemsSource将永远是null

若要解决此问题,可以为PicturesCollection属性实现属性更改通知,如下所示:

public sealed partial class MainPage : Page, INotifyPropertyChanged
{
    private FileDataSource _PicturesCollection;
    public event PropertyChangedEventHandler PropertyChanged;
    public FileDataSource PicturesCollection
    {
        get
        {
            return _PicturesCollection;
        }
        set
        {
            if (_PicturesCollection != value)
            {
                _PicturesCollection = value;
                NotifyPropertyChanged();
            }
        }
    }
    private void NotifyPropertyChanged([CallerMemberName] String propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
    ...
    private async void initdata()
    {
        StorageLibrary pictures = await StorageLibrary.GetLibraryAsync(KnownLibraryId.Pictures);
        string path = pictures.SaveFolder.Path;
        var source = await FileDataSource.GetDataSoure(path);
        if (source.Count > 0)
        {
            PicturesCollection = source;
        }
    }
}

在 XAML 中,将x:BindMode设置为OneWay如下所示:

<GridView x:Name="PicturesGrid"
          SelectionMode="Single"
          ShowsScrollingPlaceholders="False"
          ItemsSource="{x:Bind PicturesCollection, Mode=OneWay}">
    ...
</GridView>

在此之后,您的x:Bind将起作用。

上部数据:

如果只需要异步加载的数据的一次性绑定,则可以通过在加载数据后调用this.Bindings.Update();来强制初始化一次性绑定,如下所示:

async void initdata()
{
    StorageLibrary pictures = await StorageLibrary.GetLibraryAsync(KnownLibraryId.Pictures);
    string path = pictures.SaveFolder.Path;
    _PicturesCollection = await FileDataSource.GetDataSoure(path);
    if (_PicturesCollection.Count > 0)
    {
        PicturesCollection = _PicturesCollection;
        this.Bindings.Update();
    }
}

以这种方式初始化它们比使用单向绑定和侦听更改要便宜得多,因为它只需要在代码中添加一个方法。但是,在使用 MVVM 时,这可能不是一个好的做法。有关详细信息,请参阅如果在使用 {x:Bind} 声明的绑定对象中异步加载数据

支持 {x:Bind} 的代码是在编译时在页面的分部类中生成的。这些文件可以在obj文件夹中找到,名称类似于(对于 C#( <view name>.g.cs 。生成的代码包括页面 Loading 事件的处理程序,该处理程序在表示页面绑定的生成类上调用 Initialize 方法。初始化依次调用 Update 以开始在绑定源和目标之间移动数据。加载在页面或用户控件的第一个度量传递之前引发。因此,如果数据是异步加载的,则在调用 Initialize 时可能尚未准备就绪。因此,加载数据后,可以通过调用 this->Bindings->Update(); 强制初始化一次性绑定。如果只需要异步加载数据的一次性绑定,则以这种方式初始化它们比使用单向绑定和侦听更改要便宜得多。如果数据未进行精细更改,并且可能会作为特定操作的一部分进行更新,则可以一次性进行绑定,并随时通过调用 Update 强制手动更新。

若要在 UWP 中使用 x:Bind,应在 XAML 中定义 ViewModel 的对象,如下所示:

<Page.DataContext>
    <local:MyViewModel x:Name="MyViewModel"/>
</Page.DataContext>

并像这样引用它:

ItemsSource="{x:Bind MyViewModel.PicturesCollection}"

x:Bind 只能从 XAML 中预定义的 ViewModel 对象进行绑定。

最新更新