一、前言

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

二、要实现的功能

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

三、WFP实现原理

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

Xaml代码如下:

 <Style TargetType="{x:Type controls:TimeEditer}">
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderBrush" Value="#ececec"/>
<Setter Property="Hour" Value="00"/>
<Setter Property="Minute" Value="00"/>
<Setter Property="Second" Value="00"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:TimeEditer}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid Margin="3 0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="18"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="18"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="18"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<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"/>
<TextBlock Text=":" VerticalAlignment="Center" Grid.Column="1"/>
<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"/>
<TextBlock Text=":" VerticalAlignment="Center" Grid.Column="3"/>
<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"/>
<TextBox x:Name="PART_TXT4" Grid.Column="5" Background="Transparent" BorderThickness="0" IsReadOnly="True" AutoWordSelection="False" IsReadOnlyCaretVisible="False" Cursor="Arrow" /> <Grid Grid.Column="5" HorizontalAlignment="Right" x:Name="numIncrease" Visibility="{TemplateBinding NumIncreaseVisible}">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<controls:ButtonEx x:Name="PART_UP" Focusable="False" ButtonType="Icon" Icon="/BaseControl;component/Images/arrowTop.png" Width="17" Height="11" VerticalAlignment="Bottom"/>
<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"/>
</Grid>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

XAML

