【小学期】WPF番茄钟
时间
8月16日:iOS和MacOS没入门就放弃
8月17日:WinForm入门,C#入门
8月18日:WinForm放弃,WPF准备入门
8月19日:XAML入门,TabControl样式
8月20日:计时器页面诞生且完成,番茄钟页面诞生但样式乱七八糟
8月21日:番茄钟页面圆形进度条
8月22日:TextBox实现水印,番茄钟页面的逻辑结构(是一直不明原因死循环
8月23日:番茄钟页面完成,百度搜索数据库但毫无结果
8月24日:数据库与DataGrid成功绑定,历史记录页面完成
8月25日:尝试使用DataGrid.RowDetails,失败
8月26日:第二个窗口诞生
8月27日:开始摸鱼
8月28日:尝试安装部署,失败且放弃,杜撰开发文档
8月29日:今天是8月29日
细节
计时功能
- 参考C#简易计时器WPF
- 一个
Label
显示时间 - 三个
Button
实现开始、暂停、重置功能,每个Button
都有Click
事件 - 使用
Dispatchertimer
和TimeSpan
实现计时功能public class Dispatchertimer
:计时器timer2.Interval
:设置计时器间隔timer2.Tick
:当每个计时器间隔结束时发生start()
stop()
public struct TimeSpan
:时间间隔TimeSpan(Int32, Int32, Int32)
:初始化(时,分,秒)Add(TimeSpan)
:返回一个新的TimeSpan
对象,其值是指定的TimeSpan
对象与此实例的总和Substract(TimeSpan)
:... ... 的差FromSeconds(Double)
:返回表示指定秒数的TimeSpan
XAML
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="3*"/>
<RowDefinition Height="16*"/>
<RowDefinition Height="2*"/>
<RowDefinition Height="4*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="9*"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<Border Grid.Row="1" Grid.Column="1" CornerRadius="50">
<Border.Background>
<SolidColorBrush Color="#404040" Opacity="0.3"/>
</Border.Background>
<Viewbox>
<Label Content="00 : 00 : 00"
VerticalAlignment="Center" HorizontalAlignment="Center"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
FontWeight="Bold" Foreground="#F9DD8E"
Name="timeshow2"/>
</Viewbox>
</Border>
<StackPanel Grid.Row="3" Grid.Column="1"
Orientation="Horizontal"
HorizontalAlignment="Center" VerticalAlignment="Center" >
<Button Style="{StaticResource ButtonStart}" Content="开 始"
FontSize="17" FontWeight="Bold"
Name="start2" Click="StartClick2" IsEnabled="True"/>
<Button Style="{StaticResource ButtonStart}" Content="暂 停"
FontSize="17" FontWeight="Bold"
Name="stop2" Click="StopClick2" IsEnabled="False"/>
<Button Style="{StaticResource ButtonStart}" Content="重 置"
FontSize="17" FontWeight="Bold"
Name="reset2" Click="ResetClick2" IsEnabled="False"/>
</StackPanel>
C#
private void Run2(object sender, EventArgs e)
{
timespan2 = timespan2.Add(new TimeSpan(0, 0, 1));
timeshow2.Content = string.Format("{0} : {1} : {2}", timespan2.ToString("hh"), timespan2.ToString("mm"), timespan2.ToString("ss"));
}
private void StartClick2(object sender, RoutedEventArgs e)
{
timer2 = new DispatcherTimer();
timer2.Interval = TimeSpan.FromSeconds(1);
timer2.Tick += Run2;
timer2.Start();
start2.IsEnabled = false;
stop2.IsEnabled = true;
reset2.IsEnabled = true;
}
private void StopClick2(object sender, RoutedEventArgs e)
{
timer2.Stop();
timeshow2.Content = string.Format("{0} : {1} : {2}", timespan2.ToString("hh"), timespan2.ToString("mm"), timespan2.ToString("ss"));
start2.IsEnabled = true;
stop2.IsEnabled = false;
reset2.IsEnabled = true;
}
private void ResetClick2(object sender, RoutedEventArgs e)
{
timer2.Stop();
timespan2 = new TimeSpan(0, 0, 0);
timeshow2.Content = string.Format("{0} : {1} : {2}", timespan2.ToString("hh"), timespan2.ToString("mm"), timespan2.ToString("ss"));
start2.IsEnabled = true;
reset2.IsEnabled = false;
stop2.IsEnabled = false;
}
TabControl样式
- 参考博客园:WPF 自定义TabControl控件样式
- 默认的
TabControl
样式可查看官方文档Grid
两行一列- 第一行
TabPannel
- 第二行
Border
,Border
内为ContentPresenter
- 第一行
- 要想实现
TabControl
的TabItem
平均分布,需要把TabPanel
替换成UniformGrid
UniformGrid
提供一种在网格中排列内容的方法,它使网格中的所有单元格都具有相同的大小
XAML ResourceDictionary
<!--TabControl样式(不包括TabItem样式)-->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabControl}">
<Border Background="{TemplateBinding Background}"
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}" >
<Grid x:Name="templateRoot"
ClipToBounds="True" SnapsToDevicePixels="True"
KeyboardNavigation.TabNavigation="Local">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="ColumnDefinition0"/>
<ColumnDefinition x:Name="ColumnDefinition1" Width="0"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition x:Name="RowDefinition0" Height="Auto"/>
<RowDefinition x:Name="RowDefinition1" Height="*"/>
</Grid.RowDefinitions>
<!--标签-->
<UniformGrid x:Name="HeaderPanel"
Grid.Column="0" Grid.Row="0" Rows="1"
Background="Transparent" Margin="0"
IsItemsHost="True"
KeyboardNavigation.TabIndex="1"
Panel.ZIndex="1"/>
<!--内容-->
<Border x:Name="ContentPanel"
Grid.Column="0" Grid.Row="1"
BorderThickness="0"
Background="{TemplateBinding Background}"
KeyboardNavigation.DirectionalNavigation="Contained"
KeyboardNavigation.TabIndex="2"
KeyboardNavigation.TabNavigation="Local">
<ContentPresenter x:Name="PART_SelectedContentHost"
Margin="0"
ContentSource="SelectedContent"
ContentTemplate
="{TemplateBinding SelectedContentTemplate}"
Content="{TemplateBinding SelectedContent}"
ContentStringFormat
="{TemplateBinding SelectedContentStringFormat}"
SnapsToDevicePixels
="{TemplateBinding SnapsToDevicePixels}"/>
</Border>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
圆形进度条
StrokeDashArray简介
- 两个圆环
Ellipse
(使用Rectangle
同样可以),下方圆环属性不变,上方圆环StrokeDashArray
不断改变,从而模仿进度条 StrokeDashArray
用于绘制虚线- 第一个参数表示短划线的长度,第二个参数表示空白间隔的长度
- 长度的数值是相对与笔的粗细而言,值为1代表创建(短划线或空白间隔的)长度与笔的粗细相同
StrokeDashArray
的值为DoubleCollection
类型,是一系列Double
类型的集合
计算过程:
CalculatePrecent()
计算出当前已经走过的时间占总计需要走的时间的百分比,如预计计时25分钟,当前还剩20分钟时(timespan1
为20分钟),返回的precent
为20CalculateProgress()
- 进度条由一个短划线和一个空白间隔组成
- 第一个参数的计算
- 笔刷的粗细为
StrokeThickness="8"
- 半径取笔刷中心线所在的半径(所以笔刷越粗误差越大)
double radiusReal = radiusTotal - (thickness / 2);
- 由半径可计算出周长,但是长度的数值相对于笔刷粗细,所以真正数值需要再除以笔刷粗细
double perimeter = 2 * Math.PI * radiusReal / thickness;
- 假设
CalculatePrecent()
计算出结果为20,StrokeDashArray
的第一个参数应该为double step = precent / 100 * perimeter;
- 笔刷的粗细为
- 第二个参数取一个极大值,它远超出进度条圆环的周长相对与笔刷粗细的数值
ShowProgress()
将计算的结果返回到用户界面
XAML
<Grid>
<Ellipse Stroke="#404040" StrokeThickness="8" Opacity="0.5"
Width="200" Height="200"/>
<Ellipse Name ="progress"
Width="200" Height="200"
Stroke="#F9DD8E" StrokeThickness="8"
StrokeDashArray="0,1000" RenderTransformOrigin="0.5,0.5" >
<Ellipse.RenderTransform>
<RotateTransform Angle="-90"/>
</Ellipse.RenderTransform>
</Ellipse>
</Grid>
C#
/*调用方法
double precent = CalculatePrecent();
double step = CalculateProgress(precent, progress.Width / 2, progress.StrokeThickness);
ShowProgress(step);
*/
private double CalculatePrecent()
{
double precent = 0;
try
{
double tmp = new double();
if (workRestStatus == 0)
tmp = workTime;//输入的是分钟,数据库中存储的是秒,然后除以100的倍率
else
tmp = restTime;
precent = (tmp - timespan1.TotalSeconds) / tmp * 100;
}
catch
{
precent = 0;
}
return precent;
}
private double CalculateProgress(double precent, double radiusTotal, double thickness)
{
double radiusReal = radiusTotal - (thickness / 2);
double perimeter = 2 * Math.PI * radiusReal / thickness;
double step = precent / 100 * perimeter;
return step;
}
private void (double step)
{
progress.StrokeDashArray = new DoubleCollection() { step, 1000 };
}
TextBox水印
- 默认的
Textboxl
样式可查看官方文档Border
中一个ScrollViewer
- 水印的实现
- 添加一个
TextBlock
- 设置
Visibility
值为Collapsed
- 绑定
Text
到Tag
- 设置触发器条件,当
Text
没有输入时,显示Tag
- 设置
- 在使用时,
Tag
的内容为水印,TextBox
的Text
的值为用户输入的内容
- 添加一个
XAML ResourceDictionary
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Border x:Name="border" Padding="5" CornerRadius="10,10,0,0"
BorderBrush="#7CCCFF" BorderThickness="0,0,0,1"
SnapsToDevicePixels="True">
<Grid>
<ScrollViewer x:Name="PART_ContentHost" Focusable="False"
HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Hidden"/>
<TextBlock x:Name="watertxt"
Text="{TemplateBinding Tag}" Foreground="#7CCCFF"
Visibility="Collapsed" />
</Grid>
</Border>
<ControlTemplate.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="Text" Value=""/>
<Condition Property="IsFocused" Value="False"/>
</MultiTrigger.Conditions>
<Setter Property="Visibility"
TargetName="watertxt"
Value="Visible"/>
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
数据库与DataGrid
数据库:
- 每次操作需进行:
- 打开数据库文件
conn.Open();
- 建立链接
cmd.Connection = conn;
- SQLite语句写
cmd.CommandText
- 执行
cmd.ExecuteNonQuery();
- 打开数据库文件
DataAdapter
:表示用于填充DataSet
和更新数据源的一组SQL命令和数据库连接Fill(DataSet)
:添加或刷新DataSet
的行以匹配数据源中的行
DataSet
:表示内存中的数据缓存DataTable
:表示内存中数据的一个表Load(DataReader)
:使用DataReader
提供的来自数据源的值填充DataTable
,如果DataTable
已经包含行,则来自数据源的传入数据将与现有行合并
DataGrid:
- 设置
ItemsSource="{Binding dataTable}"
- 在C#中
myAdapter.Fill(ds, "dataTable");
从而实现数据绑定
XAML
<DataGrid Grid.Row="1" Width="650"
HeadersVisibility="Column"
Name ="dataGridView"
ItemsSource="{Binding dataTable}"
Template="{DynamicResource DataGridStyle}">
</DataGrid>
C#
private void CreateHistoryTable(string tableName)
{
SQLiteConnection conn = new SQLiteConnection(connStr);
if (conn.State != System.Data.ConnectionState.Open)
{
conn.Open();
SQLiteCommand cmd = new SQLiteCommand();
cmd.Connection = conn;
cmd.CommandText = "CREATE TABLE IF NOT EXISTS " + tableName +
"(开始时间 string PRIMARY KEY, " +
"目标 string, " +
"是否成功 string, " +
"番茄数 int, " +
"总时长 string)";
cmd.ExecuteNonQuery();
}
conn.Close();
}
private void InsertHistoryData(string tableName, string x)
{
SQLiteConnection conn = new SQLiteConnection(connStr);
if (conn.State != System.Data.ConnectionState.Open)
{
conn.Open();
SQLiteCommand cmd = new SQLiteCommand();
cmd.Connection = conn;
cmd.CommandText = "INSERT INTO " + tableName +
"(开始时间, 目标, 是否成功, 番茄数, 总时长) " +
"VALUES(@开始时间, @目标, @是否成功, @番茄数, @总时长)";
cmd.Parameters.Add("开始时间", DbType.String).Value = createTime;
cmd.Parameters.Add("目标", DbType.String).Value = aimSave;
cmd.Parameters.Add("是否成功", DbType.String).Value = x;
cmd.Parameters.Add("番茄数", DbType.Int32).Value = tomatoSave;
cmd.Parameters.Add("总时长", DbType.String).Value = string.Format("{0}分{1}秒", totalSpan.ToString("mm"), totalSpan.ToString("ss"));
cmd.ExecuteNonQuery();
}
conn.Close();
}
private void ShowHistoryData(string tableName)
{
SQLiteConnection conn = new SQLiteConnection(connStr);
if (conn.State != System.Data.ConnectionState.Open)
{
conn.Open();
SQLiteCommand cmd = new SQLiteCommand();
cmd.Connection = conn;
cmd.CommandText = "SELECT * FROM " + tableName + " ORDER BY 开始时间 DESC";
SQLiteDataAdapter myAdapter = new SQLiteDataAdapter(cmd.CommandText, conn);
DataSet ds = new DataSet();
DataTable dt = new DataTable();
myAdapter.Fill(ds, "dataTable");
dataGridView.DataContext = ds;
}
conn.Close();
}
private void DeleteHistoryTable(string tableName)
{
SQLiteConnection conn = new SQLiteConnection(connStr);
if (conn.State != System.Data.ConnectionState.Open)
{
conn.Open();
SQLiteCommand cmd = new SQLiteCommand();
cmd.Connection = conn;
cmd.CommandText = "DROP TABLE IF EXISTS " + tableName;
cmd.ExecuteNonQuery();
}
conn.Close();
}
private void UpdateSettingsData(string tableName, double work, double rest)
{
SQLiteConnection conn = new SQLiteConnection(MainWindow.connStr);
if (conn.State != System.Data.ConnectionState.Open)
{
conn.Open();
SQLiteCommand cmd = new SQLiteCommand();
cmd.Connection = conn;
cmd.CommandText = "UPDATE " + tableName +
" SET Pause=@Pause, History=@History, Work=@Work, Rest=@Rest";
cmd.Parameters.Add("Pause", DbType.Int32).Value = pauseOrNot;
cmd.Parameters.Add("History", DbType.Int32).Value = historyOrNot;
cmd.Parameters.Add("Work", DbType.Double).Value = work;
cmd.Parameters.Add("Rest", DbType.Double).Value = rest;
cmd.ExecuteNonQuery();
}
conn.Close();
}
public static bool IfExitData(string tableName)
{
SQLiteConnection conn = new SQLiteConnection(MainWindow.connStr);
if (conn.State != System.Data.ConnectionState.Open)
{
conn.Open();
SQLiteCommand cmd = new SQLiteCommand();
cmd.Connection = conn;
cmd.CommandText = "SELECT * FROM " + tableName;
SQLiteDataReader dr = cmd.ExecuteReader();
DataTable dt = new DataTable();
dt.Load(dr);
if (dt.Rows.Count > 0)
return true;
else
return false;
}
conn.Close();
return false;
}
第二个窗口ShowDialog
using System.Windows
Show()
:打开一个窗口并返回,不等待新打开的窗口关闭ShowDialog()
:打开一个窗口,仅在新打开的窗口关闭时返回
- 在两个窗口之间穿数据使用了
public
和数据库(反正默认选项需要保存到数据库中)
C#
private void ButtonSettingsClick(object sender, RoutedEventArgs e)
{
Settings settings = new Settings();
settings.ShowDialog();
GetStatus(Settings.dbNameSettings);
timespan1 = workTimespan;
ShowTime(timespan1);
}
打包
参考
WinForm
C#
XAML
- 脚本之家:C#学习之30分钟学会XAML
- GitHub:WPF-UI-Design
- GitHub:WpfDemo(博客园:一叶知秋)
- GitHub:wpf.controls(博客园:WPF自定义控件与样式(安木夕))
- 微软官方文档:控件自定义
WPF
SQLite