将 Observable Collection 绑定到 GridView
Binding an Observable Collection to a GridView
我的 UWP 需要有一个收藏夹页面,允许用户重新排序和保存页面上的数据。最初我的数据来自一个大的 JSON 文件,该文件使用 Newtonsoft 的 Json.net 反序列化,并为此页面存储在字典中,然后填充 public ObservableCollection。
这是我现在迷路的地方,将 ObservableCollection 设置为 DataContext,然后在 XAML 代码中使用数据作为绑定,以使用每个项目的所有标题、副标题和图像填充 GridView需要。
理论上这应该可行,但在我的试验和测试中,页面仍然空白,而幕后的所有 C# 代码让它看起来应该被填充。
不知道为什么页面不够,求助大家。
P.S:我真的不关心这段代码的整洁度,我只是想让它工作。
XAML 文件
<Page
x:Name="pageRoot"
x:Class="Melbourne_Getaway.FavouritesPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Melbourne_Getaway"
xmlns:data="using:Melbourne_Getaway.Data"
xmlns:common="using:Melbourne_Getaway.Common"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Page.Resources>
<x:String x:Key="AppName">Favourites</x:String>
</Page.Resources>
<!--
This grid acts as a root panel for the page that defines two rows:
* Row 0 contains the back button and page title
* Row 1 contains the rest of the page layout
-->
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.ChildrenTransitions>
<TransitionCollection>
<EntranceThemeTransition />
</TransitionCollection>
</Grid.ChildrenTransitions>
<Grid.RowDefinitions>
<RowDefinition Height="140" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<GridView
x:Name="itemGridView"
AutomationProperties.AutomationId="ItemsGridView"
AutomationProperties.Name="Items"
TabIndex="1"
Grid.RowSpan="2"
Padding="60,136,116,46"
SelectionMode="None"
IsSwipeEnabled="false"
CanReorderItems="True"
CanDragItems="True"
AllowDrop="True"
ItemsSource="{Binding Items}">
<GridView.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Left" Width="250" Height="107">
<Border Background="{ThemeResource ListViewItemPlaceholderBackgroundThemeBrush}">
<Image Source="{Binding ImagePath}" Stretch="UniformToFill" AutomationProperties.Name="{Binding Title}" />
</Border>
<StackPanel VerticalAlignment="Bottom" Background="{ThemeResource ListViewItemOverlayBackgroundThemeBrush}">
<TextBlock Text="{Binding Title}" Foreground="{ThemeResource ListViewItemOverlayForegroundThemeBrush}" Style="{StaticResource BaseTextBlockStyle}" Height="30" Margin="15,0,15,0" FontWeight="SemiBold" />
<TextBlock Text="{Binding Group}" Foreground="{ThemeResource ListViewItemOverlaySecondaryForegroundThemeBrush}" Style="{StaticResource BaseTextBlockStyle}" TextWrapping="NoWrap" Margin="15,-15,15,10" FontSize="12" />
</StackPanel>
</Grid>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
<!-- Back button and page title -->
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button x:Name="backButton" Margin="39,59,39,0" Command="{Binding NavigationHelper.GoBackCommand, ElementName=pageRoot}"
Style="{StaticResource NavigationBackButtonNormalStyle}"
VerticalAlignment="Top"
AutomationProperties.Name="Back"
AutomationProperties.AutomationId="BackButton"
AutomationProperties.ItemType="Navigation Button" />
<TextBlock x:Name="pageTitle" Text="{StaticResource AppName}" Style="{StaticResource HeaderTextBlockStyle}" Grid.Column="1"
IsHitTestVisible="false" TextWrapping="NoWrap" VerticalAlignment="Bottom" Margin="0,0,30,40" />
</Grid>
</Grid>
CS 文件
using Melbourne_Getaway.Common;
using Melbourne_Getaway.Data;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Windows.Storage;
using Windows.UI.Popups;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
namespace Melbourne_Getaway
{
public sealed partial class FavouritesPage : Page
{
public ObservableCollection<ItemData> Items { get; set; }
private ObservableDictionary defaultViewModel = new ObservableDictionary();
private NavigationHelper navigationHelper;
private RootObject jsonLines;
private StorageFile fileFavourites;
private Dictionary<string, ItemData> ItemData = new Dictionary<string, ItemData>();
public FavouritesPage()
{
loadJson();
getFavFile();
this.InitializeComponent();
this.navigationHelper = new NavigationHelper(this);
this.navigationHelper.LoadState += navigationHelper_LoadState;
}
private void setupObservableCollection()
{
Items = new ObservableCollection<ItemData>(ItemData.Values);
DataContext = Items;
}
private async void loadJson()
{
var file = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///DataModel/SampleData.json"));
string lines = await FileIO.ReadTextAsync(file);
jsonLines = JsonConvert.DeserializeObject<RootObject>(lines);
feedItems();
}
private async void getFavFile()
{
Windows.Storage.StorageFolder storageFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
fileFavourites = await storageFolder.GetFileAsync("MelbGetaway.fav");
}
private async void feedItems()
{
if (await FileIO.ReadTextAsync(fileFavourites) != "")
{
foreach (var line in await FileIO.ReadLinesAsync(fileFavourites))
{
foreach (var Group in jsonLines.Groups)
{
foreach (var Item in Group.Items)
{
if (Item.UniqueId == line)
{
var storage = new ItemData()
{
Title = Item.Title,
UniqueID = Item.UniqueId,
ImagePath = Item.ImagePath,
Group = Group.Title
};
ItemData.Add(storage.UniqueID, storage);
}
}
}
}
}
else
{//should only execute if favourites file is empty, first time use?
foreach (var Group in jsonLines.Groups)
{
foreach (var Item in Group.Items)
{
var storage = new ItemData()
{
Title = Item.Title,
UniqueID = Item.UniqueId,
ImagePath = Item.ImagePath,
Group = Group.Title
};
ItemData.Add(storage.UniqueID, storage);
await FileIO.AppendTextAsync(fileFavourites, Item.UniqueId + "\r\n");
}
}
}
setupObservableCollection();
}
public ObservableDictionary DefaultViewModel
{
get { return this.defaultViewModel; }
}
#region NavigationHelper loader
public NavigationHelper NavigationHelper
{
get { return this.navigationHelper; }
}
private async void MessageBox(string Message)
{
MessageDialog dialog = new MessageDialog(Message);
await dialog.ShowAsync();
}
private async void navigationHelper_LoadState(object sender, LoadStateEventArgs e)
{
var sampleDataGroups = await SampleDataSource.GetGroupsAsync();
this.defaultViewModel["Groups"] = sampleDataGroups;
}
#endregion NavigationHelper loader
#region NavigationHelper registration
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
navigationHelper.OnNavigatedFrom(e);
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
navigationHelper.OnNavigatedTo(e);
}
#endregion NavigationHelper registration
}
public class ItemData
{
public string UniqueID { get; set; }
public string Title { get; set; }
public string Group { get; set; }
public string ImagePath { get; set; }
}
}
没有好的 Minimal, Complete, and Verifiable code example 就不可能确切地知道哪里出了问题。但是,您的代码中确实出现了一个明显的错误:
private void setupObservableCollection()
{
Items = new ObservableCollection<ItemData>(ItemData.Values);
DataContext = Items;
}
在您的 XAML 中,您绑定到 {Binding Items}
。 DataContext
设置为 Items
属性 值,正确的绑定实际上只是 {Binding}
.
或者,如果您想保持 XAML 不变,则必须改为设置 DataContext = this;
。当然,如果您那样做,那么您将 运行 陷入您似乎没有提出 INotifyPropertyChanged.PropertyChanged
甚至没有实现该接口的问题。如果您确定 属性 将在 InitializeComponent()
方法被调用之前设置,您可以摆脱它,但在您显示的代码中似乎并非如此。
因此,如果您想将绑定设置为 {Binding Items}
,您还需要实施 INotifyPropertyChanged
并确保使用 属性 名称引发 PropertyChanged
事件 "Items"
当你实际设置 属性.
如果以上内容未能解决您的问题,请提供可可靠重现问题的良好 MCVE 来改进问题。
我明白了。我的问题在于我试图将数据传递给页面本身的方式。而不是使用 DataContext = Items;
并尝试以这种方式访问数据。我改为为 GridView
.
设置直接 ItemsSource
最终结果只是将 DataContext = Items
更改为 itemGridView.ItemsSource = Items;
我的 UWP 需要有一个收藏夹页面,允许用户重新排序和保存页面上的数据。最初我的数据来自一个大的 JSON 文件,该文件使用 Newtonsoft 的 Json.net 反序列化,并为此页面存储在字典中,然后填充 public ObservableCollection。
这是我现在迷路的地方,将 ObservableCollection 设置为 DataContext,然后在 XAML 代码中使用数据作为绑定,以使用每个项目的所有标题、副标题和图像填充 GridView需要。 理论上这应该可行,但在我的试验和测试中,页面仍然空白,而幕后的所有 C# 代码让它看起来应该被填充。
不知道为什么页面不够,求助大家。
P.S:我真的不关心这段代码的整洁度,我只是想让它工作。
XAML 文件
<Page
x:Name="pageRoot"
x:Class="Melbourne_Getaway.FavouritesPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Melbourne_Getaway"
xmlns:data="using:Melbourne_Getaway.Data"
xmlns:common="using:Melbourne_Getaway.Common"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Page.Resources>
<x:String x:Key="AppName">Favourites</x:String>
</Page.Resources>
<!--
This grid acts as a root panel for the page that defines two rows:
* Row 0 contains the back button and page title
* Row 1 contains the rest of the page layout
-->
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.ChildrenTransitions>
<TransitionCollection>
<EntranceThemeTransition />
</TransitionCollection>
</Grid.ChildrenTransitions>
<Grid.RowDefinitions>
<RowDefinition Height="140" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<GridView
x:Name="itemGridView"
AutomationProperties.AutomationId="ItemsGridView"
AutomationProperties.Name="Items"
TabIndex="1"
Grid.RowSpan="2"
Padding="60,136,116,46"
SelectionMode="None"
IsSwipeEnabled="false"
CanReorderItems="True"
CanDragItems="True"
AllowDrop="True"
ItemsSource="{Binding Items}">
<GridView.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Left" Width="250" Height="107">
<Border Background="{ThemeResource ListViewItemPlaceholderBackgroundThemeBrush}">
<Image Source="{Binding ImagePath}" Stretch="UniformToFill" AutomationProperties.Name="{Binding Title}" />
</Border>
<StackPanel VerticalAlignment="Bottom" Background="{ThemeResource ListViewItemOverlayBackgroundThemeBrush}">
<TextBlock Text="{Binding Title}" Foreground="{ThemeResource ListViewItemOverlayForegroundThemeBrush}" Style="{StaticResource BaseTextBlockStyle}" Height="30" Margin="15,0,15,0" FontWeight="SemiBold" />
<TextBlock Text="{Binding Group}" Foreground="{ThemeResource ListViewItemOverlaySecondaryForegroundThemeBrush}" Style="{StaticResource BaseTextBlockStyle}" TextWrapping="NoWrap" Margin="15,-15,15,10" FontSize="12" />
</StackPanel>
</Grid>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
<!-- Back button and page title -->
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button x:Name="backButton" Margin="39,59,39,0" Command="{Binding NavigationHelper.GoBackCommand, ElementName=pageRoot}"
Style="{StaticResource NavigationBackButtonNormalStyle}"
VerticalAlignment="Top"
AutomationProperties.Name="Back"
AutomationProperties.AutomationId="BackButton"
AutomationProperties.ItemType="Navigation Button" />
<TextBlock x:Name="pageTitle" Text="{StaticResource AppName}" Style="{StaticResource HeaderTextBlockStyle}" Grid.Column="1"
IsHitTestVisible="false" TextWrapping="NoWrap" VerticalAlignment="Bottom" Margin="0,0,30,40" />
</Grid>
</Grid>
CS 文件
using Melbourne_Getaway.Common;
using Melbourne_Getaway.Data;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Windows.Storage;
using Windows.UI.Popups;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
namespace Melbourne_Getaway
{
public sealed partial class FavouritesPage : Page
{
public ObservableCollection<ItemData> Items { get; set; }
private ObservableDictionary defaultViewModel = new ObservableDictionary();
private NavigationHelper navigationHelper;
private RootObject jsonLines;
private StorageFile fileFavourites;
private Dictionary<string, ItemData> ItemData = new Dictionary<string, ItemData>();
public FavouritesPage()
{
loadJson();
getFavFile();
this.InitializeComponent();
this.navigationHelper = new NavigationHelper(this);
this.navigationHelper.LoadState += navigationHelper_LoadState;
}
private void setupObservableCollection()
{
Items = new ObservableCollection<ItemData>(ItemData.Values);
DataContext = Items;
}
private async void loadJson()
{
var file = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///DataModel/SampleData.json"));
string lines = await FileIO.ReadTextAsync(file);
jsonLines = JsonConvert.DeserializeObject<RootObject>(lines);
feedItems();
}
private async void getFavFile()
{
Windows.Storage.StorageFolder storageFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
fileFavourites = await storageFolder.GetFileAsync("MelbGetaway.fav");
}
private async void feedItems()
{
if (await FileIO.ReadTextAsync(fileFavourites) != "")
{
foreach (var line in await FileIO.ReadLinesAsync(fileFavourites))
{
foreach (var Group in jsonLines.Groups)
{
foreach (var Item in Group.Items)
{
if (Item.UniqueId == line)
{
var storage = new ItemData()
{
Title = Item.Title,
UniqueID = Item.UniqueId,
ImagePath = Item.ImagePath,
Group = Group.Title
};
ItemData.Add(storage.UniqueID, storage);
}
}
}
}
}
else
{//should only execute if favourites file is empty, first time use?
foreach (var Group in jsonLines.Groups)
{
foreach (var Item in Group.Items)
{
var storage = new ItemData()
{
Title = Item.Title,
UniqueID = Item.UniqueId,
ImagePath = Item.ImagePath,
Group = Group.Title
};
ItemData.Add(storage.UniqueID, storage);
await FileIO.AppendTextAsync(fileFavourites, Item.UniqueId + "\r\n");
}
}
}
setupObservableCollection();
}
public ObservableDictionary DefaultViewModel
{
get { return this.defaultViewModel; }
}
#region NavigationHelper loader
public NavigationHelper NavigationHelper
{
get { return this.navigationHelper; }
}
private async void MessageBox(string Message)
{
MessageDialog dialog = new MessageDialog(Message);
await dialog.ShowAsync();
}
private async void navigationHelper_LoadState(object sender, LoadStateEventArgs e)
{
var sampleDataGroups = await SampleDataSource.GetGroupsAsync();
this.defaultViewModel["Groups"] = sampleDataGroups;
}
#endregion NavigationHelper loader
#region NavigationHelper registration
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
navigationHelper.OnNavigatedFrom(e);
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
navigationHelper.OnNavigatedTo(e);
}
#endregion NavigationHelper registration
}
public class ItemData
{
public string UniqueID { get; set; }
public string Title { get; set; }
public string Group { get; set; }
public string ImagePath { get; set; }
}
}
没有好的 Minimal, Complete, and Verifiable code example 就不可能确切地知道哪里出了问题。但是,您的代码中确实出现了一个明显的错误:
private void setupObservableCollection()
{
Items = new ObservableCollection<ItemData>(ItemData.Values);
DataContext = Items;
}
在您的 XAML 中,您绑定到 {Binding Items}
。 DataContext
设置为 Items
属性 值,正确的绑定实际上只是 {Binding}
.
或者,如果您想保持 XAML 不变,则必须改为设置 DataContext = this;
。当然,如果您那样做,那么您将 运行 陷入您似乎没有提出 INotifyPropertyChanged.PropertyChanged
甚至没有实现该接口的问题。如果您确定 属性 将在 InitializeComponent()
方法被调用之前设置,您可以摆脱它,但在您显示的代码中似乎并非如此。
因此,如果您想将绑定设置为 {Binding Items}
,您还需要实施 INotifyPropertyChanged
并确保使用 属性 名称引发 PropertyChanged
事件 "Items"
当你实际设置 属性.
如果以上内容未能解决您的问题,请提供可可靠重现问题的良好 MCVE 来改进问题。
我明白了。我的问题在于我试图将数据传递给页面本身的方式。而不是使用 DataContext = Items;
并尝试以这种方式访问数据。我改为为 GridView
.
ItemsSource
最终结果只是将 DataContext = Items
更改为 itemGridView.ItemsSource = Items;