简体   繁体   中英

Xamarin Forms DateTimePicker custom ContentView bindable property

I build the following custom DateTimePicker, following the accepted answer from this post: Xamarin.forms DateTime Picker , in order to select a date and a time.

DateTimePicker class:

using System;
using System.ComponentModel;
using Xamarin.Forms;

namespace NativeTaskApp.Views.Component
{
    public class DateTimePicker : ContentView, INotifyPropertyChanged
    {
        public Entry _entry { get; private set; } = new Entry();
        public DatePicker _datePicker { get; private set; } = new DatePicker() { Date = DateTime.Today, IsVisible = false };
        public TimePicker _timePicker { get; private set; } = new TimePicker() { IsVisible = false };
        string _stringFormat { get; set; }
        public string StringFormat { get { return _stringFormat ?? "dd/MM/yyyy HH:mm"; } set { _stringFormat = value; } }
        
        public DateTime DateTime
        {
            get { return (DateTime)GetValue(DateTimeProperty); }
            set { SetValue(DateTimeProperty, value); OnPropertyChanged("DateTime"); }
        }

        private TimeSpan _time
        {
            get
            {
                return TimeSpan.FromTicks(DateTime.Ticks);
            }
            set
            {
                DateTime = new DateTime(DateTime.Date.Ticks).AddTicks(value.Ticks);
            }
        }

        private DateTime _date
        {
            get
            {
                return DateTime.Date;
            }
            set
            {
                DateTime = new DateTime(DateTime.TimeOfDay.Ticks).AddTicks(value.Ticks);
            }
        }

        BindableProperty DateTimeProperty = BindableProperty.Create("DateTime", typeof(DateTime), typeof(DateTimePicker), DateTime.Now, BindingMode.TwoWay, propertyChanged: DTPropertyChanged);

        [Obsolete]
        public DateTimePicker()
        {
            BindingContext = this;

            Content = new StackLayout()
            {
                Children =
            {
                _datePicker,
                _timePicker,
                _entry
            }
            };

            _datePicker.SetBinding<DateTimePicker>(DatePicker.DateProperty, p => p._date);
            _timePicker.SetBinding<DateTimePicker>(TimePicker.TimeProperty, p => p._time);
            _timePicker.Unfocused += (sender, args) => _time = _timePicker.Time;
            _datePicker.Focused += (s, a) => UpdateEntryText();

            GestureRecognizers.Add(new TapGestureRecognizer()
            {
                Command = new Command(() => _datePicker.Focus())
            });
            _entry.Focused += (sender, args) =>
            {
                Device.BeginInvokeOnMainThread(() => _datePicker.Focus());
            };
            _datePicker.Unfocused += (sender, args) =>
            {
                Device.BeginInvokeOnMainThread(() =>
                {
                    _timePicker.Focus();
                    _date = _datePicker.Date;
                    UpdateEntryText();
                });
            };
        }

        private void UpdateEntryText()
        {
            _entry.Text = DateTime.ToString(StringFormat);
        }

        static void DTPropertyChanged(BindableObject bindable, object oldValue, object newValue)
        {
            var timePicker = (bindable as DateTimePicker);
            timePicker.UpdateEntryText();
        }
    }
}

