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

一、前言

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

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

 

二、要实现的功能

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

三、WFP实现原理

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

Xaml代码如下:

WPF 时间编辑控件的实现(TimeEditer)
 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代码如下:

WPF 时间编辑控件的实现(TimeEditer)
  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 }
查看代码

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

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

五、小结

实现的过程还是比较曲折的,需要了解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

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

上一篇:C#3.0中的新增功能10 表达式树 02 说明


下一篇:windows开了防火墙 symanetc be备份老失败?symantecbe使用哪些端口?