Search code examples
win-universal-apptimepickeruwp-xaml

UWP - How to format time in TimePicker_TimeChanged AND Select Time without Buttons inside in TimepickerFlyout


I am developing UWP App (Win10 VS2015). I have two problems.

1- How can I get the time in this format (4:00 PM or 9:34 AM etc..) in 12hours format, I can get the value without PM/AM via this TimePicker.Time = sender.Time.ToString(@"hh\:mm"), but I need the actual format as I mentioned.

XAML Code

<TimePicker ClockIdentifier="12HourClock" TimeChanged="TimePicker_TimeChanged" Style="{StaticResource TimePickerStyleCustom}"/>

.cs Code

private void TimePicker_TimeChanged(object sender, TimePickerValueChangedEventArgs e)
    {            
        timeTitle.Text = (sender as TimePicker).Time.ToString(@"hh\:mm");
    }

Via the above code, I can get the value without AM/PM and also it is in 24hour format i.e. 4:00PM is in 16:00, but I need it in 4:00PM or 4:00AM (this is just an example). If I put .ToString(@"hh\:mm tt"); it throws exception. How to get this please.

2- 2nd problem is, When we tap on the Timepicker, a TimePickerFlyout expands and we select time by clicking on hours/minutes and when finalize then click on the (Tick) Mark to select Time ... but I need to remove these buttons (Done (_/) & Cancel (X)) and select time by selecting the Hour/Min in Flyout panel rather than button click and assign it to a string. I can remove the buttons from TimePickerFlyoutPresenter Style but then how to make the Selection functional like button click.

See the screenshot, in first portion the 2 buttons are available and it worked, but I need the 2nd portion as shown in the right side.

enter image description here


