TheMoonbeam
TheMoonbeam

Reputation: 519

UWP C#/XAML: System.ArgumentException (Out of expected range)

I'm working on a Universal Windows Platform (UWP) app, and have run into a perplexing (And slightly annoying issue). When I navigate from the MainPage to EventDetailPage, a System.ArgumentException. It's indicating that the data bound object

"Value does not fall within the expected range".

Value is an ScoutingEvent object passed from MainPageViewModel to EventDetailPageViewModel. Although the passed object is sometimes null (not sure why), Value is also assigned default data to ensure it's not null when EventDetailPage attempts to bind to Bind to value.

From my perspective, everything looks correct. Is there something I'm missing here?

MainPage.xaml

<Page x:Class="ScoutsLog.Views.MainPage"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:Behaviors="using:Template10.Behaviors"
  xmlns:Core="using:Microsoft.Xaml.Interactions.Core"
  xmlns:Interactivity="using:Microsoft.Xaml.Interactivity"
  xmlns:controls="using:Template10.Controls"
  xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  xmlns:local="using:ScoutsLog.Views"
  xmlns:data="using:ScoutsLog.Models"
  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  xmlns:vm="using:ScoutsLog.ViewModels" mc:Ignorable="d">

    <Page.DataContext>
        <vm:MainPageViewModel />
    </Page.DataContext>

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

        <!--  #region default visual states  -->

        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="AdaptiveVisualStateGroup">
                <VisualState x:Name="VisualStateNarrow">
                    <VisualState.StateTriggers>
                        <AdaptiveTrigger MinWindowWidth="{StaticResource NarrowMinWidth}" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <!--  TODO  -->
                    </VisualState.Setters>
                </VisualState>
                <VisualState x:Name="VisualStateNormal">
                    <VisualState.StateTriggers>
                        <AdaptiveTrigger MinWindowWidth="{StaticResource NormalMinWidth}" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <!--  TODO  -->
                    </VisualState.Setters>
                </VisualState>
                <VisualState x:Name="VisualStateWide">
                    <VisualState.StateTriggers>
                        <AdaptiveTrigger MinWindowWidth="{StaticResource WideMinWidth}" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <!--  TODO  -->
                    </VisualState.Setters>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>

        <!--  #endregion  -->

        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition />
        </Grid.RowDefinitions>

        <!--  page header  -->

        <controls:PageHeader BackButtonVisibility="Collapsed" Content="Main Page" Frame="{x:Bind Frame}">
            <Interactivity:Interaction.Behaviors>
                <Behaviors:EllipsisBehavior Visibility="Auto" />
            </Interactivity:Interaction.Behaviors>
            <controls:PageHeader.SecondaryCommands>
                <AppBarButton Click="{x:Bind ViewModel.GotoPrivacy}" Label="Privacy" />
                <AppBarButton Click="{x:Bind ViewModel.GotoAbout}" Label="About" />
            </controls:PageHeader.SecondaryCommands>
        </controls:PageHeader>

        <!--  page content  -->

        <!--<StackPanel Grid.Row="1" VerticalAlignment="Top"
                    Orientation="Horizontal" Padding="12,8,0,0">

            <controls:Resizer>
                <TextBox Width="200" MinWidth="200"
                         MinHeight="60" Margin="0"
                         Header="Parameter to pass"
                         Text="{Binding Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                         TextWrapping="Wrap">
                    <Interactivity:Interaction.Behaviors>
                        <Behaviors:TextBoxEnterKeyBehavior>
                            <Core:CallMethodAction MethodName="GotoDetailsPage" TargetObject="{Binding}" />
                        </Behaviors:TextBoxEnterKeyBehavior>
                        <Core:EventTriggerBehavior>
                            <Behaviors:FocusAction/>
                        </Core:EventTriggerBehavior>
                    </Interactivity:Interaction.Behaviors>
                </TextBox>
            </controls:Resizer>

            <Button Margin="12,0" VerticalAlignment="Bottom"
                    Click="{x:Bind ViewModel.GotoDetailsPage}" Content="Submit" />

        </StackPanel>-->

        <ListBox Grid.Row="1" VerticalAlignment="Top" Padding="12,8,0,0" Name="EventsOverviewListBox" 
                 ItemsSource="{x:Bind ViewModel.Events}" SelectionChanged="{x:Bind ViewModel.GotoEventDetailsPage}"
                 SelectedIndex="{Binding EventsIndex, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
            <ListBox.ItemTemplate>
                <DataTemplate x:DataType="data:ScoutingEvent">
                    <TextBlock Text="{x:Bind EventName}"/>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</Page>

