【小学期】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事件
  • 使用DispatchertimerTimeSpan实现计时功能
    • 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
      • 第二行BorderBorder内为ContentPresenter
  • 要想实现TabControlTabItem平均分布,需要把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为20
  • CalculateProgress()
    • 进度条由一个短划线和一个空白间隔组成
    • 第一个参数的计算
      • 笔刷的粗细为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
      • 绑定TextTag
      • 设置触发器条件,当Text没有输入时,显示Tag
    • 在使用时,Tag的内容为水印,TextBoxText的值为用户输入的内容

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

WPF

SQLite

posted @ 2022-02-11 13:56  空白4869  阅读(140)  评论(0编辑  收藏  举报