Привязка наблюдаемой коллекции к GridView

В моем UWP должна быть страница "Избранное", которая позволяет пользователю изменять порядок и сохранять данные на странице. Первоначально мои данные поступают из большого файла JSON, который десериализуется с помощью Newtonsoft Json.net и хранится для этой страницы в словаре, который затем заполняет общедоступную коллекцию 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 File

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

person Haybale100    schedule 30.07.2016    source источник


Ответы (2)


Без хорошего минимального, полного и проверяемого примера кода невозможно точно узнать, что не так. Однако в вашем коде появляется одна вопиющая ошибка:

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, который надежно воспроизводит проблему.

person Peter Duniho    schedule 30.07.2016
comment
Я изменил DataContext на {Binding}, как вы предложили, но это ничего не изменило (если я чего-то не упускаю). Я также не особо хочу реализовать предложенное вами решение PropertyChanged, поскольку я просто не знаю, что оно делает и как работает. Я постараюсь как можно скорее разобраться в этом вопросе - person Haybale100; 30.07.2016
comment
Если вы собираетесь писать программы WPF, вы должны узнать о INotifyPropertyChanged и уметь его реализовать. Без него вы не сможете заставить работать какие-либо нетривиальные привязки. Что касается текущего вопроса, я могу дать совет только на основе того, что вы публикуете в своем вопросе. Если изменение привязки на ItemsSource="{Binding}" не устранило проблему, значит, происходит что-то еще, чего не видно в опубликованном вами коде. Исправив вопрос, включив в него хороший минимальный воспроизводимый пример, вы гарантированно получите ответ, который обязательно сработает. - person Peter Duniho; 30.07.2016

Я понял. моя проблема заключалась в том, как я пытался передать данные на саму страницу. Вместо того, чтобы использовать DataContext = Items; и пытаться получить доступ к данным таким образом. Вместо этого я установил прямой ItemsSource для GridView.

Конечным результатом было просто изменение DataContext = Items на itemGridView.ItemsSource = Items;.

person Haybale100    schedule 01.08.2016