MainPageViewModel.cs

using ScoutsLog.Models;
using ScoutsLog.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Windows.Storage;
using Windows.UI.Core;
using Windows.UI.Xaml.Navigation;

namespace ScoutsLog.ViewModels
{
    public class MainPageViewModel : Mvvm.ViewModelBase
    {
        Windows.Storage.StorageFolder localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
        public List<ScoutingEvent> Events;

        public MainPageViewModel()
        {
            //if (Windows.ApplicationModel.DesignMode.DesignModeEnabled)
            //    Value = "Designtime value";

            // Register a handler for BackRequested events and set the
            // visibility of the Back button
            SystemNavigationManager.GetForCurrentView().BackRequested += OnBackRequested;

            ReadDataFromStorage();

            //if (Events == null || Events.Count <= 0)
            //{
                Events = EventsManager.GetEvents();
            //}

        }

        private async void ReadDataFromStorage()
        {
            try
            {
                StorageFile sampleFile = await localFolder.GetFileAsync("dataFile.txt");
                String listAsXml = await FileIO.ReadTextAsync(sampleFile);
                Events = XmlHandler.DeserializeXmlToList(listAsXml);
            }
            catch(Exception e)
            {
                System.Diagnostics.Debug.WriteLine(e);
            }
        }

        private async void OnBackRequested(object sender, BackRequestedEventArgs e)
        {
            StorageFile sampleFile = await localFolder.CreateFileAsync("dataFile.txt",
            CreationCollisionOption.ReplaceExisting);
            await FileIO.WriteTextAsync(sampleFile, XmlHandler.SerializeListToXml(Events));

        }

        ScoutingEvent _Value = new ScoutingEvent();
        public ScoutingEvent Value { get { return _Value; } set { Set(ref _Value, value); } }

        int _EventsIndex = 0;
        public int EventsIndex { get { return _EventsIndex; } set { Set(ref _EventsIndex, value); } }

        public override void OnNavigatedTo(object parameter, NavigationMode mode, IDictionary<string, object> state)
        {
            if (state.ContainsKey(nameof(Value)))
                Value = (ScoutingEvent)state[nameof(Value)];
            state.Clear();
        }

        public override async Task OnNavigatedFromAsync(IDictionary<string, object> state, bool suspending)
        {
            if (suspending)
                state[nameof(Value)] = Value;
            await Task.Yield();
        }

        public void GotoEventDetailsPage()
        {
            Value = Events.ElementAt(EventsIndex);
            NavigationService.Navigate(typeof(Views.EventDetailPage), Value);
        }

        public void GotoPrivacy()
        {
            NavigationService.Navigate(typeof(Views.SettingsPage), 1);
        }

        public void GotoAbout()
        {
            NavigationService.Navigate(typeof(Views.SettingsPage), 2);
        }

    }
}

EventDetailPage.xaml

