I am new to WPF and Control Templating.

I have a MaterialDesignFloatingHintTextBox which displays a "Date of Birth" hint to my users when it is empty.

Like this

Here is what I want to implement: the TextBox to display three TextBoxes to the user when clicked in like this, so that my users enter the date part they know:

Like this

This is because my Users sometimes only know some parts of the date such as Year and Month Only, That was why i didn't use the DatePicker. The other thing that became hard to implement was the TextBox to display to the Use the date as following When it has a value:

Full Date

Month and Year

Year Only

Here is what I tried.

XAML:

<Window x:Class="DateTextBoxApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:DateTextBoxApp"
        xmlns:materialdesign="http://materialdesigninxaml.net/winfx/xaml/themes"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Dark.xaml"/>
                <ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Defaults.xaml"/>
                <ResourceDictionary Source="pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Primary/MaterialDesignColor.Green.xaml"/>
                <ResourceDictionary Source="pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Accent/MaterialDesignColor.Lime.xaml"/>
            </ResourceDictionary.MergedDictionaries>
            <Style x:Key="NameTextBox" TargetType="TextBox" BasedOn="{StaticResource MaterialDesignFloatingHintTextBox}">
                <Setter Property="materialdesign:HintAssist.HintOpacity" Value="0.35"/>
                <Setter Property="materialdesign:ColorZoneAssist.Mode" Value="Dark"/>
                <Setter Property="materialdesign:HintAssist.HintOpacity" Value="0.35"/>
                <Setter Property="materialdesign:HintAssist.FontFamily" Value="Century Gothic"/>
                <Setter Property="Foreground" Value="AliceBlue"/>
                <Setter Property="FontSize" Value="20"/>
            </Style>
            <Style x:Key="DateTextBox" TargetType="TextBox" BasedOn="{StaticResource MaterialDesignFloatingHintTextBox}">
                <Setter Property="materialdesign:HintAssist.HintOpacity" Value="0.35"/>
                <Setter Property="materialdesign:ColorZoneAssist.Mode" Value="Dark"/>
                <Setter Property="materialdesign:HintAssist.HintOpacity" Value="0.35"/>
                <Setter Property="materialdesign:HintAssist.FontFamily" Value="Century Gothic"/>
                <Setter Property="Foreground" Value="AliceBlue"/>
                <Setter Property="FontSize" Value="20"/>
                <Style.Triggers>
                    <Trigger Property="IsKeyboardFocused" Value="True">
                        <Setter Property="Template">
                            <Setter.Value>
                                <ControlTemplate TargetType="TextBox">
                                    <Grid>
                                        <Grid.ColumnDefinitions>
                                           <ColumnDefinition Width="*"/>
                                            <ColumnDefinition Width="*"/>
                                            <ColumnDefinition Width="*"/>
                                        </Grid.ColumnDefinitions>
                                        <TextBox x:Name="DayTextBox" Grid.Column="0" Text="{Binding Date, StringFormat='dd'}" TextAlignment="Center" Width="80"
                                                 materialdesign:HintAssist.Hint="Day" Style="{StaticResource MaterialDesignFloatingHintTextBox}"/>
                                        <TextBox x:Name="MonthTextBox"  Grid.Column="1" Text="{Binding Date, StringFormat='MM'}" TextAlignment="Center" Width="80"
                                                 materialdesign:HintAssist.Hint="Month" Style="{StaticResource MaterialDesignFloatingHintTextBox}"/>
                                        <TextBox x:Name="YearTextBox"  Grid.Column="2" Text="{Binding Date, StringFormat='yyyy'}" TextAlignment="Center"  Width="160"
                                                 materialdesign:HintAssist.Hint="Year" Style="{StaticResource MaterialDesignFloatingHintTextBox}"/>
                                    </Grid>
                                        <!-- Set the keyboard focus to the DayTextBox -->
                                        <ControlTemplate.Triggers>
                                            <Trigger Property="IsKeyboardFocused" Value="True">
                                                <Setter Property="FocusManager.FocusedElement" Value="{Binding ElementName=DayTextBox}" />
                                            </Trigger>
                                        </ControlTemplate.Triggers>
                                    </ControlTemplate>
                                </Setter.Value>
                            </Setter>
                        </Trigger>
                    </Style.Triggers>
                </Style>
        </ResourceDictionary>
    </Window.Resources>

    <Grid>
        <Grid.Background>
            <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                <GradientStop Color="#FF06013F" Offset="1"/>
                <GradientStop Color="#FF040F2E"/>
            </LinearGradientBrush>
        </Grid.Background>
        <StackPanel Margin="30" Orientation="Horizontal" Height="80">
            <TextBox Style="{StaticResource NameTextBox}" Width="100" materialdesign:HintAssist.Hint="FirstName" Margin="0 0 50 0"/>
            <TextBox Style="{StaticResource DateTextBox}" Text="{Binding DOB}" Width="200" materialdesign:HintAssist.Hint="Date Of Birth"/>
        </StackPanel>
    </Grid>
