How to pass TimeSpan data using query property attributes from one XAML Page to the other XAML Page?

113 Views Asked by At

Maybe to some of you it might be elemental issue, but I am really at loss to resolve System.InvalidCastException: 'Invalid cast from 'System.String' to 'System.TimeSpan'.

I have two XAML pages, and I need to pass TimeSpan parameter from one page to the other (so that I can use TimePicker on the other page).

The first xaml page:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="TestApp2.Views.DateTimePage"
              xmlns:viewmodels="clr-namespace:TestApp2.ViewModels" 
                    xmlns:local="clr-namespace:TestApp2.ViewModels"  
                    xmlns:model="clr-namespace:TestApp2.Models" 
             x:DataType="viewmodels:DateTimePageViewModel"
             Title="DateTimePage"
             >
        <ContentPage.Content>
        <StackLayout>
            <Label Text="Tap">
                <Label.GestureRecognizers>
                    <TapGestureRecognizer
                        NumberOfTapsRequired="2"
                        Command="{Binding Source={RelativeSource AncestorType={x:Type local:DateTimePageViewModel}}, Path=TimePickerTapped}"        
                        CommandParameter="{Binding .}">
                    </TapGestureRecognizer>
                </Label.GestureRecognizers>
            
        </Label>

            <TimePicker x:Name="timeFromTimePicker" Time="{Binding Time, Mode=TwoWay}" />

        </StackLayout>
    </ContentPage.Content>
</ContentPage>

and

namespace TestApp2.Views
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class DateTimePage : ContentPage
    {
        public DateTimePage()
        {
            InitializeComponent();
            BindingContext = new DateTimePageViewModel();
        }
    }
}

ViewModel behind the first page:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Text;
using TestApp2.Views;
using Xamarin.Forms;

namespace TestApp2.ViewModels
{
    public class DateTimePageViewModel: BaseViewModel, INotifyPropertyChanged
    {
        public Command TimePickerTapped { get; }
        

        private TimeSpan time;
        public TimeSpan Time
        {
            get => time;
            
            set
            {
                time = value;
                OnPropertyChanged();
            }
        }

        public DateTimePageViewModel()
        {
            TimePickerTapped = new Command(OnTimePickerSelected);
            time = new TimeSpan(17, 0, 0);
        }

        async void OnTimePickerSelected()
        {
            if (time != null)
            {
                await Shell.Current.GoToAsync($"{nameof(TransferDateTimePage)}?" +
                    $"{nameof(TransferDateTimePageViewModel.Time)}={Time}");
            }
        }
    }
}

The second XAML page:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="TestApp2.Views.TransferDateTimePage"
                    xmlns:viewmodels="clr-namespace:TestApp2.ViewModels" 
                    xmlns:local="clr-namespace:TestApp2.ViewModels"  
                    xmlns:model="clr-namespace:TestApp2.Models" 
             x:DataType="viewmodels:TransferDateTimePageViewModel"
             >
        <ContentPage.Content>
        <StackLayout>
            <TimePicker x:Name="timeFromTimePicker" Time="{Binding Time, Mode=TwoWay}" />
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

and

namespace TestApp2.Views
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class TransferDateTimePage : ContentPage
    {
        public TransferDateTimePage()
        {
            InitializeComponent();
            BindingContext = new TransferDateTimePageViewModel();
        }
    }
}

and View Model behind second page is:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using Xamarin.Forms;

namespace TestApp2.ViewModels
{

        [QueryProperty(nameof(Time), nameof(Time))]
        public class TransferDateTimePageViewModel : BaseViewModel, INotifyPropertyChanged      //, IQueryAttributable
        {
            private TimeSpan time;
            public TimeSpan Time
            {
                get => time;
                set
                {
                    time = value;
                    OnPropertyChanged();
                }
            }

            public TransferDateTimePageViewModel()
            {

            }
       }
   }

I think that error somehow relates to

            await Shell.Current.GoToAsync($"{nameof(TransferDateTimePage)}?" +
                $"{nameof(TransferDateTimePageViewModel.Time)}={Time}"); 

ViewModel behind first page code 

and passing parameter to second page:

[QueryProperty(nameof(Time), nameof(Time))]

I really do not know how to resolve this. I tried to search Internet extensively for several days, but I was unable to resolve this so far.

I need the user to be able to edit on second page the time appearing on first .

Any help? Thank you.

2

There are 2 best solutions below

0
zorama On BEST ANSWER

With help of Jason advice herein above I finally found the solution, which is to change the View Model of second page this way:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Web;
using Xamarin.Forms;

namespace TestApp2.ViewModels
{
    public class TransferDateTimePageViewModel : BaseViewModel, INotifyPropertyChanged, IQueryAttributable        
    {

        public void ApplyQueryAttributes(IDictionary<string, string> query)
        {
            string Time = HttpUtility.UrlDecode(query["MyTime"]);
            LoadMyTime(Time);
        }


        void LoadMyTime(string Time)
        {
            _ = TimeSpan.TryParse(Time, out myTime);
            OnPropertyChanged(nameof(MyTime));
        }

        private TimeSpan myTime;

        public TimeSpan MyTime
           {
            get => myTime;
            set
               {
                   myTime = value;
                   OnPropertyChanged();
               }
           }
     }
}
5
Jason On

from the docs

Primitive data can be passed as string-based query parameters

primitive usually means simple value types, not complex objects like TimeSpan

to pass objects, use this syntax

var navigationParameter = new Dictionary<string, object>
{
    { "Time", myTimespanObject }
};
await Shell.Current.GoToAsync($"mypage", false, navigationParameter);