一、前言

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

查看代码

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

五、小结

实现的过程还是比较曲折的,需要了解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)的更多相关文章

  1. WPF Timeline简易时间轴控件的实现

    原文:WPF Timeline简易时间轴控件的实现 效果图: 由于整个控件是实现之后才写的教程,因此这里记录的代码是最终实现后的,前后会引用到其他的一些依赖属性或者代码,需要阅读整篇文章. 1.确定T ...

  2. [转载]ExtJs4 笔记(8) Ext.slider 滚轴控件、 Ext.ProgressBar 进度条控件、 Ext.Editor 编辑控件

    作者:李盼(Lipan)出处:[Lipan] (http://www.cnblogs.com/lipan/)版权声明:本文的版权归作者与博客园共有.转载时须注明本文的详细链接,否则作者将保留追究其法律 ...

  3. ExtJs4 笔记(8) Ext.slider 滚轴控件、 Ext.ProgressBar 进度条控件、 Ext.Editor 编辑控件

    本篇要登场的有三个控件,分别是滚轴控件.进度条控件和编辑控件. 一.滚轴控件 Ext.slider 1.滚轴控件的定义 下面我们定义三个具有代表意义滚轴控件,分别展示滚轴横向.纵向,以及单值.多值选择 ...

  4. SNF开发平台WinForm之十五-时间轴控件使用-SNF快速开发平台3.3-Spring.Net.Framework

    一.显示效果如下: 二.在控件库里选择UCTimeAxis 拖拽到窗体里. 三.加入以下代码,在load事件里进行调用就可以运行了. #region 给时间轴控件加载数据 private void U ...

  5. Wpf使用Winform控件后Wpf元素被Winform控件遮盖问题的解决

    有人会说不建议Wpf中使用Winform控件,有人会说建议使用Winform控件在Wpf下的替代方案,然而在实际工作中由于项目的特殊需求,考虑到时间.成本等因素,往往难免会碰到在WPF中使用Winfr ...

  6. 深入理解MVC C#+HtmlAgilityPack+Dapper走一波爬虫 StackExchange.Redis 二次封装 C# WPF 用MediaElement控件实现视频循环播放 net 异步与同步

    深入理解MVC   MVC无人不知,可很多程序员对MVC的概念的理解似乎有误,换言之他们一直在错用MVC,尽管即使如此软件也能被写出来,然而软件内部代码的组织方式却是不科学的,这会影响到软件的可维护性 ...

  7. WPF范围选择控件(RangeSelector)

    原文:WPF范围选择控件(RangeSelector) 版权声明:本文为博主原创文章,转载请注明作者和出处 https://blog.csdn.net/ZZZWWWPPP11199988899/art ...

  8. WPF的Timer控件的使用

    原文:WPF的Timer控件的使用 通过System.Threaing.Timer控件来实现“初始加载页面时为DataGrid的模版列赋初始值” System.Threaing.Timer的用法: 步 ...

  9. 潜移默化学会WPF(难点控件treeview)--改造TreeView(CheckBox多选择版本),递归绑定数据

    原文:潜移默化学会WPF(难点控件treeview)--改造TreeView(CheckBox多选择版本),递归绑定数据 目前自己对treeview的感慨很多 今天先讲 面对这种 表结构的数据 的其中 ...

随机推荐

  1. DjangoForm 提交验证

    用户提交数据的验证 1.创建模版 -- class LoginForm(forms.Form):.... 2.将请求交给模版,创建一个对象 -- obj = LoginForm(request.POS ...

  2. Confluence 6.9.0 安装

    平台环境:centos 7.6 数据库版本:mysql-5.7.26,提前安装好,安装步骤略. 软件版本:Confluence6.9.0 所需软件:提前下载到本地电脑 atlassian-conflu ...

  3. IDEA结合Maven的profile构建不同开发环境(SpringBoot)

    一.概述 在开发过程中,我们的项目会存在不同的开发环境,比如开发环境.生产环境.测试环境,而我们的项目在不同的环境中有些配置也是不一样的,比如数据源配置.日志文件配置等,假如我们每次将软件部署到不同的 ...

  4. Pycharm工具使用和安装

    1.安装包:https://pan.baidu.com/s/1O9JwuowlodhTR1m0VaKmhg 2.双击安装包安装: 3.选择安装目录 4.安装选项,Create Associations ...

  5. 【oracle】lpad函数 作用(填充)

  6. Linux学习笔记-第5天- 坚持去做一件对的事

    坚持去做一件对的事情,并完成它.不要再给自己留遗憾了,人生已如此,是时候应该做出点改变了.

  7. 原生js-input框全选

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  8. requests--重定向,序列化

    重定向 默认情况下,除了 HEAD, Requests 会自动处理所有重定向.可以使用响应对象的 history 方法来追踪重定向. Response.history 是一个 Response 对象的 ...

  9. 怎么删除STL容器的元素

    在STL容器有顺序容器和关联容器两种. 顺序容器删除元素的方法有两种: 1.c.erase(p) 从c中删除迭代器p指定的元素.p必须指向c中一个真实元素,不能等于c.end().返回一个指向p之后元 ...

  10. [LeetCode] 486. Predict the Winner 预测赢家

    Given an array of scores that are non-negative integers. Player 1 picks one of the numbers from eith ...