<Page
x:Class="ScoutsLog.Views.EventDetailPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Behaviors="using:Template10.Behaviors"
xmlns:Core="using:Microsoft.Xaml.Interactions.Core"
xmlns:Interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:controls="using:Template10.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:ScoutsLog.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:ScoutsLog.ViewModels" x:Name="ThisPage"
xmlns:data="using:ScoutsLog.Models"
mc:Ignorable="d">

    <Page.DataContext>
        <vm:EventDetailsPageViewModel />
    </Page.DataContext>

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

        <!--  adaptive states  -->

        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="AdaptiveVisualStateGroup">
                <VisualState x:Name="VisualStateNarrow">
                    <VisualState.StateTriggers>
                        <AdaptiveTrigger MinWindowWidth="{StaticResource NarrowMinWidth}" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <!--  TODO  -->
                    </VisualState.Setters>
                </VisualState>
                <VisualState x:Name="VisualStateNormal">
                    <VisualState.StateTriggers>
                        <AdaptiveTrigger MinWindowWidth="{StaticResource NormalMinWidth}" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <!--  TODO  -->
                    </VisualState.Setters>
                </VisualState>
                <VisualState x:Name="VisualStateWide">
                    <VisualState.StateTriggers>
                        <AdaptiveTrigger MinWindowWidth="{StaticResource WideMinWidth}" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <!--  TODO  -->
                    </VisualState.Setters>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>

        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition />
        </Grid.RowDefinitions>

        <!--  header  -->
        <controls:PageHeader Frame="{x:Bind Frame}" Text="Detail Page">
            <Interactivity:Interaction.Behaviors>
                <Behaviors:EllipsisBehavior Visibility="Auto" />
            </Interactivity:Interaction.Behaviors>
        </controls:PageHeader>

        <GridView Grid.Row="1" VerticalAlignment="Top" Padding="12,8,0,0" Name="EventsOverviewGridView" ItemsSource="{x:Bind ViewModel.Value}">
            <GridView.ItemTemplate>
                <DataTemplate x:DataType="data:ScoutingEvent">
                    <Grid>
                        <StackPanel Grid.Row="0">
                            <TextBlock Text="{x:Bind EventName}" />
                            <ListBox ItemsSource="{x:Bind Companies}">
                                <ListBox.ItemTemplate>
                                    <DataTemplate x:DataType="data:Company">
                                        <TextBlock Text="{x:Bind CompanyName}"/>
                                    </DataTemplate>
                                </ListBox.ItemTemplate>
                            </ListBox>
                        </StackPanel>
                    </Grid>
                </DataTemplate>
            </GridView.ItemTemplate>
        </GridView>

        <!--  #endregion  -->

    </Grid>
</Page>

EventDetailPageViewModel.cs

using ScoutsLog.Models;
using System.Linq;
using System.Collections.Generic;
using System.Threading.Tasks;
using Windows.UI.Xaml.Navigation;

namespace ScoutsLog.ViewModels
{
    public class EventDetailsPageViewModel : Mvvm.ViewModelBase
    {
        public EventDetailsPageViewModel()
        {

        }

        ScoutingEvent _Value = new ScoutingEvent { EventName = string.Empty, EventDate = string.Empty, Companies = new List<Company>()};
        public ScoutingEvent Value { get { return _Value; } set { Set(ref _Value, value); } }

        public override void OnNavigatedTo(object parameter, NavigationMode mode, IDictionary<string, object> state)
        {
            if (state.ContainsKey(nameof(Value)))
                Value = (ScoutingEvent)state[nameof(Value)];
            state.Clear();
        }

        public override async Task OnNavigatedFromAsync(IDictionary<string, object> state, bool suspending)
        {
            if (suspending)
                state[nameof(Value)] = Value;
            await Task.Yield();
        }

        public void GotoDetailsPage()
        {
            NavigationService.Navigate(typeof(Views.EventDetailPage), Value);
        }

        public void GotoPrivacy()
        {
            NavigationService.Navigate(typeof(Views.SettingsPage), 1);
        }

        public void GotoAbout()
        {
            NavigationService.Navigate(typeof(Views.SettingsPage), 2);
        }
    }
}

Update 1: Adding ScoutingEvent.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;

