I am trying to populate my gridview with Photos from the Pictures library using data virtualization and compiled binding.
I've taken the Microsoft UWP (Data Virtualization Sample) and using its FileSource as my base, I modified it to use my own Picture object and tried to apply it to my UWP app. All I am getting is a blank page, and the designer is throwing an exception.
I want to use x:Bind to bind to my data source object in my model as I am using MVVM and don't want code-behind.
I couldn't get this to work in my app so I wrote a small test app that isn't even MVVM and tried to use x:Bind with my data source as an object in the code behind and it fails as to bind to the collection as well.
I can get this work with my Picture object if I set the gridview's source directly in my code-behind (which is what the sample is doing).
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);
}
}
The FileDataSource is defined as follows:
/// <summary>
/// A custom datasource over the file system that supports data virtualization
/// </summary>
public class FileDataSource : INotifyCollectionChanged, System.Collections.IList, IItemsRangeInfo
{
...
}
In my code, I have created the PicturesCollection as a property:
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;
}
}
}
and bound it to my GridView:
<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>
This gives me a blank page, but if I set it in code-behind, it works. Can anyone tell me why this is so? What am I missing?
To use x:Bind in UWP you should define an object of the ViewModel in XAML like this:
And refer to it like this:
x:Bind can only bind from a ViewModel object that is predefined in XAML.
The problem here is that when your page loaded, your
PicturesCollection
property is not set, so yourPicturesGrid
'sItemsSource
isnull
and you can sell noting is you page.In the constructor of your
MainPage
, you are usinginitdata
method to get all data. However this method isasync void
and you didn't wait for its finishing. Actually, we also can't useawait
in constructor. So when your page loaded, the execution ofawait FileDataSource.GetDataSoure(path);
may be not finished, youPicturesCollection
property is stillnull
here, but yourPicturesGrid
'sItemsSource
has bound to yourPicturesCollection
property. Thus, theItemsSource
is null and you can see nothing. Although yourPicturesCollection
property will be set to the real data later, but you didn't implement property-change notification for yourPicturesCollection
property. And forx:Bind
the defaultMode
isOneTime
, so yourPicturesGrid
'sItemsSource
will always benull
.To fix this issue, you can implement property-change notification for
PicturesCollection
property like following:And in XAML, set the
Mode
ofx:Bind
toOneWay
like:After this, your
x:Bind
will work.Updata:
If you only need one-time bindings for asynchronously-loaded data, you can force one-time bindings to be initialized by calling
this.Bindings.Update();
after you've loaded data like following:It’s much cheaper to initialize them this way than it is to have one-way bindings and to listen for changes as it only need to add one method in your code. However, this may be not a good practice when using MVVM. For more info, please see If your data loads asynchronously in Binding object declared using {x:Bind}