cs代码如下:

 public class TimeEditer : Control
{
static TimeEditer()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(TimeEditer), new FrameworkPropertyMetadata(typeof(TimeEditer)));
} private TextBox currentTextBox;
private Button btnUp;
private Button btnDown;
private TextBox txt1;
private TextBox txt2;
private TextBox txt3;
private TextBox txt4; public TimeEditer()
{
var newTime = DateTime.Now.AddMinutes();
Hour = newTime.Hour;
Minute= newTime.Minute;
Second= newTime.Second;
} public override void OnApplyTemplate()
{
base.OnApplyTemplate();
btnUp =Template.FindName("PART_UP",this) as Button;
btnDown = Template.FindName("PART_DOWN", this) as Button;
txt1 = Template.FindName("PART_TXTHOUR", this) as TextBox;
txt2 = Template.FindName("PART_TXTMINUTE", this) as TextBox;
txt3 = Template.FindName("PART_TXTSECOND", this) as TextBox;
txt4 = Template.FindName("PART_TXT4", this) as TextBox; txt1.GotFocus += TextBox_GotFocus;
txt2.GotFocus += TextBox_GotFocus;
txt3.GotFocus += TextBox_GotFocus;
txt1.LostFocus += TextBox_LostFocus;
txt2.LostFocus += TextBox_LostFocus;
txt3.LostFocus += TextBox_LostFocus;
txt1.KeyDown += Txt1_KeyDown;
txt2.KeyDown += Txt1_KeyDown;
txt3.KeyDown += Txt1_KeyDown; txt4.GotFocus += TextBox2_GotFocus;
txt4.LostFocus += TextBox2_LostFocus; this.GotFocus += UserControl_GotFocus;
this.LostFocus += UserControl_LostFocus; this.Repeater(btnUp, (t, num, reset) =>
{
if (reset && t.Interval == )
t.Interval = ;
Dispatcher.Invoke(new Action(() =>
{
if (currentTextBox.Name == "PART_TXTHOUR")
{
int.TryParse(currentTextBox.Text, out int numResult);
numResult += num;
if (numResult >= )
numResult = ;
if (numResult < )
numResult = ;
currentTextBox.Text = numResult.ToString("");
}
else if (currentTextBox.Name == "PART_TXTMINUTE")
{
int.TryParse(currentTextBox.Text, out int numResult);
numResult += num;
if (numResult >= )
numResult = ;
if (numResult < )
numResult = ;
currentTextBox.Text = numResult.ToString("");
}
else if (currentTextBox.Name == "PART_TXTSECOND")
{
int.TryParse(currentTextBox.Text, out int numResult);
numResult += num;
if (numResult >= )
numResult = ;
if (numResult < )
numResult = ;
currentTextBox.Text = numResult.ToString("");
}
})); }, );
this.Repeater(btnDown, (t, num, reset) =>
{
if (reset && t.Interval == )
t.Interval = ;
Dispatcher.Invoke(new Action(() =>
{
if (currentTextBox.Name == "PART_TXTHOUR")
{
int.TryParse(currentTextBox.Text, out int numResult);
numResult += num;
if (numResult >= )
numResult = ;
if (numResult < )
numResult = ;
currentTextBox.Text = numResult.ToString("");
}
else if (currentTextBox.Name == "PART_TXTMINUTE")
{
int.TryParse(currentTextBox.Text, out int numResult);
numResult += num;
if (numResult >= )
numResult = ;
if (numResult < )
numResult = ;
currentTextBox.Text = numResult.ToString("");
}
else if (currentTextBox.Name == "PART_TXTSECOND")
{
int.TryParse(currentTextBox.Text, out int numResult);
numResult += num;
if (numResult >= )
numResult = ;
if (numResult < )
numResult = ;
currentTextBox.Text = numResult.ToString("");
}
})); }, -);
}
private void TextBox_GotFocus(object sender, RoutedEventArgs e)
{
var textBox = sender as TextBox;
textBox.Background = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#0078d7"));
textBox.Foreground = new SolidColorBrush(Colors.White);
currentTextBox = textBox; } private void TextBox_LostFocus(object sender, RoutedEventArgs e)
{
var textBox = sender as TextBox;
textBox.Background = new SolidColorBrush(Colors.Transparent);
textBox.Foreground = new SolidColorBrush(Colors.Black);
int.TryParse(currentTextBox.Text, out int numResult);
currentTextBox.Text = numResult.ToString("");
} private void UserControl_LostFocus(object sender, RoutedEventArgs e)
{
this.BorderBrush = new SolidColorBrush(Color.FromArgb(, , , ));
NumIncreaseVisible = Visibility.Collapsed;
} private void UserControl_GotFocus(object sender, RoutedEventArgs e)
{
this.BorderBrush = new SolidColorBrush(Color.FromArgb(, , , ));
NumIncreaseVisible = Visibility.Visible;
} private void TextBox2_GotFocus(object sender, RoutedEventArgs e)
{
txt3.Focus();
} private void TextBox2_LostFocus(object sender, RoutedEventArgs e)
{ } public void Repeater(Button btn, Action<Timer, int, bool> callBack, int num, int interval = )
{
var timer = new Timer { Interval = interval };
timer.Elapsed += (sender, e) =>
{
callBack?.Invoke(timer, num, true);
};
btn.PreviewMouseLeftButtonDown += (sender, e) =>
{
callBack?.Invoke(timer, num, false);
timer.Start();
};
btn.PreviewMouseLeftButtonUp += (sender, e) =>
{
timer.Interval = ;
timer.Stop();
};
} private void Txt1_KeyDown(object sender, KeyEventArgs e)
{
int.TryParse(currentTextBox.Text, out int numResult); if ((int)e.Key >= && (int)e.Key <= )
{ if (currentTextBox.Text.Length == )
{
int.TryParse(currentTextBox.Text + ((int)e.Key - ).ToString(), out int preNumResult);
if (currentTextBox.Name == "PART_TXTHOUR")
{
if (preNumResult >= )
{
return;
}
}
else if (currentTextBox.Name == "PART_TXTMINUTE")
{
if (preNumResult >= )
{
return;
}
}
else if (currentTextBox.Name == "PART_TXTSECOND")
{
if (preNumResult >= )
{
return;
}
} currentTextBox.Text += ((int)e.Key - ).ToString();
}
else
{
currentTextBox.Text = ((int)e.Key - ).ToString();
}
} if ((int)e.Key >= && (int)e.Key <= )
{ if (currentTextBox.Text.Length == )
{
int.TryParse(currentTextBox.Text + ((int)e.Key - ).ToString(), out int preNumResult);
if (currentTextBox.Name == "PART_TXTHOUR")
{
if (preNumResult >= )
{
return;
}
}
else if (currentTextBox.Name == "PART_TXTMINUTE")
{
if (preNumResult >= )
{
return;
}
}
else if (currentTextBox.Name == "PART_TXTSECOND")
{
if (preNumResult >= )
{
return;
}
}
currentTextBox.Text += ((int)e.Key - ).ToString();
}
else
{
currentTextBox.Text = ((int)e.Key - ).ToString();
}
} } public int Hour
{
get { return (int)GetValue(HourProperty); }
set { SetValue(HourProperty, value); }
} // Using a DependencyProperty as the backing store for Hour. This enables animation, styling, binding, etc...
public static readonly DependencyProperty HourProperty =
DependencyProperty.Register("Hour", typeof(int), typeof(TimeEditer), new PropertyMetadata(DateTime.Now.Hour)); public int Minute
{
get { return (int)GetValue(MinuteProperty); }
set { SetValue(MinuteProperty, value); }
} // Using a DependencyProperty as the backing store for Minute. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MinuteProperty =
DependencyProperty.Register("Minute", typeof(int), typeof(TimeEditer), new PropertyMetadata(DateTime.Now.Minute)); public int Second
{
get { return (int)GetValue(SecondProperty); }
set { SetValue(SecondProperty, value); }
} // Using a DependencyProperty as the backing store for Second. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SecondProperty =
DependencyProperty.Register("Second", typeof(int), typeof(TimeEditer), new PropertyMetadata(DateTime.Now.Second)); public Visibility NumIncreaseVisible
{
get { return (Visibility)GetValue(NumIncreaseVisibleProperty); }
set { SetValue(NumIncreaseVisibleProperty, value); }
} // Using a DependencyProperty as the backing store for NumIncreaseVisible. This enables animation, styling, binding, etc...
public static readonly DependencyProperty NumIncreaseVisibleProperty =
DependencyProperty.Register("NumIncreaseVisible", typeof(Visibility), typeof(TimeEditer), new PropertyMetadata(Visibility.Collapsed)); }

