• WPF 时间编辑控件的实现(TimeEditer)


    一、前言

    有个项目需要用到时间编辑控件,在大量搜索无果后只能自己自定义一个了。MFC中倒是有这个控件,叫CDateTimeCtrl。大概是这个样子:

    二、要实现的功能

    • 要实现的功能包含:
    • 编辑时、分、秒(可按数字键输入编辑)
    • 获取焦点后可实现递增或递减

    三、WFP实现原理

    四个TextBox和两个TextBlock组和,再加两个按钮应该就能组成这个控件的基本结构了。再设置焦点事件及按键事件可以实现编辑。

    Xaml代码如下:

     1 <Style TargetType="{x:Type controls:TimeEditer}">
     2         <Setter Property="BorderThickness" Value="1"/>
     3         <Setter Property="BorderBrush" Value="#ececec"/>
     4         <Setter Property="Hour" Value="00"/>
     5         <Setter Property="Minute" Value="00"/>
     6         <Setter Property="Second" Value="00"/>
     7         <Setter Property="Template">
     8             <Setter.Value>
     9                 <ControlTemplate TargetType="{x:Type controls:TimeEditer}">
    10                     <Border Background="{TemplateBinding Background}"
    11                             BorderBrush="{TemplateBinding BorderBrush}"
    12                             BorderThickness="{TemplateBinding BorderThickness}">
    13                         <Grid Margin="3 0">
    14                             <Grid.ColumnDefinitions>
    15                                 <ColumnDefinition Width="18"/>
    16                                 <ColumnDefinition Width="auto"/>
    17                                 <ColumnDefinition Width="18"/>
    18                                 <ColumnDefinition Width="auto"/>
    19                                 <ColumnDefinition Width="18"/>
    20                                 <ColumnDefinition Width="*"/>
    21                             </Grid.ColumnDefinitions>
    22                             <TextBox x:Name="PART_TXTHOUR" HorizontalContentAlignment="Center" Cursor="Arrow" BorderThickness="0" SelectionBrush="White" AutoWordSelection="False" Text="{Binding Hour,RelativeSource={RelativeSource TemplatedParent},StringFormat={}{0:00},UpdateSourceTrigger=PropertyChanged}" Foreground="Black" Focusable="True" IsReadOnly="True" IsReadOnlyCaretVisible="False"  VerticalAlignment="Center"/>
    23                             <TextBlock Text=":" VerticalAlignment="Center" Grid.Column="1"/>
    24                             <TextBox x:Name="PART_TXTMINUTE" HorizontalContentAlignment="Center" Cursor="Arrow" Grid.Column="2" BorderThickness="0" AutoWordSelection="False" Text="{Binding Minute,RelativeSource={RelativeSource TemplatedParent},StringFormat={}{0:00},UpdateSourceTrigger=PropertyChanged}" Foreground="Black" Focusable="True" IsReadOnly="True" IsReadOnlyCaretVisible="False" VerticalAlignment="Center"/>
    25                             <TextBlock Text=":" VerticalAlignment="Center" Grid.Column="3"/>
    26                             <TextBox x:Name="PART_TXTSECOND" HorizontalContentAlignment="Center" Cursor="Arrow" Grid.Column="4" BorderThickness="0" AutoWordSelection="False" Text="{Binding Second,RelativeSource={RelativeSource TemplatedParent},StringFormat={}{0:00},UpdateSourceTrigger=PropertyChanged}" Foreground="Black" Focusable="True" IsReadOnly="True" IsReadOnlyCaretVisible="False"  VerticalAlignment="Center"/>
    27                             <TextBox x:Name="PART_TXT4" Grid.Column="5" Background="Transparent" BorderThickness="0" IsReadOnly="True" AutoWordSelection="False" IsReadOnlyCaretVisible="False" Cursor="Arrow" />
    28 
    29                             <Grid Grid.Column="5" HorizontalAlignment="Right" x:Name="numIncrease" Visibility="{TemplateBinding NumIncreaseVisible}">
    30                                 <Grid.RowDefinitions>
    31                                     <RowDefinition/>
    32                                     <RowDefinition/>
    33                                 </Grid.RowDefinitions>
    34                                 <controls:ButtonEx x:Name="PART_UP" Focusable="False"  ButtonType="Icon" Icon="/BaseControl;component/Images/arrowTop.png" Width="17" Height="11" VerticalAlignment="Bottom"/>
    35                                 <controls:ButtonEx x:Name="PART_DOWN" Focusable="False" ButtonType="Icon" Icon="/BaseControl;component/Images/arrowBottom.png"  Width="17" Height="11" VerticalAlignment="Top" Grid.Row="1"/>
    36                             </Grid>
    37                         </Grid>
    38                     </Border>
    39                 </ControlTemplate>
    40            </Setter.Value>
    41       </Setter>
    42 </Style>
    XAML

    cs代码如下:

      1 public class TimeEditer : Control
      2     {
      3         static TimeEditer()
      4         {
      5             DefaultStyleKeyProperty.OverrideMetadata(typeof(TimeEditer), new FrameworkPropertyMetadata(typeof(TimeEditer)));
      6         }
      7 
      8         private TextBox currentTextBox;
      9         private Button btnUp;
     10         private Button btnDown;
     11         private TextBox txt1;
     12         private TextBox txt2;
     13         private TextBox txt3;
     14         private TextBox txt4;
     15 
     16         public TimeEditer()
     17         {
     18             var newTime = DateTime.Now.AddMinutes(5);
     19             Hour = newTime.Hour;
     20             Minute= newTime.Minute;
     21             Second= newTime.Second;
     22         }
     23 
     24         public override void OnApplyTemplate()
     25         {
     26             base.OnApplyTemplate();
     27             btnUp =Template.FindName("PART_UP",this) as Button;
     28             btnDown = Template.FindName("PART_DOWN", this) as Button;
     29             txt1 = Template.FindName("PART_TXTHOUR", this) as TextBox;
     30             txt2 = Template.FindName("PART_TXTMINUTE", this) as TextBox;
     31             txt3 = Template.FindName("PART_TXTSECOND", this) as TextBox;
     32             txt4 = Template.FindName("PART_TXT4", this) as TextBox;
     33 
     34 
     35             txt1.GotFocus += TextBox_GotFocus;
     36             txt2.GotFocus += TextBox_GotFocus;
     37             txt3.GotFocus += TextBox_GotFocus;
     38             txt1.LostFocus += TextBox_LostFocus;
     39             txt2.LostFocus += TextBox_LostFocus;
     40             txt3.LostFocus += TextBox_LostFocus;
     41             txt1.KeyDown += Txt1_KeyDown;
     42             txt2.KeyDown += Txt1_KeyDown;
     43             txt3.KeyDown += Txt1_KeyDown;
     44 
     45             txt4.GotFocus += TextBox2_GotFocus;
     46             txt4.LostFocus += TextBox2_LostFocus;
     47 
     48             this.GotFocus += UserControl_GotFocus;
     49             this.LostFocus += UserControl_LostFocus;
     50 
     51             this.Repeater(btnUp, (t, num, reset) =>
     52             {
     53                 if (reset && t.Interval == 500)
     54                     t.Interval = 50;
     55                 Dispatcher.Invoke(new Action(() =>
     56                 {
     57                     if (currentTextBox.Name == "PART_TXTHOUR")
     58                     {
     59                         int.TryParse(currentTextBox.Text, out int numResult);
     60                         numResult += num;
     61                         if (numResult >= 24)
     62                             numResult = 0;
     63                         if (numResult < 0)
     64                             numResult = 23;
     65                         currentTextBox.Text = numResult.ToString("00");
     66                     }
     67                     else if (currentTextBox.Name == "PART_TXTMINUTE")
     68                     {
     69                         int.TryParse(currentTextBox.Text, out int numResult);
     70                         numResult += num;
     71                         if (numResult >= 60)
     72                             numResult = 0;
     73                         if (numResult < 0)
     74                             numResult = 59;
     75                         currentTextBox.Text = numResult.ToString("00");
     76                     }
     77                     else if (currentTextBox.Name == "PART_TXTSECOND")
     78                     {
     79                         int.TryParse(currentTextBox.Text, out int numResult);
     80                         numResult += num;
     81                         if (numResult >= 60)
     82                             numResult = 0;
     83                         if (numResult < 0)
     84                             numResult = 59;
     85                         currentTextBox.Text = numResult.ToString("00");
     86                     }
     87                 }));
     88 
     89             }, 1);
     90             this.Repeater(btnDown, (t, num, reset) =>
     91             {
     92                 if (reset && t.Interval == 500)
     93                     t.Interval = 50;
     94                 Dispatcher.Invoke(new Action(() =>
     95                 {
     96                     if (currentTextBox.Name == "PART_TXTHOUR")
     97                     {
     98                         int.TryParse(currentTextBox.Text, out int numResult);
     99                         numResult += num;
    100                         if (numResult >= 24)
    101                             numResult = 0;
    102                         if (numResult < 0)
    103                             numResult = 23;
    104                         currentTextBox.Text = numResult.ToString("00");
    105                     }
    106                     else if (currentTextBox.Name == "PART_TXTMINUTE")
    107                     {
    108                         int.TryParse(currentTextBox.Text, out int numResult);
    109                         numResult += num;
    110                         if (numResult >= 60)
    111                             numResult = 0;
    112                         if (numResult < 0)
    113                             numResult = 59;
    114                         currentTextBox.Text = numResult.ToString("00");
    115                     }
    116                     else if (currentTextBox.Name == "PART_TXTSECOND")
    117                     {
    118                         int.TryParse(currentTextBox.Text, out int numResult);
    119                         numResult += num;
    120                         if (numResult >= 60)
    121                             numResult = 0;
    122                         if (numResult < 0)
    123                             numResult = 59;
    124                         currentTextBox.Text = numResult.ToString("00");
    125                     }
    126                 }));
    127 
    128             }, -1);
    129         }
    130         private void TextBox_GotFocus(object sender, RoutedEventArgs e)
    131         {
    132             var textBox = sender as TextBox;
    133             textBox.Background = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#0078d7"));
    134             textBox.Foreground = new SolidColorBrush(Colors.White);
    135             currentTextBox = textBox;
    136 
    137         }
    138 
    139         private void TextBox_LostFocus(object sender, RoutedEventArgs e)
    140         {
    141             var textBox = sender as TextBox;
    142             textBox.Background = new SolidColorBrush(Colors.Transparent);
    143             textBox.Foreground = new SolidColorBrush(Colors.Black);
    144             int.TryParse(currentTextBox.Text, out int numResult);
    145             currentTextBox.Text = numResult.ToString("00");
    146         }
    147 
    148         private void UserControl_LostFocus(object sender, RoutedEventArgs e)
    149         {
    150             this.BorderBrush = new SolidColorBrush(Color.FromArgb(255, 234, 234, 234));
    151             NumIncreaseVisible = Visibility.Collapsed;
    152         }
    153 
    154         private void UserControl_GotFocus(object sender, RoutedEventArgs e)
    155         {
    156             this.BorderBrush = new SolidColorBrush(Color.FromArgb(255, 0, 120, 215));
    157             NumIncreaseVisible = Visibility.Visible;
    158         }
    159 
    160         private void TextBox2_GotFocus(object sender, RoutedEventArgs e)
    161         {
    162             txt3.Focus();
    163         }
    164 
    165         private void TextBox2_LostFocus(object sender, RoutedEventArgs e)
    166         {
    167 
    168         }
    169 
    170         public void Repeater(Button btn, Action<Timer, int, bool> callBack, int num, int interval = 500)
    171         {
    172             var timer = new Timer { Interval = interval };
    173             timer.Elapsed += (sender, e) =>
    174             {
    175                 callBack?.Invoke(timer, num, true);
    176             };
    177             btn.PreviewMouseLeftButtonDown += (sender, e) =>
    178             {
    179                 callBack?.Invoke(timer, num, false);
    180                 timer.Start();
    181             };
    182             btn.PreviewMouseLeftButtonUp += (sender, e) =>
    183             {
    184                 timer.Interval = 500;
    185                 timer.Stop();
    186             };
    187         }
    188 
    189         private void Txt1_KeyDown(object sender, KeyEventArgs e)
    190         {
    191             int.TryParse(currentTextBox.Text, out int numResult);
    192 
    193             if ((int)e.Key >= 34 && (int)e.Key <= 43)
    194             {
    195 
    196                 if (currentTextBox.Text.Length == 1)
    197                 {
    198                     int.TryParse(currentTextBox.Text + ((int)e.Key - 34).ToString(), out int preNumResult);
    199                     if (currentTextBox.Name == "PART_TXTHOUR")
    200                     {
    201                         if (preNumResult >= 24)
    202                         {
    203                             return;
    204                         }
    205                     }
    206                     else if (currentTextBox.Name == "PART_TXTMINUTE")
    207                     {
    208                         if (preNumResult >= 60)
    209                         {
    210                             return;
    211                         }
    212                     }
    213                     else if (currentTextBox.Name == "PART_TXTSECOND")
    214                     {
    215                         if (preNumResult >= 60)
    216                         {
    217                             return;
    218                         }
    219                     }
    220 
    221                     currentTextBox.Text += ((int)e.Key - 34).ToString();
    222                 }
    223                 else
    224                 {
    225                     currentTextBox.Text = ((int)e.Key - 34).ToString();
    226                 }
    227             }
    228 
    229             if ((int)e.Key >= 74 && (int)e.Key <= 83)
    230             {
    231 
    232                 if (currentTextBox.Text.Length == 1)
    233                 {
    234                     int.TryParse(currentTextBox.Text + ((int)e.Key - 74).ToString(), out int preNumResult);
    235                     if (currentTextBox.Name == "PART_TXTHOUR")
    236                     {
    237                         if (preNumResult >= 24)
    238                         {
    239                             return;
    240                         }
    241                     }
    242                     else if (currentTextBox.Name == "PART_TXTMINUTE")
    243                     {
    244                         if (preNumResult >= 60)
    245                         {
    246                             return;
    247                         }
    248                     }
    249                     else if (currentTextBox.Name == "PART_TXTSECOND")
    250                     {
    251                         if (preNumResult >= 60)
    252                         {
    253                             return;
    254                         }
    255                     }
    256                     currentTextBox.Text += ((int)e.Key - 74).ToString();
    257                 }
    258                 else
    259                 {
    260                     currentTextBox.Text = ((int)e.Key - 74).ToString();
    261                 }
    262             }
    263 
    264         }
    265 
    266 
    267 
    268         public int Hour
    269         {
    270             get { return (int)GetValue(HourProperty); }
    271             set { SetValue(HourProperty, value); }
    272         }
    273 
    274         // Using a DependencyProperty as the backing store for Hour.  This enables animation, styling, binding, etc...
    275         public static readonly DependencyProperty HourProperty =
    276             DependencyProperty.Register("Hour", typeof(int), typeof(TimeEditer), new PropertyMetadata(DateTime.Now.Hour));
    277 
    278 
    279         public int Minute
    280         {
    281             get { return (int)GetValue(MinuteProperty); }
    282             set { SetValue(MinuteProperty, value); }
    283         }
    284 
    285         // Using a DependencyProperty as the backing store for Minute.  This enables animation, styling, binding, etc...
    286         public static readonly DependencyProperty MinuteProperty =
    287             DependencyProperty.Register("Minute", typeof(int), typeof(TimeEditer), new PropertyMetadata(DateTime.Now.Minute));
    288 
    289 
    290         public int Second
    291         {
    292             get { return (int)GetValue(SecondProperty); }
    293             set { SetValue(SecondProperty, value); }
    294         }
    295 
    296         // Using a DependencyProperty as the backing store for Second.  This enables animation, styling, binding, etc...
    297         public static readonly DependencyProperty SecondProperty =
    298             DependencyProperty.Register("Second", typeof(int), typeof(TimeEditer), new PropertyMetadata(DateTime.Now.Second));
    299 
    300 
    301 
    302         public Visibility NumIncreaseVisible
    303         {
    304             get { return (Visibility)GetValue(NumIncreaseVisibleProperty); }
    305             set { SetValue(NumIncreaseVisibleProperty, value); }
    306         }
    307 
    308         // Using a DependencyProperty as the backing store for NumIncreaseVisible.  This enables animation, styling, binding, etc...
    309         public static readonly DependencyProperty NumIncreaseVisibleProperty =
    310             DependencyProperty.Register("NumIncreaseVisible", typeof(Visibility), typeof(TimeEditer), new PropertyMetadata(Visibility.Collapsed));
    311 
    312 
    313 }
    查看代码

    四、实现效果(可双向绑定)

     

    五、小结

    实现的过程还是比较曲折的,需要了解TextBox相关属性,刚开始不清楚走了很多弯路,相关属性可以在这里查看https://docs.microsoft.com/zh-cn/dotnet/api/system.windows.controls.textbox?view=netframework-4.8 。另外一个就是实现数字递增递减的方案,刚开始始终没法实现快速递增和递减,只能开线程匀速增减,还很慢,太快的话又会影响单次点击的效果。最后是使用Timmer控件,通过修改Interval实现了,哈哈。

    源码放git了:

    https://github.com/cmfGit/TimeEditer.git

  • 相关阅读:
    break和continue
    while循环嵌套
    while循环语句
    SDUT 2766-小明传奇2(母函数)
    那些奇妙的&quot;大师&quot;是怎样炼成的(科学、迷信、心理)
    深入理解Linux字符设备驱动
    [从头学数学] 第162节 锐角三角函数
    iOS将数组中的内容分拼接成字符串
    win10 UWP 全屏
    杂(三)-The type java.lang.Object cannot be resolved It is indirectly referenced ...
  • 原文地址:https://www.cnblogs.com/xiaomingg/p/11180355.html
Copyright © 2020-2023  润新知