wpf 自定义mask datepicker
注意如果设置了系统dateformat格式。在变化系统culture的时候需要设置dateformat
CultureInfo info = new CultureInfo(this.LanguageItems[value].Name); info.DateTimeFormat.ShortDatePattern= "dd/MM/yyyy"; info.DateTimeFormat.LongDatePattern = "dd/MM/yyyy"; Thread.CurrentThread.CurrentCulture = info; Thread.CurrentThread.CurrentUICulture = info;
在textbox中设置
InputMethod.IsInputMethodEnabled=“false”;可以禁止textbox禁用IME开关,(做到不允许中文字符的输入)
// 若要将光标置于 TextBox 控件的内容的开头,应调用 Select 方法,并指定选择内容的起始位置为 0,选择长度为 0。
//1 textBox1.Select(0, 0);
//若要将光标置于 TextBox 控件的内容的末尾,应调用 Select 方法,并指定选择内容的起始位置等于文本内容的长度,选择长度为 0。
//1 textBox1.Select(textBox1.Text.Length, 0);
//若要将光标置于 TextBox 控件的内容的当前位置,应调用 Select 方法,并指定选择内容的起始位置等于光标的当前位置,选择长度为 0。
//1 textBox1.Select(textBox1.SelectionStart, 0);
//原因是通过鼠标让TextBox获得输入焦点时,TextBox触发的事件顺序是:MouseDown->GotFocus->MouseUp,
// 也就是说TextBox在鼠标按下的那一刻已经获得了输入焦点,此时可以对Select(0, 0)设置焦点位置。
// 但郁闷的是,MouseUp却会取消TextBox状态...也就是说文本焦点其实曾经被设置了,但立即又被取消(-_-#)
textbox 用鼠标获得焦点的顺序是 mouseDown GotFocus mouseUP. mouseup会把gotFocus设置的select取消
private void MyTextBox_GotFocus(object sender, RoutedEventArgs e) { this.MyTextBox.Tag = true; if (this.SelectedDate != null) { this.MyTextBox.Text = string.Format("{0:dd/MM/yyyy}", this.SelectedDate.GetValueOrDefault()); } else { UpdateInputMask(); // this.MyTextBox.Text = "__/__/____"; this.MyTextBox.Select(0, 0); } }
所有在mouseup的时候要设置
private void MyTextBox_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e) { if (this.SelectedDate==null) { if (e.ChangedButton == MouseButton.Left && (bool)this.MyTextBox.Tag == true) { this.MyTextBox.Select(0, 0); } this.MyTextBox.Tag = false; } }
使用tag来标记。。是否是左击鼠标获得焦点。。获得之后就不在设置焦点位置
样式
:
<!--Control colors.--> <Color x:Key="WindowColor">#FFE8EDF9</Color> <Color x:Key="ContentAreaColorLight">#FFC5CBF9</Color> <Color x:Key="ContentAreaColorDark">#FF7381F9</Color> <Color x:Key="DisabledControlLightColor">#FFE8EDF9</Color> <Color x:Key="DisabledControlDarkColor">#FFC5CBF9</Color> <Color x:Key="DisabledForegroundColor">#FF888888</Color> <Color x:Key="SelectedBackgroundColor">#FFC5CBF9</Color> <Color x:Key="SelectedUnfocusedColor">#FFDDDDDD</Color> <Color x:Key="ControlLightColor">White</Color> <Color x:Key="ControlMediumColor">#FF7381F9</Color> <Color x:Key="ControlDarkColor">#FF211AA9</Color> <Color x:Key="ControlMouseOverColor">#FF3843C4</Color> <Color x:Key="ControlPressedColor">#FF211AA9</Color> <Color x:Key="GlyphColor">#FF444444</Color> <Color x:Key="GlyphMouseOver">sc#1, 0.004391443, 0.002428215, 0.242281124</Color> <!--Border colors--> <Color x:Key="BorderLightColor">#FFCCCCCC</Color> <Color x:Key="BorderMediumColor">#FF888888</Color> <Color x:Key="BorderDarkColor">#FF444444</Color> <Color x:Key="PressedBorderLightColor">#FF888888</Color> <Color x:Key="PressedBorderDarkColor">#FF444444</Color> <Color x:Key="DisabledBorderLightColor">#FFAAAAAA</Color> <Color x:Key="DisabledBorderDarkColor">#FF888888</Color> <Color x:Key="DefaultBorderBrushDarkColor">Black</Color> <!--Control-specific resources.--> <Color x:Key="HeaderTopColor">#FFC5CBF9</Color> <Color x:Key="DatagridCurrentCellBorderColor">Black</Color> <Color x:Key="SliderTrackDarkColor">#FFC5CBF9</Color> <Color x:Key="NavButtonFrameColor">#FF3843C4</Color> <LinearGradientBrush x:Key="MenuPopupBrush" EndPoint="0.5,1" StartPoint="0.5,0"> <GradientStop Color="{DynamicResource ControlLightColor}" Offset="0" /> <GradientStop Color="{DynamicResource ControlMediumColor}" Offset="0.5" /> <GradientStop Color="{DynamicResource ControlLightColor}" Offset="1" /> </LinearGradientBrush> <LinearGradientBrush x:Key="ProgressBarIndicatorAnimatedFill" StartPoint="0,0" EndPoint="1,0"> <LinearGradientBrush.GradientStops> <GradientStopCollection> <GradientStop Color="#000000FF" Offset="0" /> <GradientStop Color="#600000FF" Offset="0.4" /> <GradientStop Color="#600000FF" Offset="0.6" /> <GradientStop Color="#000000FF" Offset="1" /> </GradientStopCollection> </LinearGradientBrush.GradientStops> </LinearGradientBrush> <Style x:Key="DatePickerCalendarStyle" TargetType="{x:Type Calendar}" BasedOn="{StaticResource {x:Type Calendar}}" /> <!--The template for the button that displays the calendar.--> <Style x:Key="DropDownButtonStyle" TargetType="{x:Type Button}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate 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> <ColorAnimationUsingKeyFrames BeginTime="0" Duration="00:00:00.001" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Border.Background). (GradientBrush.GradientStops)[1].(GradientStop.Color)"> <SplineColorKeyFrame KeyTime="0" Value="#F2FFFFFF" /> </ColorAnimationUsingKeyFrames> <ColorAnimationUsingKeyFrames BeginTime="0" Duration="00:00:00.001" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Border.Background). (GradientBrush.GradientStops)[2].(GradientStop.Color)"> <SplineColorKeyFrame KeyTime="0" Value="#CCFFFFFF" /> </ColorAnimationUsingKeyFrames> <ColorAnimation Duration="0" To="#FF448DCA" Storyboard.TargetProperty="(Border.Background). (SolidColorBrush.Color)" Storyboard.TargetName="Background" /> <ColorAnimationUsingKeyFrames BeginTime="0" Duration="00:00:00.001" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Border.Background). (GradientBrush.GradientStops)[3].(GradientStop.Color)"> <SplineColorKeyFrame KeyTime="0" Value="#7FFFFFFF" /> </ColorAnimationUsingKeyFrames> </Storyboard>--> </VisualState> <VisualState x:Name="Pressed"> <!--<Storyboard> <ColorAnimationUsingKeyFrames BeginTime="0" Duration="00:00:00.001" Storyboard.TargetName="Background" Storyboard.TargetProperty="(Border.Background). (SolidColorBrush.Color)"> <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.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Border.Background). (GradientBrush.GradientStops)[0].(GradientStop.Color)"> <SplineColorKeyFrame KeyTime="0" Value="#F4FFFFFF" /> </ColorAnimationUsingKeyFrames> <ColorAnimationUsingKeyFrames BeginTime="0" Duration="00:00:00.001" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Border.Background). (GradientBrush.GradientStops)[1].(GradientStop.Color)"> <SplineColorKeyFrame KeyTime="0" Value="#EAFFFFFF" /> </ColorAnimationUsingKeyFrames> <ColorAnimationUsingKeyFrames BeginTime="0" Duration="00:00:00.001" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Border.Background). (GradientBrush.GradientStops)[2].(GradientStop.Color)"> <SplineColorKeyFrame KeyTime="0" Value="#C6FFFFFF" /> </ColorAnimationUsingKeyFrames> <ColorAnimationUsingKeyFrames BeginTime="0" Duration="00:00:00.001" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Border.Background). (GradientBrush.GradientStops)[3].(GradientStop.Color)"> <SplineColorKeyFrame KeyTime="0" Value="#6BFFFFFF" /> </ColorAnimationUsingKeyFrames> </Storyboard>--> </VisualState> <VisualState x:Name="Disabled" /> </VisualStateGroup> <VisualStateGroup x:Name="FocusStates"/> <VisualStateGroup x:Name="ValidationStates"/> </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> <Image Grid.ColumnSpan="4" Margin="0" Grid.RowSpan="4" Source="Image\icon_datepicker.png" Stretch="none"/> <!--<Rectangle Grid.ColumnSpan="4" Grid.RowSpan="1" StrokeThickness="1"> <Rectangle.Fill> <LinearGradientBrush EndPoint="0,1" StartPoint="0,0"> <GradientStop Color="{DynamicResource HeaderTopColor}" /> <GradientStop Color="{DynamicResource ControlMediumColor}" 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>--> <!--<Ellipse Grid.ColumnSpan="4" Fill="#FFFFFFFF" HorizontalAlignment="Center" Height="3" StrokeThickness="0" VerticalAlignment="Center" Width="3" d:IsHidden="True" />--> </Grid> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style> <Style x:Key="MyDatePicker" TargetType="{x:Type DatePicker}"> <Setter Property="Foreground" Value="#FF333333" /> <Setter Property="IsTodayHighlighted" Value="True" /> <Setter Property="SelectedDateFormat" Value="Short" /> <Setter Property="Padding" Value="2" /> <Setter Property="BorderThickness" Value="1" /> <Setter Property="HorizontalContentAlignment" Value="Stretch" /> <!--Set CalendarStyle to DatePickerCalendarStyle.--> <Setter Property="CalendarStyle" Value="{DynamicResource DatePickerCalendarStyle}" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type DatePicker}"> <Border BorderThickness="1" Padding="{TemplateBinding Padding}" BorderBrush="#5d7fad" Background="White" CornerRadius="5" SnapsToDevicePixels="True" Height="28" x:Name="DatePickerBorder"> <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> <VisualStateGroup x:Name="ValidationStates"/> </VisualStateManager.VisualStateGroups> <Grid x:Name="PART_Root" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="Center"> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="Auto" /> </Grid.ColumnDefinitions> <Button x:Name="PART_Button" Grid.Column="1" Foreground="#0c223a" Focusable="False" HorizontalAlignment="Left" Margin="5,0,5,0" Grid.Row="0" Style="{StaticResource DropDownButtonStyle}" VerticalAlignment="Top" /> <TextBox x:Name="PART_MyTextBox" Grid.Column="0" BorderThickness="0" Foreground="#0c223a" Focusable="{TemplateBinding Focusable}" HorizontalContentAlignment="Stretch" InputMethod.IsInputMethodEnabled="False" Grid.Row="0" VerticalContentAlignment="Stretch" FontFamily="Arial" FontSize="12" VerticalAlignment="Center" > </TextBox> <DatePickerTextBox x:Name="PART_TextBox" Visibility="Hidden" Grid.Column="0" Foreground="#0c223a" Focusable="{TemplateBinding Focusable}" HorizontalContentAlignment="Stretch" Grid.Row="0" VerticalContentAlignment="Stretch" FontFamily="Arial" FontSize="12" VerticalAlignment="Center" Style="{DynamicResource DatePickerTextBoxStyle1}" > <DatePickerTextBox.Resources> <Style x:Key="DatePickerTextBoxStyle1" TargetType="{x:Type DatePickerTextBox}"> <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}"/> <Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/> <Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst"/> <Setter Property="Stylus.IsFlicksEnabled" Value="False"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type DatePickerTextBox}"> <Grid> <Grid.Resources> <SolidColorBrush x:Key="WatermarkBrush" Color="#FFAAAAAA"/> </Grid.Resources> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="CommonStates"> <VisualStateGroup.Transitions> <VisualTransition GeneratedDuration="0"/> <VisualTransition GeneratedDuration="0:0:0.1" To="MouseOver"/> </VisualStateGroup.Transitions> <VisualState x:Name="Normal"/> <VisualState x:Name="MouseOver"> <Storyboard> <ColorAnimation Duration="0" To="#FF99C1E2" Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)" Storyboard.TargetName="ContentElement"/> <ColorAnimation Duration="0" To="#FF99C1E2" Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)" Storyboard.TargetName="watermark_decorator"/> </Storyboard> </VisualState> </VisualStateGroup> <VisualStateGroup x:Name="WatermarkStates"> <VisualStateGroup.Transitions> <VisualTransition GeneratedDuration="0"/> </VisualStateGroup.Transitions> <VisualState x:Name="Unwatermarked"/> <VisualState x:Name="Watermarked"> <Storyboard> <DoubleAnimation Duration="0" To="0" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="ContentElement"/> <DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="PART_Watermark"/> </Storyboard> </VisualState> </VisualStateGroup> <VisualStateGroup x:Name="FocusStates"> <VisualStateGroup.Transitions> <VisualTransition GeneratedDuration="0"/> </VisualStateGroup.Transitions> <VisualState x:Name="Unfocused"/> <VisualState x:Name="Focused"> <Storyboard> <DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="FocusVisual"/> </Storyboard> </VisualState> </VisualStateGroup> <VisualStateGroup x:Name="ValidationStates"/> </VisualStateManager.VisualStateGroups> <Border x:Name="Border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" CornerRadius="1" Opacity="1" Padding="{TemplateBinding Padding}"> <Grid x:Name="WatermarkContent" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"> <Border x:Name="ContentElement" BorderBrush="#FFFFFFFF" BorderThickness="0"/> <Border x:Name="watermark_decorator" BorderBrush="#FFFFFFFF" BorderThickness="0"> <ContentControl x:Name="PART_Watermark" Focusable="False" IsHitTestVisible="False" Opacity="0" Padding="2" Style="{DynamicResource ContentControlStyle1}"> <ContentControl.Resources> <Style x:Key="ContentControlStyle1" TargetType="{x:Type ContentControl}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ContentControl}"> <ContentPresenter Visibility="Collapsed"/> </ControlTemplate> </Setter.Value> </Setter> </Style> </ContentControl.Resources> </ContentControl> </Border> <ScrollViewer x:Name="PART_ContentHost" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="0" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" Template="{DynamicResource ScrollViewerControlTemplate1}"> <ScrollViewer.Resources> <ControlTemplate x:Key="ScrollViewerControlTemplate1" TargetType="{x:Type ScrollViewer}"> <Grid x:Name="Grid" Background="{TemplateBinding Background}"> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"/> <ColumnDefinition Width="Auto"/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="*"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <Rectangle x:Name="Corner" Grid.Column="1" Fill="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" Grid.Row="1"/> <ScrollContentPresenter x:Name="PART_ScrollContentPresenter" CanContentScroll="{TemplateBinding CanContentScroll}" CanHorizontallyScroll="False" CanVerticallyScroll="False" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" Grid.Column="0" Margin="{TemplateBinding Padding}" Grid.Row="0"/> <ScrollBar x:Name="PART_VerticalScrollBar" AutomationProperties.AutomationId="VerticalScrollBar" Cursor="Arrow" Grid.Column="1" Maximum="{TemplateBinding ScrollableHeight}" Minimum="0" Grid.Row="0" Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}" Value="{Binding VerticalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" ViewportSize="{TemplateBinding ViewportHeight}"/> <ScrollBar x:Name="PART_HorizontalScrollBar" AutomationProperties.AutomationId="HorizontalScrollBar" Cursor="Arrow" Grid.Column="0" Maximum="{TemplateBinding ScrollableWidth}" Minimum="0" Orientation="Horizontal" Grid.Row="1" Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}" Value="{Binding HorizontalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" ViewportSize="{TemplateBinding ViewportWidth}"/> </Grid> </ControlTemplate> </ScrollViewer.Resources> </ScrollViewer> <Border x:Name="FocusVisual" BorderBrush="#FF45D6FA" CornerRadius="1" IsHitTestVisible="False" Opacity="0"/> </Grid> </Border> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style> </DatePickerTextBox.Resources> </DatePickerTextBox> <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="3" Grid.Row="0" RadiusX="3" /> <Rectangle Grid.Column="1" Fill="#A5FFFFFF" Height="18" Margin="5,0,5,0" RadiusY="3" Grid.Row="0" RadiusX="3" Width="19" /> <Popup x:Name="PART_Popup" AllowsTransparency="True" Placement="Bottom" PlacementTarget="{Binding ElementName=PART_TextBox}" StaysOpen="False" /> </Grid> </Grid> </Border> <ControlTemplate.Triggers> <Trigger Property="Validation.HasError" Value="True"> <Setter TargetName="DatePickerBorder" Property="BorderBrush" Value="Red"></Setter> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style>
cs.
using MCE.Gems.Common.Common; using MCE.Gems.Common.Events; using Microsoft.Practices.Prism.Events; using Microsoft.Practices.ServiceLocation; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Input; namespace MCE.Gems.Common.Controls.CustomControls { public class CustomDatePicker : DatePicker { #region Field // private static IEventAggregator eventAggregator = ServiceLocator.Current.GetInstance<IEventAggregator>(); private List<InputMaskChar> _maskChars; private static string currentWaterMark; private bool isTextChanged; private int _caretIndex; #endregion #region Property /// <summary> /// Gets the text box in charge of the editable portion of the MyDatePicker. /// </summary> protected TextBox MyTextBox { get { return base.GetTemplateChild("PART_MyTextBox") as TextBox; } } public string WaterMark { get { return (string)GetValue(WaterMarkProperty); } set { SetValue(WaterMarkProperty, value); } } // Using a DependencyProperty as the backing store for WaterMark. This enables animation, styling, binding, etc... public static readonly DependencyProperty WaterMarkProperty = DependencyProperty.Register("WaterMark", typeof(string), typeof(CustomDatePicker), new PropertyMetadata(null,new PropertyChangedCallback(OnWaterMarkChanged))); public DateTime DefaultCalendarDisplayDate { get { return (DateTime)GetValue(DefaultCalendarDisplayDateProperty); } set { SetValue(DefaultCalendarDisplayDateProperty, value); } } // Using a DependencyProperty as the backing store for DefaultCalendarDisplayDate. This enables animation, styling, binding, etc... public static readonly DependencyProperty DefaultCalendarDisplayDateProperty = DependencyProperty.Register("DefaultCalendarDisplayDate", typeof(DateTime), typeof(CustomDatePicker), new PropertyMetadata(null)); /// <summary> /// Invoked when the WaterMark dependency property reports a change. /// </summary> /// <param name="obj"></param> /// <param name="e"></param> private static void OnWaterMarkChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) { CustomDatePicker cdp = obj as CustomDatePicker; if (cdp.SelectedDate==null&&cdp.MyTextBox!=null) { cdp.MyTextBox.Text = e.NewValue.ToString(); } currentWaterMark = e.NewValue.ToString(); } #endregion static CustomDatePicker() { DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomDatePicker), new FrameworkPropertyMetadata(typeof(CustomDatePicker))); } public CustomDatePicker() { // eventAggregator.GetEvent<UpdateLanguageEvent>().Subscribe(UpdateMaskLanguage); this._maskChars = new List<InputMaskChar>(); } public override void OnApplyTemplate() { base.OnApplyTemplate(); //load the text box control if (this.MyTextBox != null) { this.MyTextBox.GotFocus += new RoutedEventHandler(MyTextBox_GotFocus); this.MyTextBox.LostFocus += new RoutedEventHandler(MyTextBox_LostFocus); this.MyTextBox.TextChanged += new TextChangedEventHandler(MyTextBox_TextChanged); this.MyTextBox.PreviewMouseLeftButtonUp += new MouseButtonEventHandler(MyTextBox_PreviewMouseLeftButtonUp); this.MyTextBox.PreviewKeyDown += MyTextBox_PreviewKeyDown; this.MyTextBox.PreviewTextInput += MyTextBox_PreviewTextInput; this.MyTextBox.SelectionChanged += MyTextBox_SelectionChanged; //default this.MyTextBox.Text = currentWaterMark; this.SelectedDateChanged += CustomDatePicker_SelectedDateChanged; this.Loaded += CustomDatePicker_Loaded; DataObject.AddPastingHandler(this, new DataObjectPastingEventHandler(MyTextBox_Paste)); //UpdateInputMask(); } } void CustomDatePicker_SelectedDateChanged(object sender, SelectionChangedEventArgs e) { //if (this.IsLoaded) //{ if (this.SelectedDate != null) { this.MyTextBox.Text = string.Format("{0:dd/MM/yyyy}", this.SelectedDate.GetValueOrDefault()); UpdateInputMask(); this._caretIndex = 10; this.MyTextBox.CaretIndex = 10; } else { this.MyTextBox.Text = WaterMark; } //} // this.DisplayDate = DateTime.Today; } void CustomDatePicker_Loaded(object sender, RoutedEventArgs e) { if (this.SelectedDate != null) { this.MyTextBox.Text = string.Format("{0:dd/MM/yyyy}", this.SelectedDate.GetValueOrDefault()); } else { // this.MyTextBox.Text = WaterMark; } } void MyTextBox_SelectionChanged(object sender, RoutedEventArgs e) { if (!isTextChanged) { TextBox t = (TextBox)sender; this._caretIndex = t.CaretIndex; } isTextChanged = false; } /// <summary> /// Invokes when a paste event is raised. /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void MyTextBox_Paste(object sender, DataObjectPastingEventArgs e) { //TODO: play nicely here? // if (e.DataObject.GetDataPresent(typeof(string))) { string value = e.DataObject.GetData(typeof(string)).ToString(); string displayText; if (this.ValidateTextInternal(value, out displayText)) { this.MyTextBox.Text = displayText; } } e.CancelCommand(); } private void MyTextBox_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e) { if (this.SelectedDate == null) { if (e.ChangedButton == MouseButton.Left && (bool)this.MyTextBox.Tag == true) { this.MyTextBox.Select(0, 0); } this.MyTextBox.Tag = false; } } private void MyTextBox_TextChanged(object sender, TextChangedEventArgs e) { isTextChanged = true; if (string.IsNullOrWhiteSpace(this.MyTextBox.Text)) { this.SelectedDate = null; this.MyTextBox.Text = WaterMark; } } private void MyTextBox_LostFocus(object sender, RoutedEventArgs e) { if (this.MyTextBox.Text != null) { try { this.SelectedDate = DateTime.Parse(this.MyTextBox.Text); //this.DisplayDate = DateTime.Today; } catch (Exception) { this.SelectedDate = null; this.MyTextBox.Text = WaterMark; // this.MyTextBox.Text = null; } } if (this.DefaultCalendarDisplayDate != null) { this.DisplayDate = this.DefaultCalendarDisplayDate; } //if (this.SelectedDate != null) //{ // this.MyTextBox.Text = string.Format("{0:dd/MM/yyyy}", this.SelectedDate.GetValueOrDefault()); //} //else //{ // //this.MyTextBox.Text = WaterMark; //} } private void MyTextBox_GotFocus(object sender, RoutedEventArgs e) { this.MyTextBox.Tag = true; if (this.SelectedDate != null) { this.MyTextBox.Text = string.Format("{0:dd/MM/yyyy}", this.SelectedDate.GetValueOrDefault()); } else { UpdateInputMask(); // this.MyTextBox.Text = "__/__/____"; this.MyTextBox.Select(0, 0); } } void MyTextBox_PreviewKeyDown(object sender, KeyEventArgs e) { //no mask specified, just function as a normal textbox if (this._maskChars.Count == 0) return; if (e.Key == Key.Delete) { //delete key pressed: delete all text this.MyTextBox.Text = this.GetDefaultText(); this._caretIndex = this.MyTextBox.CaretIndex = 0; e.Handled = true; } else { //backspace key pressed if (e.Key == Key.Back) { if (this._caretIndex > 0 || this.MyTextBox.SelectionLength > 0) { if (this.MyTextBox.SelectionLength > 0) { //if one or more characters selected, delete them this.DeleteSelectedText(); } else { //if no characters selected, shift the caret back to the previous non-literal char and delete it this.MoveBack(); char[] characters = this.MyTextBox.Text.ToCharArray(); characters[this._caretIndex] = this._maskChars[this._caretIndex].GetDefaultChar(); this.MyTextBox.Text = new string(characters); } //update the base class caret index, and swallow the event this.MyTextBox.CaretIndex = this._caretIndex; e.Handled = true; } } else if (e.Key == Key.Left) { //move back to the previous non-literal character this.MoveBack(); e.Handled = true; } else if (e.Key == Key.Right || e.Key == Key.Space) { //move forwards to the next non-literal character this.MoveForward(); e.Handled = true; } } } void MyTextBox_PreviewTextInput(object sender, TextCompositionEventArgs e) { if (this._maskChars.Count == 0) return; this._caretIndex = this.MyTextBox.CaretIndex = this.MyTextBox.SelectionStart; if (this._caretIndex == this._maskChars.Count) { //at the end of the character count defined by the input mask- no more characters allowed e.Handled = true; } else { //validate the character against its validation scheme bool isValid = this.ValidateInputChar(char.Parse(e.Text), this._maskChars[this._caretIndex].ValidationFlags); if (isValid) { //delete any selected text if (this.MyTextBox.SelectionLength > 0) { this.DeleteSelectedText(); } //insert the new character char[] characters = this.MyTextBox.Text.ToCharArray(); characters[this._caretIndex] = char.Parse(e.Text); this.MyTextBox.Text = new string(characters); //move the caret on this.MoveForward(); } e.Handled = true; } } #region validateMethod protected virtual bool IsPlaceholderChar(char character, out InputMaskValidationFlags validationFlags) { validationFlags = InputMaskValidationFlags.None; switch (character.ToString().ToUpper()) { case "I": validationFlags = InputMaskValidationFlags.AllowInteger; break; case "D": validationFlags = InputMaskValidationFlags.AllowDecimal; break; case "A": validationFlags = InputMaskValidationFlags.AllowAlphabet; break; case "W": validationFlags = (InputMaskValidationFlags.AllowAlphanumeric); break; } return (validationFlags != InputMaskValidationFlags.None); } /// <summary> /// Returns a value indicating if the current text value is valid. /// </summary> /// <returns></returns> public virtual bool ValidateTextInternal(string text, out string displayText) { if (this._maskChars.Count == 0) { displayText = text; return true; } StringBuilder displayTextBuilder = new StringBuilder(this.GetDefaultText()); bool valid = (!string.IsNullOrEmpty(text) && text.Length <= this._maskChars.Count); if (valid) { for (int i = 0; i < text.Length; i++) { if (!this._maskChars[i].IsLiteral()) { if (this.ValidateInputChar(text[i], this._maskChars[i].ValidationFlags)) { displayTextBuilder[i] = text[i]; } else { valid = false; } } } } displayText = displayTextBuilder.ToString(); return valid; } /// <summary> /// Validates the specified character against all selected validation schemes. /// </summary> /// <param name="input"></param> /// <param name="validationFlags"></param> /// <returns></returns> protected virtual bool ValidateInputChar(char input, InputMaskValidationFlags validationFlags) { bool valid = (validationFlags == InputMaskValidationFlags.None); if (!valid) { Array values = Enum.GetValues(typeof(InputMaskValidationFlags)); //iterate through the validation schemes foreach (object o in values) { InputMaskValidationFlags instance = (InputMaskValidationFlags)(int)o; if ((instance & validationFlags) != 0) { if (this.ValidateCharInternal(input, instance)) { valid = true; break; } } } } return valid; } /// <summary> /// Validates the specified character against its input mask validation scheme. /// </summary> /// <param name="input"></param> /// <param name="validationType"></param> /// <returns></returns> private bool ValidateCharInternal(char input, InputMaskValidationFlags validationType) { bool valid = false; switch (validationType) { case InputMaskValidationFlags.AllowInteger: case InputMaskValidationFlags.AllowDecimal: int i; if (validationType == InputMaskValidationFlags.AllowDecimal && input == '.' && !this.MyTextBox.Text.Contains('.')) { valid = true; } else { valid = int.TryParse(input.ToString(), out i); } break; case InputMaskValidationFlags.AllowAlphabet: valid = char.IsLetter(input); break; case InputMaskValidationFlags.AllowAlphanumeric: valid = (char.IsLetter(input) || char.IsNumber(input)); break; } return valid; } #endregion #region Method public void UpdateInputMask() { string text = this.MyTextBox.Text; this._maskChars.Clear(); // this.MyTextBox.Text = string.Empty; string mask = "ii/ii/iiii"; //if (string.IsNullOrEmpty(mask)) // return; this._maskChars.Add(new InputMaskChar() { Literal = (char)0, DefaultChar = 'd', ValidationFlags = InputMaskValidationFlags.AllowInteger }); this._maskChars.Add(new InputMaskChar() { Literal = (char)0, DefaultChar = 'd', ValidationFlags = InputMaskValidationFlags.AllowInteger }); this._maskChars.Add(new InputMaskChar() { Literal = (char)1, DefaultChar = '/', ValidationFlags = InputMaskValidationFlags.None }); this._maskChars.Add(new InputMaskChar() { Literal = (char)0, DefaultChar = 'M', ValidationFlags = InputMaskValidationFlags.AllowInteger }); this._maskChars.Add(new InputMaskChar() { Literal = (char)0, DefaultChar = 'M', ValidationFlags = InputMaskValidationFlags.AllowInteger }); this._maskChars.Add(new InputMaskChar() { Literal = (char)1, DefaultChar = '/', ValidationFlags = InputMaskValidationFlags.None }); this._maskChars.Add(new InputMaskChar() { Literal = (char)0, DefaultChar = 'y', ValidationFlags = InputMaskValidationFlags.AllowInteger }); this._maskChars.Add(new InputMaskChar() { Literal = (char)0, DefaultChar = 'y', ValidationFlags = InputMaskValidationFlags.AllowInteger }); this._maskChars.Add(new InputMaskChar() { Literal = (char)0, DefaultChar = 'y', ValidationFlags = InputMaskValidationFlags.AllowInteger }); this._maskChars.Add(new InputMaskChar() { Literal = (char)0, DefaultChar = 'y', ValidationFlags = InputMaskValidationFlags.AllowInteger }); //InputMaskValidationFlags validationFlags = InputMaskValidationFlags.None; //for (int i = 0; i < mask.Length; i++) //{ // bool isPlaceholder = this.IsPlaceholderChar(mask[i], out validationFlags); // if (isPlaceholder) // { // this._maskChars.Add(new InputMaskChar(validationFlags)); // } // else // { // this._maskChars.Add(new InputMaskChar(mask[i])); // } //} string displayText; if (text.Length > 0 && this.ValidateTextInternal(text, out displayText)) { this.MyTextBox.Text = displayText; } else { this.MyTextBox.Text = this.GetDefaultText(); } } private void UpdateMaskLanguage(string obj) { if (this.SelectedDate == null) { //this.MyTextBox.Text = WaterMark; } } public string GetDefaultText() { StringBuilder text = new StringBuilder(); foreach (InputMaskChar maskChar in this._maskChars) { text.Append(maskChar.GetDefaultChar()); } return text.ToString(); } /// <summary> /// Moves the caret forward to the next non-literal position. /// </summary> private void MoveForward() { int pos = this._caretIndex; while (pos < this._maskChars.Count) { if (++pos == this._maskChars.Count || !this._maskChars[pos].IsLiteral()) { this._caretIndex = this.MyTextBox.CaretIndex = pos; break; } } } /// <summary> /// Moves the caret backward to the previous non-literal position. /// </summary> private void MoveBack() { int pos = this._caretIndex; while (pos > 0) { if (--pos == 0 || !this._maskChars[pos].IsLiteral()) { this._caretIndex = this.MyTextBox.CaretIndex = pos; break; } } } /// <summary> /// Deletes the currently selected text. /// </summary> protected virtual void DeleteSelectedText() { StringBuilder text = new StringBuilder(this.MyTextBox.Text); string defaultText = this.GetDefaultText(); int selectionStart = this.MyTextBox.SelectionStart; int selectionLength = this.MyTextBox.SelectionLength; text.Remove(selectionStart, selectionLength); text.Insert(selectionStart, defaultText.Substring(selectionStart, selectionLength)); this.MyTextBox.Text = text.ToString(); //reset the caret position this.MyTextBox.CaretIndex = this._caretIndex = selectionStart; } #endregion [Flags] protected enum InputMaskValidationFlags { None = 0, AllowInteger = 1, AllowDecimal = 2, AllowAlphabet = 4, AllowAlphanumeric = 8 } private class InputMaskChar { private InputMaskValidationFlags _validationFlags; private char _literal; public char DefaultChar { get; set; } public InputMaskChar(InputMaskValidationFlags validationFlags) { this._validationFlags = validationFlags; this._literal = (char)0; } public InputMaskChar(char literal) { this._literal = literal; } public InputMaskChar() { } public InputMaskValidationFlags ValidationFlags { get { return this._validationFlags; } set { this._validationFlags = value; } } public char Literal { get { return this._literal; } set { this._literal = value; } } public bool IsLiteral() { return (this._literal != (char)0); } public char GetDefaultChar() { return DefaultChar; } } } }