查看代码

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

五、小结

实现的过程还是比较曲折的,需要了解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. 白话SCRUM 之三:sprint backlog

    Sprint Backlog就是任务列表,如果映射到传统的项目管理理论中就是WBS(work breakdown structure),而且是典型的采用面向交付物的任务分解方法得到的WBS. 比如有一 ...

  2. Windows下面startup.bat启动Tomcat偶发死锁问题

    Windows下面startup.bat启动Tomcat时,因为日志都打印到了cmd里面,存在偶发卡死Tomcat的问题,该问题确认是Windows系统的问题,而且一直没有解决.解决的办法是把日志重定 ...

  3. Alpha版本1发布

    这个作业属于哪个课程 课程的链接 这个作业的要求在哪里 作业要求的链接 团队名称 西柚三剑客 这个作业的目标 -Alpha版本发布说明 1. 团队成员的学号列表 团队成员 学号 whisperzxx( ...

  4. docker 中,修改了 mysql 配置,如何重启 mysql?

    直接重新启动容器: docker restart containerID/containerName

  5. javascript高级实战学习

    学习目标: - 理解面向对象开发思想 - 掌握 JavaScript 面向对象开发相关模式 - 掌握在 JavaScript 中使用正则表达式 - typora-copy-images-to medi ...

  6. zz在自动驾驶研发中充分发挥数据的潜能

    本次分享内容提纲 数据标注 数据驱动开发 数据驱动决策 前言 上图这是我加入小马智行之前的一个小故事.这不断的提醒我,人工智能需要有足够的数据量,并且充分发挥这些数据的潜能,是我们作为人工智能公司的一 ...

  7. zz扔掉anchor!真正的CenterNet——Objects as Points论文解读

    首发于深度学习那些事 已关注写文章   扔掉anchor!真正的CenterNet——Objects as Points论文解读 OLDPAN 不明觉厉的人工智障程序员 ​关注他 JustDoIT 等 ...

  8. JDOJ 1929: 求最长不下降序列长度

    JDOJ 1929: 求最长不下降序列长度 JDOJ传送门 Description 设有一个正整数的序列:b1,b2,-,bn,对于下标i1<i2<-<im,若有bi1≤bi2≤-≤ ...

  9. USACO Making the Grade

    洛谷 P2893 [USACO08FEB]修路Making the Grade https://www.luogu.org/problemnew/show/P2893 JDOJ 2566: USACO ...

  10. 关于==和equals的区别和联系,面试这么回答就可以

    长篇大论的话,我这里就不多写了,相信大家入门java 的时候就知道个大概了,这里想表述的是,如果面试官问你<关于==和equals的区别>,该怎么回答完美呢?可以这样说 总结的来说: 1) ...