</Window>

But the problem is that the Textboxes don't accept Inputs, Why? and How can I achieve this?

Here is an animation of the error

1

There are 1 best solutions below

6
EldHasp On

What type of Date property is in your ViewModel? I suspect it's DateTime. It will not be possible to enter it in parts. Try this simple example to understand it:

    public class DateMaimViewModel
    {
        public DateTime Date { get; set; } = DateTime.Now;
    }
    <StackPanel>
        <FrameworkElement.DataContext>
            <local:DateMaimViewModel/>
        </FrameworkElement.DataContext>
        <TextBox Text="{Binding Date, StringFormat='MM'}"/>
        <TextBlock Text="{Binding Date}"/>
    </StackPanel>

Try using your custom type with an implementation like this:

    public class OnlyDate : ViewModelBase
    {
        public int? Year { get => Get<int>(); set => Set(value); }
        public int? Mount { get => Get<int>(); set => Set(value); }
        public int? Day { get => Get<int>(); set => Set(value); }

        public bool IsCorrect { get => Get<bool>(); private set => Set(value); }

        protected override void OnPropertyChanged(string propertyName, object? oldValue, object? newValue)
        {
            base.OnPropertyChanged(propertyName, oldValue, newValue);

            int year = Year ?? 1;
            int month = Mount ?? 1;
            int day = Day ?? 1;

            try
            {
                DateTime date = new DateTime(year, month, day);
                IsCorrect = true;
            }
            catch
            {
                IsCorrect = false;
            }
        }
    }

You asked in the comments how to create the viewModel. Here is a more complete example:

    public class DateMaimViewModel
    {
        public OnlyDate Date { get;} = new OnlyDate();
    }

    public class OnlyDate : ViewModelBase
    {
        public int? Year { get => Get<int?>(); set => Set(value); }
        public int? Mount { get => Get<int?>(); set => Set(value); }
        public int? Day { get => Get<int?>(); set => Set(value); }

        public bool NotCorrect { get => Get<bool>(); private set => Set(value); }

        public DateTime Date { get => Get<DateTime>(); private set => Set(value.Date); }

        protected override void OnPropertyChanged(string propertyName, object? oldValue, object? newValue)
        {
            base.OnPropertyChanged(propertyName, oldValue, newValue);

            if (propertyName is nameof(Year) or nameof(Mount) or nameof(Day))
            {
                int year = Year ?? 1;
                int month = Mount ?? 1;
                int day = Day ?? 1;

                try
                {
                    DateTime date = new DateTime(year, month, day);
                    NotCorrect = false;
                    Date = date;
                }
                catch
                {
                    NotCorrect = true;
                }
            }
        }
    }
    <StackPanel>
        <FrameworkElement.DataContext>
            <local:DateMaimViewModel/>
        </FrameworkElement.DataContext>
        <Border Margin="5" BorderThickness="2"
                DataContext="{Binding Date}">
            <Border.Style>
                <Style TargetType="Border">
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding NotCorrect}"
                                        Value="true">
                            <Setter Property="BorderBrush" Value="Red"/>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </Border.Style>
            <StackPanel Orientation="Horizontal">
                <TextBox Margin="5" MinWidth="30" HorizontalContentAlignment="Center"
                            Text="{Binding Day}"/>
                <TextBox Margin="5" MinWidth="30" HorizontalContentAlignment="Center"
                            Text="{Binding Mount}"/>
                <TextBox Margin="5" MinWidth="30" HorizontalContentAlignment="Center"
                            Text="{Binding Year}"/>
            </StackPanel>
        </Border>
        <TextBlock Margin="5" Text="{Binding Date.Date}"/>
    </StackPanel>

P.S. The implementation details depend on what base class you use for the ViewModel and the GUI styling you want.

P.S.S. If you use the "Community Toolkit" then it has TextBoxExtensions, with which you can also customize the TextBox.