DatePicker.Focus() not opening the picker on Windows

Charlos889Charlos889 Member ✭✭

Hi!

Anyone knows why calling Focus() on a Forms DatePicker opens the picker on Android and IOS, but not on Windows (WPF)?
I've seen some posts on bugzilla discussing about this issue, but they are almost all 2-3 years old and none of them seems to be correct.

Tagged:

Posts

  • JohnHardmanJohnHardman GBUniversity mod
    edited March 6

    @Charlos889 said:
    Hi!

    Anyone knows why calling Focus() on a Forms DatePicker opens the picker on Android and IOS, but not on Windows (WPF)?
    I've seen some posts on bugzilla discussing about this issue, but they are almost all 2-3 years old and none of them seems to be correct.

    Whether Xamarin did it this way intentionally or not, I don't know. However, on devices being used without a touch-screen interface, the user would expect to be able to tab around the controls on the screen using the keyboard without controls automatically expanding. Tabbing onto a Picker, DatePicker or TimePicker, should put the focus onto the control, with the current value displayed, but without automatically expanding the control.

  • Charlos889Charlos889 Member ✭✭

    @JohnHardman Thanks for your reply!
    Well it makes sense.. Do you know if there's a workaround or another way to open Pickers without clicking directly on them ? I need to open it on a button click event.

  • JohnHardmanJohnHardman GBUniversity mod

    @Charlos889 said:
    @JohnHardman Thanks for your reply!
    Well it makes sense.. Do you know if there's a workaround or another way to open Pickers without clicking directly on them ? I need to open it on a button click event.

    I haven't done it (I don't need it), but I believe Windows.UI.Xaml.Controls.Button.Flyout.ShowAt is the method you'd be after. You'd need to create a custom DatePickerRenderer for UWP, in which you find the appropriate Windows.UI.Xaml.Controls.Button by iterating through the descendents of the Control's parent, looking for the "FlyoutButton".

    So, something based on:

    Windows.UI.Xaml.Controls.DatePicker nativeDatePicker = Control;
    Windows.UI.Xaml.Controls.Button button = GetDescendantsByName(nativeDatePicker.Parent, "FlyoutButton").FirstOrDefault();
    button.Flyout.ShowAt(button);
    

    GetDescendantsByName would be implemented using Windows.UI.Xaml.Media.VisualTreeHelper

    Hope that helps. I know a number of people have asked for this functionality since UWP support was added to XF. If you get it working, it would be a useful piece of code to share :-)

  • Charlos889Charlos889 Member ✭✭

    @JohnHardman
    I'll definetly try this and share it if it works.

    Thanks !

  • Charlos889Charlos889 Member ✭✭
    edited March 7

    Finally, i looked into it today and found a solution.

    Turns out that the WPF DatePicker has a property named IsDropDownOpen that once set to true opens the calendar.

    I made a renderer for the picker and overridden the OnElementPropertyChanged method to detect when the Forms Picker IsFocus property changed to then show the calendar. Unfortunately, the focus property doesn't seem to change at all.

    I ended up making a custom style based on the base style of the WPF DatePicker control using Blend. In this style, I only changed the visibility of the DatePickerTextBox and the Button to Hidden. Then, when I click the button to show the picker (the one in my layout, not the one inside the WPF Picker), I set the Forms Picker IsVisible property to true and catch that in the OnElementPropertyChanged of the renderer where I set the IsDropDownOpen property of the WPF Picker to true. Finally, I added a handler for the SelectedDateChanged event of the WPF Picker to change the Forms Picker date when user clicks on the calendar.

    here's my renderer :

    XamDatePicker is an alias for the Xamarin.Forms.DatePicker
    InvisibleDatePicker is a custom control I made to apply the custom style (see below)

    `
    using XamDatePicker = Xamarin.Forms.DatePicker;

    [assembly: ExportRenderer(typeof(XamDatePicker), typeof(YourProject.WPF.Renderers.DatePickerExRenderer))]
    
    namespace YourProject.WPF.Renderers
    {
        public class DatePickerExRenderer : ViewRenderer<XamDatePicker, InvisibleDatePicker>
        { 
            //private DatePickerEx Picker => Element as DatePickerEx;
            private XamDatePicker Picker => Element as XamDatePicker;
    
            protected override void OnElementChanged(ElementChangedEventArgs<XamDatePicker> e)
            {        
                base.OnElementChanged(e);
    
                if (Control == null)
                {
                    var invPicker = new InvisibleDatePicker();
    
                    SetNativeControl(invPicker);
                }
    
                if (e.OldElement != null)
                {
                    e.OldElement.PropertyChanging -= OnPropertyChanging;
                    e.OldElement.PropertyChanged -= OnElementPropertyChanged;
    
                    Control.SelectedDateChanged -= DatePicker_DateSelected;
                }
    
                if (e.NewElement != null)
                {
                    e.NewElement.PropertyChanging += OnPropertyChanging;
                    e.NewElement.PropertyChanged += OnElementPropertyChanged;
    
                    Control.SelectedDateChanged += DatePicker_DateSelected;
                }
            }
    
            private void DatePicker_DateSelected(object sender, SelectionChangedEventArgs e)
            {
                Picker.Date = (sender as InvisibleDatePicker).SelectedDate.Value;
            }
    
            private void OnPropertyChanging(object sender, Xamarin.Forms.PropertyChangingEventArgs e)
            {
                if (Control == null || Element == null)
                    return;
    
                if (e.PropertyName == XamDatePicker.DateProperty.PropertyName)
                {
                    Picker.IsVisible = false;
                }    
            }
    
            protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
            {
                base.OnElementPropertyChanged(sender, e);
    
                if (e.PropertyName == XamDatePicker.IsVisibleProperty.PropertyName)
                {
                    if (Picker.IsVisible)
                    {
                        Control.IsDropDownOpen = true;
                    }
                    else
                    {
                        Control.IsDropDownOpen = false;
                    }
                }
            }
        }
    }
    

    `

  • Charlos889Charlos889 Member ✭✭
    edited March 7

    And here's my custom DatePicker style (InvisibleDatePicker) :

    ***** Check the bottom part to find the Button and DatePickerTextBox ******

    `

    <DatePicker.Resources>
        <Style x:Key="InvisibleDatePicker" TargetType="{x:Type DatePicker}">
            <Setter Property="Foreground" Value="#FF333333"/>
            <Setter Property="IsTodayHighlighted" Value="True"/>
            <Setter Property="SelectedDateFormat" Value="Short"/>
            <Setter Property="Background" Value="Transparent"/>
            <Setter Property="Padding" Value="2"/>
            <Setter Property="BorderBrush">
                <Setter.Value>
                    <LinearGradientBrush EndPoint=".5,0" StartPoint=".5,1">
                        <GradientStop Color="#FFA3AEB9" Offset="0"/>
                        <GradientStop Color="#FF8399A9" Offset="0.375"/>
                        <GradientStop Color="#FF718597" Offset="0.375"/>
                        <GradientStop Color="#FF617584" Offset="1"/>
                    </LinearGradientBrush>
                </Setter.Value>
            </Setter>
            <Setter Property="BorderThickness" Value="1"/>
            <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type DatePicker}">
                        <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}">
                            <VisualStateManager.VisualStateGroups>
                                <VisualStateGroup x:Name="CommonStates">
                                    <VisualState x:Name="Normal"/>
                                    <VisualState x:Name="Disabled">
                                        <Storyboard>
                                            <DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="PART_DisabledVisual"/>
                                        </Storyboard>
                                    </VisualState>
                                </VisualStateGroup>
                            </VisualStateManager.VisualStateGroups>
                            <Grid x:Name="PART_Root" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
                                <Grid.Resources>
                                    <SolidColorBrush x:Key="DisabledBrush" Color="#A5FFFFFF"/>
                                    <ControlTemplate x:Key="DropDownButtonTemplate" TargetType="{x:Type Button}">
                                        <Grid>
                                            <VisualStateManager.VisualStateGroups>
                                                <VisualStateGroup x:Name="CommonStates">
                                                    <VisualStateGroup.Transitions>
                                                        <VisualTransition GeneratedDuration="0"/>
                                                        <VisualTransition GeneratedDuration="0:0:0.1" To="MouseOver"/>
                                                        <VisualTransition GeneratedDuration="0:0:0.1" To="Pressed"/>
                                                    </VisualStateGroup.Transitions>
                                                    <VisualState x:Name="Normal"/>
                                                    <VisualState x:Name="MouseOver">
                                                        <Storyboard>
                                                            <ColorAnimation Duration="0" To="#FF448DCA" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)" Storyboard.TargetName="Background"/>
                                                            <ColorAnimationUsingKeyFrames BeginTime="0" Duration="00:00:00.001" Storyboard.TargetProperty="(Border.Background).(GradientBrush.GradientStops)[3].(GradientStop.Color)" Storyboard.TargetName="BackgroundGradient">
                                                                <SplineColorKeyFrame KeyTime="0" Value="#7FFFFFFF"/>
                                                            </ColorAnimationUsingKeyFrames>
                                                            <ColorAnimationUsingKeyFrames BeginTime="0" Duration="00:00:00.001" Storyboard.TargetProperty="(Border.Background).(GradientBrush.GradientStops)[2].(GradientStop.Color)" Storyboard.TargetName="BackgroundGradient">
                                                                <SplineColorKeyFrame KeyTime="0" Value="#CCFFFFFF"/>
                                                            </ColorAnimationUsingKeyFrames>
                                                            <ColorAnimationUsingKeyFrames BeginTime="0" Duration="00:00:00.001" Storyboard.TargetProperty="(Border.Background).(GradientBrush.GradientStops)[1].(GradientStop.Color)" Storyboard.TargetName="BackgroundGradient">
                                                                <SplineColorKeyFrame KeyTime="0" Value="#F2FFFFFF"/>
                                                            </ColorAnimationUsingKeyFrames>
                                                        </Storyboard>
                                                    </VisualState>
                                                    <VisualState x:Name="Pressed">
                                                        <Storyboard>
                                                            <ColorAnimationUsingKeyFrames BeginTime="0" Duration="00:00:00.001" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)" Storyboard.TargetName="Background">
                                                                <SplineColorKeyFrame KeyTime="0" Value="#FF448DCA"/>
                                                            </ColorAnimationUsingKeyFrames>
                                                            <DoubleAnimationUsingKeyFrames BeginTime="0" Duration="00:00:00.001" Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="Highlight">
                                                                <SplineDoubleKeyFrame KeyTime="0" Value="1"/>
                                                            </DoubleAnimationUsingKeyFrames>
                                                            <ColorAnimationUsingKeyFrames BeginTime="0" Duration="00:00:00.001" Storyboard.TargetProperty="(Border.Background).(GradientBrush.GradientStops)[1].(GradientStop.Color)" Storyboard.TargetName="BackgroundGradient">
                                                                <SplineColorKeyFrame KeyTime="0" Value="#EAFFFFFF"/>
                                                            </ColorAnimationUsingKeyFrames>
                                                            <ColorAnimationUsingKeyFrames BeginTime="0" Duration="00:00:00.001" Storyboard.TargetProperty="(Border.Background).(GradientBrush.GradientStops)[2].(GradientStop.Color)" Storyboard.TargetName="BackgroundGradient">
                                                                <SplineColorKeyFrame KeyTime="0" Value="#C6FFFFFF"/>
                                                            </ColorAnimationUsingKeyFrames>
                                                            <ColorAnimationUsingKeyFrames BeginTime="0" Duration="00:00:00.001" Storyboard.TargetProperty="(Border.Background).(GradientBrush.GradientStops)[3].(GradientStop.Color)" Storyboard.TargetName="BackgroundGradient">
                                                                <SplineColorKeyFrame KeyTime="0" Value="#6BFFFFFF"/>
                                                            </ColorAnimationUsingKeyFrames>
                                                            <ColorAnimationUsingKeyFrames BeginTime="0" Duration="00:00:00.001" Storyboard.TargetProperty="(Border.Background).(GradientBrush.GradientStops)[0].(GradientStop.Color)" Storyboard.TargetName="BackgroundGradient">
                                                                <SplineColorKeyFrame KeyTime="0" Value="#F4FFFFFF"/>
                                                            </ColorAnimationUsingKeyFrames>
                                                        </Storyboard>
                                                    </VisualState>
                                                    <VisualState x:Name="Disabled"/>
                                                </VisualStateGroup>
                                            </VisualStateManager.VisualStateGroups>
                                            <Grid Background="#11FFFFFF" FlowDirection="LeftToRight" HorizontalAlignment="Center" Height="18" Margin="0" VerticalAlignment="Center" Width="19">
                                                <Grid.ColumnDefinitions>
                                                    <ColumnDefinition Width="20*"/>
                                                    <ColumnDefinition Width="20*"/>
                                                    <ColumnDefinition Width="20*"/>
                                                    <ColumnDefinition Width="20*"/>
                                                </Grid.ColumnDefinitions>
                                                <Grid.RowDefinitions>
                                                    <RowDefinition Height="23*"/>
                                                    <RowDefinition Height="19*"/>
                                                    <RowDefinition Height="19*"/>
                                                    <RowDefinition Height="19*"/>
                                                </Grid.RowDefinitions>
                                                <Border x:Name="Highlight" BorderBrush="#FF45D6FA" BorderThickness="1" Grid.ColumnSpan="4" CornerRadius="0,0,1,1" Margin="-1" Opacity="0" Grid.Row="0" Grid.RowSpan="4"/>
                                                <Border x:Name="Background" BorderBrush="#FFFFFFFF" BorderThickness="1" Background="#FF1F3B53" Grid.ColumnSpan="4" CornerRadius=".5" Margin="0,-1,0,0" Opacity="1" Grid.Row="1" Grid.RowSpan="3"/>
                                                <Border x:Name="BackgroundGradient" BorderBrush="#BF000000" BorderThickness="1" Grid.ColumnSpan="4" CornerRadius=".5" Margin="0,-1,0,0" Opacity="1" Grid.Row="1" Grid.RowSpan="3">
                                                    <Border.Background>
                                                        <LinearGradientBrush EndPoint=".7,1" StartPoint=".7,0">
                                                            <GradientStop Color="#FFFFFFFF" Offset="0"/>
                                                            <GradientStop Color="#F9FFFFFF" Offset="0.375"/>
                                                            <GradientStop Color="#E5FFFFFF" Offset="0.625"/>
                                                            <GradientStop Color="#C6FFFFFF" Offset="1"/>
                                                        </LinearGradientBrush>
                                                    </Border.Background>
                                                </Border>
                                                <Rectangle Grid.ColumnSpan="4" Grid.RowSpan="1" StrokeThickness="1">
                                                    <Rectangle.Fill>
                                                        <LinearGradientBrush EndPoint="0.3,-1.1" StartPoint="0.46,1.6">
                                                            <GradientStop Color="#FF4084BD"/>
                                                            <GradientStop Color="#FFAFCFEA" Offset="1"/>
                                                        </LinearGradientBrush>
                                                    </Rectangle.Fill>
                                                    <Rectangle.Stroke>
                                                        <LinearGradientBrush EndPoint="0.48,-1" StartPoint="0.48,1.25">
                                                            <GradientStop Color="#FF494949"/>
                                                            <GradientStop Color="#FF9F9F9F" Offset="1"/>
                                                        </LinearGradientBrush>
                                                    </Rectangle.Stroke>
                                                </Rectangle>
                                                <Path Grid.ColumnSpan="4" Grid.Column="0" Data="M11.426758,8.4305077 L11.749023,8.4305077 L11.749023,16.331387 L10.674805,16.331387 L10.674805,10.299648 L9.0742188,11.298672 L9.0742188,10.294277 C9.4788408,10.090176 9.9094238,9.8090878 10.365967,9.4510155 C10.82251,9.0929432 11.176106,8.7527733 11.426758,8.4305077 z M14.65086,8.4305077 L18.566387,8.4305077 L18.566387,9.3435936 L15.671368,9.3435936 L15.671368,11.255703 C15.936341,11.058764 16.27293,10.960293 16.681133,10.960293 C17.411602,10.960293 17.969301,11.178717 18.354229,11.615566 C18.739157,12.052416 18.931622,12.673672 18.931622,13.479336 C18.931622,15.452317 18.052553,16.438808 16.294415,16.438808 C15.560365,16.438808 14.951641,16.234707 14.468243,15.826504 L14.881817,14.929531 C15.368796,15.326992 15.837872,15.525723 16.289043,15.525723 C17.298809,15.525723 17.803692,14.895514 17.803692,13.635098 C17.803692,12.460618 17.305971,11.873379 16.310528,11.873379 C15.83071,11.873379 15.399232,12.079271 15.016094,12.491055 L14.65086,12.238613 z" Fill="#FF2F2F2F" HorizontalAlignment="Center" Margin="4,3,4,3" Grid.Row="1" Grid.RowSpan="3" RenderTransformOrigin="0.5,0.5" Stretch="Fill" VerticalAlignment="Center"/>
                                                <Ellipse Grid.ColumnSpan="4" Fill="#FFFFFFFF" HorizontalAlignment="Center" Height="3" StrokeThickness="0" VerticalAlignment="Center" Width="3"/>
                                                <Border x:Name="DisabledVisual" BorderBrush="#B2FFFFFF" BorderThickness="1" Grid.ColumnSpan="4" CornerRadius="0,0,.5,.5" Opacity="0" Grid.Row="0" Grid.RowSpan="4"/>
                                            </Grid>
                                        </Grid>
                                    </ControlTemplate>
                                </Grid.Resources>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="*"/>
                                    <ColumnDefinition Width="Auto"/>
                                </Grid.ColumnDefinitions>
                                <Button x:Name="PART_Button" Grid.Column="1" Foreground="{TemplateBinding Foreground}" Focusable="False" HorizontalAlignment="Left" Margin="3,0,3,0" Grid.Row="0" Template="{StaticResource DropDownButtonTemplate}" VerticalAlignment="Top" Width="20" Visibility="Hidden"/>
                                <DatePickerTextBox x:Name="PART_TextBox" Grid.Column="0" Focusable="{TemplateBinding Focusable}" HorizontalContentAlignment="Stretch" Grid.Row="0" VerticalContentAlignment="Stretch" Visibility="Hidden"/>
                                <Grid x:Name="PART_DisabledVisual" Grid.ColumnSpan="2" Grid.Column="0" IsHitTestVisible="False" Opacity="0" Grid.Row="0">
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="*"/>
                                        <ColumnDefinition Width="Auto"/>
                                    </Grid.ColumnDefinitions>
                                    <Rectangle Grid.Column="0" Fill="#A5FFFFFF" RadiusY="1" Grid.Row="0" RadiusX="1"/>
                                    <Rectangle Grid.Column="1" Fill="#A5FFFFFF" Height="18" Margin="3,0,3,0" RadiusY="1" Grid.Row="0" RadiusX="1" Width="19"/>
                                    <Popup x:Name="PART_Popup" AllowsTransparency="True" Placement="Bottom" PlacementTarget="{Binding ElementName=PART_TextBox}" StaysOpen="False"/>
                                </Grid>
                            </Grid>
                        </Border>
                        <ControlTemplate.Triggers>
                            <DataTrigger Binding="{Binding Source={x:Static SystemParameters.HighContrast}}" Value="false">
                                <Setter Property="Foreground" TargetName="PART_TextBox" Value="{Binding Foreground, RelativeSource={RelativeSource TemplatedParent}}"/>
                            </DataTrigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </DatePicker.Resources>
    


    `

  • Charlos889Charlos889 Member ✭✭
    edited March 7

    Again, thanks for your help! @JohnHardman

  • JohnHardmanJohnHardman GBUniversity mod

    @Charlos889 said:
    Again, thanks for your help! @JohnHardman

    Glad you got it working. I must have been in need of coffee when I responded about UWP instead of WPF :-)

  • larsvlarsv Member ✭✭

    @JohnHardman said:

    @Charlos889 said:
    Again, thanks for your help! @JohnHardman

    Glad you got it working. I must have been in need of coffee when I responded about UWP instead of WPF :-)

    Just to clarify for readers, the above solution works for WPF DatePicker (System.Windows.Controls.DatePicker) but not for UWP Datepicker (Windows.UI.XAML.Controls.DatePicker), as the UWP DatePicker does not have an exposed IsDropDownOpen property.

    I'm looking into finding a solution for UWP DatePicker. My current approach is to try to trigger a space keystroke programmatically once focus is received. Still looking for a mechanism to do that in my UWP custom renderer.

Sign In or Register to comment.