Solution

  • For First problem TimeSpan represents a time interval not a time of day. You have to convert it to DateTime then format it

     private void TestTimePicker_TimeChanged(object sender, TimePickerValueChangedEventArgs e)
            {
              string Text = (sender as TimePicker).Time.ToString(@"hh\:mm");
                var dateTime = new DateTime((sender as TimePicker).Time.Ticks); // Date part is 01-01-0001
                var formattedTime = dateTime.ToString("h:mm tt", CultureInfo.InvariantCulture);
            }
    

    Problem 2 For this either you have to implement your own TimerPickerFlyout from PickerFlyoutBase or from Flyout. It is bit complicated and I havent worked on that. You can watch this link for that

    There is a easy workaround . As you mentioned in question you have to edit TimePickerFlyoutPresenter style.

    I tried adding Tapped event handler to FirstPickerHost,SecondPickerHost,ThirdPickerHost.But you cant add event handlers in app.xaml. So i used Behavioural SDK's interactions. If you have Template10 used in your project you dont have to download anything just add following namespaces in app.xaml

    xmlns:interact="using:Microsoft.Xaml.Interactivity" 
    xmlns:interactcore="using:Microsoft.Xaml.Interactions.Core"
    
    
    
     <Style TargetType="TimePickerFlyoutPresenter">
            <Setter Property="Width" Value="242" />
            <Setter Property="MinWidth" Value="242" />
            <Setter Property="MaxHeight" Value="396" />
            <Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
            <Setter Property="FontWeight" Value="Normal" />
            <Setter Property="IsTabStop" Value="False" />
            <Setter Property="Background" Value="{ThemeResource SystemControlBackgroundChromeMediumLowBrush}" />
            <Setter Property="AutomationProperties.AutomationId" Value="TimePickerFlyoutPresenter" />
            <Setter Property="BorderBrush" Value="{ThemeResource SystemControlForegroundTransparentBrush}" />
            <Setter Property="BorderThickness" Value="{ThemeResource DateTimeFlyoutBorderThickness}" />
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="TimePickerFlyoutPresenter">
                        <Border x:Name="Background"
                            Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}"
                            MaxHeight="396">
                            <Grid x:Name="ContentPanel">
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="*" />
                               </Grid.RowDefinitions>
    
                                <Grid >
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="*" x:Name="FirstPickerHostColumn" />
                                        <ColumnDefinition Width="Auto" />
                                        <ColumnDefinition Width="*" x:Name="SecondPickerHostColumn" />
                                        <ColumnDefinition Width="Auto" />
                                        <ColumnDefinition Width="*" x:Name="ThirdPickerHostColumn" />
                                    </Grid.ColumnDefinitions>
    
                                    <Rectangle x:Name="HighlightRect" Fill="{ThemeResource SystemControlHighlightListAccentLowBrush}" Grid.Column="0" Grid.ColumnSpan="5" VerticalAlignment="Center" Height="44"  >
    
                                    </Rectangle>
    
                                    <Border x:Name="FirstPickerHost" Grid.Column="0" >
                                        <interact:Interaction.Behaviors>
                                            <interactcore:EventTriggerBehavior EventName="Tapped">
                                                <interactcore:InvokeCommandAction Command="{Binding ClosePopUp}"/>
                                            </interactcore:EventTriggerBehavior>
                                        </interact:Interaction.Behaviors>
                                    </Border>
                                    <Rectangle x:Name="FirstPickerSpacing" Fill="{ThemeResource SystemControlForegroundBaseLowBrush}" HorizontalAlignment="Center" Width="2" Grid.Column="1" >
    
                                    </Rectangle>
                                    <Border x:Name="SecondPickerHost" Grid.Column="2" >
                                        <interact:Interaction.Behaviors>
                                            <interactcore:EventTriggerBehavior EventName="Tapped">
                                                <interactcore:InvokeCommandAction Command="{Binding ClosePopUp}"/>
                                            </interactcore:EventTriggerBehavior>
                                        </interact:Interaction.Behaviors>
                                    </Border>
                                    <Rectangle x:Name="SecondPickerSpacing" Fill="{ThemeResource SystemControlForegroundBaseLowBrush}" HorizontalAlignment="Center" Width="2" Grid.Column="3" >
    
                                    </Rectangle>
                                    <Border x:Name="ThirdPickerHost" Grid.Column="4" >
                                        <interact:Interaction.Behaviors>
                                            <interactcore:EventTriggerBehavior EventName="Tapped">
                                                <interactcore:InvokeCommandAction Command="{Binding ClosePopUp}"/>
                                            </interactcore:EventTriggerBehavior>
                                        </interact:Interaction.Behaviors>
                                    </Border>
    
                                </Grid>
    
                                <Grid Grid.Row="1"  Visibility="Collapsed">
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="*" />
                                        <ColumnDefinition Width="*" />
                                    </Grid.ColumnDefinitions>
                                    <Rectangle Height="2" VerticalAlignment="Top" Fill="{ThemeResource SystemControlForegroundBaseLowBrush}" Grid.ColumnSpan="2" />
    
                                    <Button x:Name="AcceptButton" Grid.Column="0" Content="&#xE8FB;" FontFamily="{ThemeResource SymbolThemeFontFamily}" FontSize="16" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Style="{StaticResource DateTimePickerFlyoutButtonStyle}" Margin="0,2,0,0" />
                                    <Button x:Name="DismissButton" Grid.Column="1" Content="&#xE711;" FontFamily="{ThemeResource SymbolThemeFontFamily}" FontSize="16" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Style="{StaticResource DateTimePickerFlyoutButtonStyle}" Margin="0,2,0,0" />
                                </Grid>
                            </Grid>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    

    And you have to set datacontext of Timepicker to your viewmodel.

       <TimePicker x:Name="TestTimePicker" Time="{Binding SelectedTime,Mode=TwoWay}" ClockIdentifier="12HourClock" Time="0" TimeChanged="TestTimePicker_TimeChanged" >
         </TimePicker>
    
        public MainPage()
                {
                    this.InitializeComponent();
                    DataContext = new TestViewModel();
                    TestTimePicker.DataContext = this.DataContext;
                }
    
        public class TestViewModel:INotifyPropertyChanged
            {
                public DelegateCommand<TappedRoutedEventArgs> ClosePopUp { get; set; }
     TimeSpan selectedTime;
            public TimeSpan SelectedTime
            { get { return selectedTime; }
                set
                {
                    if (value != selectedTime)
                    {
                        selectedTime = value;
                        OnPropertyChanged("SelectedTime");
                    }
                }
            }
    
                public TestViewModel()
            {
    
                ClosePopUp = new DelegateCommand<TappedRoutedEventArgs>((args) =>
                {
                    if (args.OriginalSource is Grid)
                    {
                        Grid grid = args.OriginalSource as Grid;
                        if (grid != null)
                        {
                           var fly = FlyoutBase.GetAttachedFlyout(grid);
                            var flyoutpresenter = FindParent<TimePickerFlyoutPresenter>(grid);
                            if (flyoutpresenter != null)
                                (flyoutpresenter.Parent as Popup).IsOpen = false;
                           var firstPicker= FindParent(grid,"FirstPickerHost");
                            var secondPicker = FindParent(grid, "SecondPickerHost");
                            var thirdPicker = FindParent(grid, "ThirdPickerHost");
                            var textblock = FindElementInVisualTree<TextBlock>(grid);
                            if (firstPicker != null)
                            {
                                SelectedTime = new TimeSpan(int.Parse(textblock.Text), SelectedTime.Minutes, SelectedTime.Seconds);
    
                            }
                             if(secondPicker!=null)
                            {
                                SelectedTime = new TimeSpan(SelectedTime.Hours, int.Parse(textblock.Text), SelectedTime.Seconds);
                            }
                            if (thirdPicker != null)
                            {
                              // AM/PM
                            }
    
                        }
                    }
                    else if(args.OriginalSource is TextBlock)
                    {
                        TextBlock textblock = args.OriginalSource as TextBlock;
                        if (textblock != null)
                        {
                            var fly = FlyoutBase.GetAttachedFlyout(textblock);
                            var flyoutpresenter = FindParent<TimePickerFlyoutPresenter>(textblock);
                            if (flyoutpresenter != null)
                                (flyoutpresenter.Parent as Popup).IsOpen = false;
                            var firstPicker = FindParent(textblock, "FirstPickerHost");
                            var secondPicker = FindParent(textblock, "SecondPickerHost");
                            var thirdPicker = FindParent(textblock, "ThirdPickerHost");
    
                            if (firstPicker != null)
                            {
                                SelectedTime = new TimeSpan(int.Parse(textblock.Text), SelectedTime.Minutes, SelectedTime.Seconds);
    
                            }
                            if (secondPicker != null)
                            {
                                SelectedTime = new TimeSpan(SelectedTime.Hours, int.Parse(textblock.Text), SelectedTime.Seconds);
                            }
                            if (thirdPicker != null)
                            {
                               //  AM/PM
                            }
                        }
                    }
                    else
                    {
    
                    }
                });
    
            }
    
            public event PropertyChangedEventHandler PropertyChanged;
            void OnPropertyChanged(string propertyName)
            {
                // the new Null-conditional Operators are thread-safe:
                this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
            }
    
            private T FindParent<T>(DependencyObject child) where T : DependencyObject
            {
                var parent = VisualTreeHelper.GetParent(child);
                if (parent != null && parent is T)
                    return (T)parent;
                else if (parent == null)
                    return null;
                else
                {
                    var result = FindParent<T>(parent);
                    if (result != null)
                        return result;
    
                }
                return null;
            }
            private DependencyObject FindParent(DependencyObject child,string parentName) 
            {
                var parent = VisualTreeHelper.GetParent(child);
                if (parent != null && (parent as FrameworkElement).Name.Equals(parentName))
                    return parent;
                else if (parent == null)
                    return null;
                else
                {
                    var result = FindParent(parent,parentName);
                    if (result != null)
                        return result;
    
                }
                return null;
            }
           private T FindElementInVisualTree<T>(DependencyObject parentElement) where T : DependencyObject
        {
            var count = VisualTreeHelper.GetChildrenCount(parentElement);
            if (count == 0) return null;
    
            for (int i = 0; i < count; i++)
            {
                var child = VisualTreeHelper.GetChild(parentElement, i);
                if (child != null && child is T)
                    return (T)child;
                else
                {
                    var result = FindElementInVisualTree<T>(child);
                    if (result != null)
                        return result;
                }
            }
            return null;
        }
        }
    

    What i'm doing above in ClosePopUp command is programmatically finding TimePickerFlyoutPresenter using VisualTreeHelper getparaent() method TimePickerFlyoutPresenter parent is a PopUp that is actually your TimePickerFlyout. Set popup's IsOpen to false

    // Updated the code to reflect selected hour and minute in timepicker. One issue left is update the selected AM or PM. I ll update if i get the solution
    

    Here is a link to complete project which solves all issues Source Code