template item c# gridview mvvm data-binding win-universal-app

c# - item - uwp datatemplate



Problemas de UWP que configuran los elementos de GridView fuente utilizando x: Bind (2)

Para usar x: Bind en UWP debe definir un objeto de ViewModel en XAML de esta manera:

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

Y refiérete a esto así:

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

x: Bind solo se puede enlazar desde un objeto ViewModel predefinido en XAML.

Intento poblar mi vista de cuadrícula con Fotos de la biblioteca de Imágenes utilizando la virtualización de datos y el enlace compilado.

Tomé el UWP de Microsoft ( Muestra de virtualización de datos ) y utilicé su FileSource como base, lo modifiqué para usar mi propio objeto Picture e intenté aplicarlo a mi aplicación UWP. Todo lo que obtengo es una página en blanco, y el diseñador está lanzando una excepción.

Quiero usar x: Bind para enlazar a mi objeto fuente de datos en mi modelo ya que estoy usando MVVM y no quiero código subyacente.

No pude hacer que esto funcionara en mi aplicación, así que escribí una pequeña aplicación de prueba que ni siquiera es MVVM y traté de usar x: Bind con mi origen de datos como un objeto en el código subyacente y falla al vincularse al colección también.

Puedo hacer que esto funcione con mi objeto Picture si configuro el origen de la rejilla directamente en mi código subyacente (que es lo que está haciendo la muestra).

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

FileDataSource se define de la siguiente manera:

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

En mi código, he creado PicturesCollection como una propiedad:

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

y enlazarlo a mi 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>

Esto me da una página en blanco, pero si lo configuro en código subyacente, funciona. ¿Alguien puede decirme por qué es así? ¿Qué me estoy perdiendo?


El problema aquí es que cuando su página se carga, su propiedad PicturesCollection no está configurada, por lo que ItemsSource PicturesGrid es null y puede vender notando su página.

En el constructor de su MainPage , está utilizando el método initdata para obtener todos los datos. Sin embargo, este método es un async void y no esperó su finalización. En realidad, tampoco podemos usar await en constructor. Entonces, cuando su página se cargó, la ejecución de await FileDataSource.GetDataSoure(path); puede que no esté terminado, la propiedad PicturesCollection sigue siendo null aquí, pero ItemsSource PicturesGrid ha vinculado a su propiedad PicturesCollection . Por lo tanto, ItemsSource es nulo y no puede ver nada. Aunque su propiedad PicturesCollection se establecerá en los datos reales más adelante, pero no implementó la notificación de cambio de propiedad para su propiedad PicturesCollection . Y para x:Bind el Mode predeterminado es OneTime , por lo que OneTime PicturesGrid siempre será null .

Para solucionar este problema, puede implementar la notificación de cambio de propiedad de la propiedad PicturesCollection siguiente manera:

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

Y en XAML, establece el Mode de x:Bind OneWay a OneWay como:

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

Después de esto, tu x:Bind funcionará.

Updata:

Si solo necesita enlaces de una sola vez para datos cargados de forma asíncrona, puede forzar el this.Bindings.Update(); enlaces de una sola vez llamando a this.Bindings.Update(); después de haber cargado datos como los siguientes:

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

Es mucho más económico inicializarlos de esta manera que tener enlaces unidireccionales y escuchar los cambios, ya que solo se necesita agregar un método en el código. Sin embargo, esto puede no ser una buena práctica al usar MVVM. Para obtener más información, consulte Si sus datos se cargan de forma asíncrona en el objeto Enlace declarado con {x: Bind}

El código para admitir {x: Bind} se genera en tiempo de compilación en las clases parciales para sus páginas. Estos archivos se pueden encontrar en su carpeta obj , con nombres como (para C #) <view name>.g.cs . El código generado incluye un controlador para el evento Cargando de su página, y ese controlador llama al método Inicializar en una clase generada que representa los enlaces de su página. Inicializar a su vez llamadas Actualizar para comenzar a mover datos entre el origen de enlace y el destino. La carga se levanta justo antes del primer paso de medición de la página o el control del usuario. Por lo tanto, si sus datos se cargan de manera asíncrona, es posible que no estén listos cuando se llame a Initialize . Entonces, después de que hayas cargado los datos, puedes forzar que los enlaces únicos se inicialicen llamando a this->Bindings->Update(); . Si solo necesita enlaces de una sola vez para datos cargados de forma asíncrona, entonces es mucho más económico inicializarlos de esta manera que tener enlaces unidireccionales y escuchar cambios. Si sus datos no se someten a cambios detallados, y si es probable que se actualicen como parte de una acción específica, entonces puede hacer sus enlaces una sola vez y forzar una actualización manual en cualquier momento con una llamada a Actualizar .