namespace ScoutsLog.Models
{
    public class ScoutingEvent : INotifyPropertyChanged
    {
        private string eventName;
         public string EventName
         {
            get
            {
                return eventName;
            }
               set
            {
                if (eventName != value)
                {
                    eventName = value;
                    OnNotifyPropertyChanged("EventName");
                }
            }
        }

    private string eventDate;
    public string EventDate
    {
        get
        {
            return eventDate;
        }
        set
        {
            if (eventDate != value)
            {
                eventDate = value;
                OnNotifyPropertyChanged("EventDate");
            }
        }
    }

    public List<Company> Companies { get; set; }

    public event PropertyChangedEventHandler PropertyChanged;

    private void OnNotifyPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

public class Company
{
    public string CompanyName { get; set; }
    public string Category { get; set; }
    public Boolean isRelevent { get; set; }
    public String DateVisited { get; set; }
    public string Notes { get; set; }
}

public class EventsManager
{
    public static List<ScoutingEvent> GetEvents()
    {
        var events = new List<ScoutingEvent>();
        var companies = GetCompanies();

        events.Add(new ScoutingEvent { EventName = "CES", EventDate = "1/6/2016 - 1/9/2016", Companies = companies});
        events.Add(new ScoutingEvent { EventName = "MWC", EventDate = "3/3/2016 - 3/10/2016", Companies = companies });

        return events;
    }

    public static List<Company> GetCompanies()
    {
        var companies = new List<Company>();
        companies.Add(new Company { CompanyName = "Spire", Category = "Fittness", DateVisited = "1/6/2016", isRelevent = true, Notes = "Follow-up after event." });
        companies.Add(new Company { CompanyName = "Samsung", Category = "Fittness", DateVisited = "1/8/2016", isRelevent = false, Notes = "Not working on relevent technologies." });
        companies.Add(new Company { CompanyName = "Fitbit", Category = "Fittness", DateVisited = "1/7/2016", isRelevent = true, Notes = "Pass along to remote team." });
        companies.Add(new Company { CompanyName = "Home.io", Category = "Fittness", DateVisited = "1/9/2016", isRelevent = true, Notes = "SHare with Paul/Martta." });
        return companies;
    }
}
}

Upvotes: 3

Views: 2106

Answers (1)

Bart
Bart

Reputation: 10015

The problem is in your EventDetailPage where you try to assign the ItemsSource of your GridView to a single ScoutingEvent instead of a collection.

    <GridView Grid.Row="1" VerticalAlignment="Top" Padding="12,8,0,0"
              Name="EventsOverviewGridView" ItemsSource="{x:Bind ViewModel.Value}">
        <GridView.ItemTemplate>
            <DataTemplate x:DataType="data:ScoutingEvent">
                <Grid>
                    <StackPanel Grid.Row="0">
                        <TextBlock Text="{x:Bind EventName}" />
                        <ListBox ItemsSource="{x:Bind Companies}">
                            <ListBox.ItemTemplate>
                                <DataTemplate x:DataType="data:Company">
                                    <TextBlock Text="{x:Bind CompanyName}"/>
                                </DataTemplate>
                            </ListBox.ItemTemplate>
                        </ListBox>
                    </StackPanel>
                </Grid>
            </DataTemplate>
        </GridView.ItemTemplate>
    </GridView>

As it's a single item you can either use a ContentPresenter with ContentTemplate, or just keep it easy and only keep the inner controls of your template. Replace the GridView above by following XAML:

<StackPanel Grid.Row="1" VerticalAlignment="Top" Padding="12,8,0,0">
    <TextBlock Text="{x:Bind ViewModel.Value.EventName}" />
    <ListBox ItemsSource="{x:Bind ViewModel.Value.Companies}">
        <ListBox.ItemTemplate>
            <DataTemplate x:DataType="viewModels:Company">
                <TextBlock Text="{x:Bind CompanyName}"/>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
</StackPanel>

Upvotes: 2

Related Questions