I also read through the Microsoft documentation ( https://learn.microsoft.com/en-us/xamarin/xamarin.forms/xaml/bindable-properties ) about bindable properties and I think I have everything I need in my DateTimePicker class to use bindable properties. However, if i use the DateTime property in my xaml i get the following error: No property, BindableProperty, or event found for "DateTime", or mismatching type between value and property

xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
             xmlns:common="clr-namespace:NativeTaskApp.Views.Partials" 
             xmlns:component="clr-namespace:NativeTaskApp.Views.Component"
             x:Class="NativeTaskApp.Views.CreatePage">
    <ContentPage.Content>
        <StackLayout>
            <!--Header-->
            <common:TaskHeader/>
            <!--Fields-->
            <StackLayout Spacing="40">
                <StackLayout>
                    <Label Text="Titel" Style="{StaticResource main-label}"/>
                    <Entry Text="{Binding Task.Titel, Mode=TwoWay}" BackgroundColor="white"/>
                </StackLayout>
                <StackLayout>
                    <Label Text="Deadline" Style="{StaticResource main-label}"/>
                    <component:DateTimePicker DateTime="{Binding Task.Deadline, Mode=TwoWay}" BackgroundColor="white"/>
                </StackLayout>
                <StackLayout>
                    <Label Text="Schedule" Style="{StaticResource main-label}"/>
                    <ListView ItemsSource="{Binding Todo.Schedules, Mode=TwoWay}">
                        <ListView.ItemTemplate>
                            <DataTemplate>
                                <ViewCell>
                                    <StackLayout Orientation="Vertical">
                                        <component:DateTimePicker DateTime="{Binding Date, Mode=TwoWay}"  BackgroundColor="white"/>

                                    </StackLayout>
                                </ViewCell>
                            </DataTemplate>
                        </ListView.ItemTemplate>
                    </ListView>
                    <component:DateTimePicker DateTime="{Binding NewScheduleDate, Mode=TwoWay}"  BackgroundColor="white"/>
                    <Button BackgroundColor="Green" Text="Add Schedule" Command="{Binding AddScheduleCommand}" />
                </StackLayout>
                <StackLayout>
                    <Label Text="Description" Style="{StaticResource main-label}"/>
                    <Editor Text="{Binding Task.Description, Mode=TwoWay}" HeightRequest="500" BackgroundColor="white"/>
                </StackLayout>
            </StackLayout>
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

Xaml BindingContext

using NativeTaskApp.Services.GeneratedClients;
using System;
using System.Collections.Generic;
using Xamarin.Forms;

namespace NativeTaskApp.ViewModels
{
    public class CreateVM : BaseViewModel
    {
        /*Task.Schedules = new List<Schedule>*/
        /*Schedule.Date = DateTime*/
        public Todo Task { get; set; }
        public DateTime NewScheduleDate { get; set; }



        public Command AddScheduleCommand { get; }
        public CreateVM()
        {
            Task = new Todo();
            Task.Schedules = new List<Schedule>();
            NewScheduleDate = DateTime.Now;

            AddScheduleCommand = new Command(AddSchedule);
        }




        public void AddSchedule()
        {
            Task.Schedules.Add(new Schedule() { Date = NewScheduleDate });
        }
    }
}

1. DateTime as a bindable property

Maybe I misunderstood the previous answer, but as a matter of fact, DateTime is a bindable property and we use it every day in a specific control in our mobile application.

Declaration example:

public static readonly BindableProperty MinimumDateProperty = BindableProperty.Create(nameof(MinimumDate),
typeof(DateTime),
typeof(DateTime), default(DateTime), BindingMode.OneWay, propertyChanged: OnYearsRangeChanged);

And we consume it like this in our xaml:

<controls:MonthYearPicker 
 MinimumDate="{Binding TodayDateTime}"
 MaximumDate="{Binding ExitDateTime}"
 SelectedDateTime="{Binding SelectedExitLocalDate}"/>

As the Microsoft documentations states it:

The purpose of bindable properties is to provide a property system that supports data binding, styles, templates, and values set through parent-child relationships.

As a consequence, as long as both binded properties have the same type, it should work flawlessly.

2. Proper declaration of bindable property

When you have this No property, BindableProperty, or event found for "DateTime", or mismatching type between value and property error message:

  • check the declaring type and the target type of your bindable property to detect any mismatch
  • ensure that your BindableProperties are correctly declared. Especially be careful with the access specifier which should be public most of the time.

In your case your:

BindableProperty DateTimeProperty = BindableProperty.Create("DateTime", typeof(DateTime), typeof(DateTimePicker), DateTime.Now, BindingMode.TwoWay, propertyChanged: DTPropertyChanged) 

Seems internal and not public : ( cf. official doc on access specifier )

Add the public access specifier and see if the error message persists.

For example:

public static readonly BindableProperty DateTimeProperty = BindableProperty.Create("DateTime", typeof(DateTime), typeof(DateTimePicker), DateTime.Now, BindingMode.TwoWay, propertyChanged: DTPropertyChanged) 

I build the following custom DateTimePicker, following the accepted answer from this post: Xamarin forms DateTime Picker , in order to select a date and a time.

DateTimePicker class:

using System;
using System.ComponentModel;
using Xamarin.Forms;

namespace NativeTaskApp.Views.Component
{
    public class DateTimePicker : ContentView, INotifyPropertyChanged
    {
        public Entry _entry { get; private set; } = new Entry();
        public DatePicker _datePicker { get; private set; } = new DatePicker() { Date = DateTime.Today, IsVisible = false };
        public TimePicker _timePicker { get; private set; } = new TimePicker() { IsVisible = false };
        string _stringFormat { get; set; }
        public string StringFormat { get { return _stringFormat ?? "dd/MM/yyyy HH:mm"; } set { _stringFormat = value; } }
        
        public DateTime DateTime
        {
            get { return (DateTime)GetValue(DateTimeProperty); }
            set { SetValue(DateTimeProperty, value); OnPropertyChanged("DateTime"); }
        }

        private TimeSpan _time
        {
            get
            {
                return TimeSpan.FromTicks(DateTime.Ticks);
            }
            set
            {
                DateTime = new DateTime(DateTime.Date.Ticks).AddTicks(value.Ticks);
            }
        }

        private DateTime _date
        {
            get
            {
                return DateTime.Date;
            }
            set
            {
                DateTime = new DateTime(DateTime.TimeOfDay.Ticks).AddTicks(value.Ticks);
            }
        }

        BindableProperty DateTimeProperty = BindableProperty.Create("DateTime", typeof(DateTime), typeof(DateTimePicker), DateTime.Now, BindingMode.TwoWay, propertyChanged: DTPropertyChanged);

        [Obsolete]
        public DateTimePicker()
        {
            BindingContext = this;

            Content = new StackLayout()
            {
                Children =
            {
                _datePicker,
                _timePicker,
                _entry
            }
            };

            _datePicker.SetBinding<DateTimePicker>(DatePicker.DateProperty, p => p._date);
            _timePicker.SetBinding<DateTimePicker>(TimePicker.TimeProperty, p => p._time);
            _timePicker.Unfocused += (sender, args) => _time = _timePicker.Time;
            _datePicker.Focused += (s, a) => UpdateEntryText();

            GestureRecognizers.Add(new TapGestureRecognizer()
            {
                Command = new Command(() => _datePicker.Focus())
            });
            _entry.Focused += (sender, args) =>
            {
                Device.BeginInvokeOnMainThread(() => _datePicker.Focus());
            };
            _datePicker.Unfocused += (sender, args) =>
            {
                Device.BeginInvokeOnMainThread(() =>
                {
                    _timePicker.Focus();
                    _date = _datePicker.Date;
                    UpdateEntryText();
                });
            };
        }

        private void UpdateEntryText()
        {
            _entry.Text = DateTime.ToString(StringFormat);
        }

        static void DTPropertyChanged(BindableObject bindable, object oldValue, object newValue)
        {
            var timePicker = (bindable as DateTimePicker);
            timePicker.UpdateEntryText();
        }
    }
}

I also read through the Microsoft documentation ( https://docs.microsoft.com/en-us/xamarin/xamarin-forms/xaml/bindable-properties ) about bindable properties and I think I have everything I need in my DateTimePicker class to use bindable properties. However, if i use the DateTime property in my xaml i get the following error: No property, BindableProperty, or event found for "DateTime", or mismatching type between value and property

xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
             xmlns:common="clr-namespace:NativeTaskApp.Views.Partials" 
             xmlns:component="clr-namespace:NativeTaskApp.Views.Component"
             x:Class="NativeTaskApp.Views.CreatePage">
    <ContentPage.Content>
        <StackLayout>
            <!--Header-->
            <common:TaskHeader/>
            <!--Fields-->
            <StackLayout Spacing="40">
                <StackLayout>
                    <Label Text="Titel" Style="{StaticResource main-label}"/>
                    <Entry Text="{Binding Task.Titel, Mode=TwoWay}" BackgroundColor="white"/>
                </StackLayout>
                <StackLayout>
                    <Label Text="Deadline" Style="{StaticResource main-label}"/>
                    <component:DateTimePicker DateTime="{Binding Task.Deadline, Mode=TwoWay}" BackgroundColor="white"/>
                </StackLayout>
                <StackLayout>
                    <Label Text="Schedule" Style="{StaticResource main-label}"/>
                    <ListView ItemsSource="{Binding Todo.Schedules, Mode=TwoWay}">
                        <ListView.ItemTemplate>
                            <DataTemplate>
                                <ViewCell>
                                    <StackLayout Orientation="Vertical">
                                        <component:DateTimePicker DateTime="{Binding Date, Mode=TwoWay}"  BackgroundColor="white"/>

                                    </StackLayout>
                                </ViewCell>
                            </DataTemplate>
                        </ListView.ItemTemplate>
                    </ListView>
                    <component:DateTimePicker DateTime="{Binding NewScheduleDate, Mode=TwoWay}"  BackgroundColor="white"/>
                    <Button BackgroundColor="Green" Text="Add Schedule" Command="{Binding AddScheduleCommand}" />
                </StackLayout>
                <StackLayout>
                    <Label Text="Description" Style="{StaticResource main-label}"/>
                    <Editor Text="{Binding Task.Description, Mode=TwoWay}" HeightRequest="500" BackgroundColor="white"/>
                </StackLayout>
            </StackLayout>
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

Xaml BindingContext

using NativeTaskApp.Services.GeneratedClients;
using System;
using System.Collections.Generic;
using Xamarin.Forms;

namespace NativeTaskApp.ViewModels
{
    public class CreateVM : BaseViewModel
    {
        /*Task.Schedules = new List<Schedule>*/
        /*Schedule.Date = DateTime*/
        public Todo Task { get; set; }
        public DateTime NewScheduleDate { get; set; }



        public Command AddScheduleCommand { get; }
        public CreateVM()
        {
            Task = new Todo();
            Task.Schedules = new List<Schedule>();
            NewScheduleDate = DateTime.Now;

            AddScheduleCommand = new Command(AddSchedule);
        }




        public void AddSchedule()
        {
            Task.Schedules.Add(new Schedule() { Date = NewScheduleDate });
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM