ひとりでのアプリ開発 - fineの備忘録 -

ひとりでアプリ開発をするなかで起こったことや学んだことを書き溜めていきます

C# - Todoアプリの作成

初めに

 .NET Framework を使って、Todo アプリを作る方法をまとめます。

前提知識

 Visual Studio のテンプレートの選び方やツールボックスの使い方など基本的な操作は下の記事でまとめております。本記事では、それらは前提知識としておりますので、わからない場合はまず、下の記事をご覧ください。

fineworks-fine.hatenablog.com

実装する機能

(機能一覧)

  • タスクを入力した日付とタスクの期限、タスクの内容を入力できるようにすること
  • 入力した内容は後から修正できるようにすること
  • タスクを入力した日付は自動で書かれるようにすること
  • 保存先のファイルを1つにし、アプリ起動時に自動で読み込まれるようにすること
  • タスクが完了したら、そのタスクを消せるようにすること
  • 期限の順にソートすること
  • タスクは一覧で期日とともに確認できるようにすること
  • ファイルの保存先がボタンから開けるようにすること

デザイン

 次のようにデザインが構成されています。

(使用ツール)

  1. ボタン4つ
    addButton、editButton、deleteButton、openButton
  2. DateTimePicker
    dueDateTimePicker、期日を入力する用
  3. ListBox
    tasksListBox、タスク一覧を表示する用
  4. TextBox10こ
    descriptionTextBox(タスクを入力する用)
    taskContentTextBox(選択したタスクの内容を表示させる用)
    inputDateTextBox(入力日表示用)
    dueDateTextBox(期日表示用)
    タイトル表示用(Todoアプリの部分)
    説明用5つ(入力フォーム、タスク一覧、選択中のタスク、入力日、期日)

 説明用の TextBox 以外はスクリプトから関数を割り当てるので、名前をちゃんと付けておきましょう。本記事では、上記のように名前を付けています。
 また、descriptionTextbox(タスクを入力する用)と taskContentTextBox(選択したタスクの内容を表示させる用)は Mutiline を true にしています。

 他の見栄えに関する部分は BackColor を変えたり、BorderStyle を変えたりしました。

スクリプト

全体
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using System.Xml.Serialization;
using System.Diagnostics;

namespace TodoApp1
{
    public partial class Form1 : Form
    {
        private List<Task> tasks = new List<Task>();

        public Form1()
        {
            InitializeComponent();
            LoadTasks();
            RefreshList();
        }

        private void addButton_Click(object sender, EventArgs e)
        {
            Task task = new Task();
            task.Date = DateTime.Now;
            task.DueDate = dueDateTimePicker.Value;
            task.Description = descriptionTextBox.Text;
            tasks.Add(task);
            SaveTasks();
            RefreshList();
            ClearForm();
        }

        private void editButton_Click(object sender, EventArgs e)
        {
            if (tasksListBox.SelectedItem != null)
            {
                int index = tasksListBox.SelectedIndex;
                Task task = tasks[index];
                task.DueDate = dueDateTimePicker.Value;
                task.Description = descriptionTextBox.Text;
                tasks[index] = task;
                SaveTasks();
                RefreshList();
                ClearForm();
            }
        }

        private void deleteButton_Click(object sender, EventArgs e)
        {
            if (tasksListBox.SelectedItem != null)
            {
                int index = tasksListBox.SelectedIndex;
                tasks.RemoveAt(index);
                SaveTasks();
                RefreshList();
            }
        }

        private void tasksListBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (tasksListBox.SelectedItem != null)
            {
                int index = tasksListBox.SelectedIndex;
                Task task = tasks[index];
                inputDateTextBox.Text = task.Date.Date.ToString("yyyy-MM-dd");
                dueDateTextBox.Text = task.DueDate.Date.ToString("yyyy-MM-dd");
                taskContentTextBox.Text = task.Description;
            }
        }

        private void RefreshList()
        {
            tasksListBox.DataSource = null;
            tasksListBox.DataSource = tasks.OrderBy(t => t.DueDate).ToList();
            tasksListBox.DisplayMember = "DescriptionAndDueDate";
        }

        private void ClearForm()
        {
            dueDateTimePicker.Value = DateTime.Now;
            descriptionTextBox.Text = "";
        }

        private void LoadTasks()
        {
            string fileName = "tasks.xml";
            if (File.Exists(fileName))
            {
                XmlSerializer serializer = new XmlSerializer(typeof(List<Task>));
                using (FileStream stream = File.OpenRead(fileName))
                {
                    tasks = (List<Task>)serializer.Deserialize(stream);
                }
            }
        }

        private void SaveTasks()
        {
            string fileName = "tasks.xml";
            XmlSerializer serializer = new XmlSerializer(typeof(List<Task>));
            using (FileStream stream = File.Create(fileName))
            {
                serializer.Serialize(stream, tasks);
            }
        }

        private void openSaveFolderButton_Click(object sender, EventArgs e)
        {
            string saveFilePath = Path.Combine(Environment.CurrentDirectory, "tasks.xml");
            string saveFolder = Path.GetDirectoryName(saveFilePath);

            Process.Start(saveFolder);
        }

    }

    public class Task
    {
        public DateTime Date { get; set; }
        public DateTime DueDate { get; set; }
        public string Description { get; set; }

        public string DescriptionAndDueDate
        {
            get { return $"{DueDate.ToShortDateString()} - {Description}"; }
        }
    }
}
Task クラス

 タスクを入力した日付、タスクの期日、タスクの内容が入っている Task クラスを作っています。

public class Task
    {
        public DateTime Date { get; set; }  //入力日
        public DateTime DueDate { get; set; }  //タスクの期日
        public string Description { get; set; }  //タスクの内容

        public string DescriptionAndDueDate
        {
            get { return $"{DueDate.ToShortDateString()} - {Description}"; }
        }
    }

 DesctriptionAndDueDate は返り値を string とする期日とタスクの内容を返す関数になっています。

 その中で使われている DateTime.ToShortDateString 関数は DateTime を短い形式の日付の文字列形式(yyyy/MM/dd)に変換する関数です。
learn.microsoft.com

データの保存、ロード
private void LoadTasks()  //xmlファイルからTasksを読み込む関数、読み込み先はアプリケーションの実行ファイルが存在するディレクトリ
{
     string fileName = "tasks.xml";  //保存するファイル名
     if (File.Exists(fileName))  //ファイルの有無の判定
    {
        XmlSerializer serializer = new XmlSerializer(typeof(List<Task>));  //Xmlシリアライザーをインスタンス化
        using (FileStream stream = File.OpenRead(fileName))
        {
            tasks = (List<Task>)serializer.Deserialize(stream);  //xmlファイルに書かれていたものをデシリアライズして、tasksに代入
        }
    }
}

private void SaveTasks()  //Tasksをxmlファイルに保存する関数, 保存先はアプリケーションの実行ファイルが存在するディレクトリ
{
    string fileName = "tasks.xml";  //ファイル名
    XmlSerializer serializer = new XmlSerializer(typeof(List<Task>));  //Xmlシリアライザーのインスタンス化
    using (FileStream stream = File.Create(fileName))  //上記のファイル名でファイルを開き、シリアライズ化したものを書き込む
    {
        serializer.Serialize(stream, tasks);
    }
}

private void openSaveFolderButton_Click(object sender, EventArgs e)  //xmlファイルが保存してあるフォルダを開く関数
{
     string saveFilePath = Path.Combine(Environment.CurrentDirectory, "tasks.xml");  //ファイルのパス
     string saveFolder = Path.GetDirectoryName(saveFilePath);  //ファイルがあるディレクトリ名を取得

     Process.Start(saveFolder);  //外部のプログラムを呼び出す関数. 使うには using System.Diagnostics; が必要
}

 説明はスクリプト内のコメントを見てください。

RefreshList
private void RefreshList()
{
    tasksListBox.DataSource = null;
    tasksListBox.DataSource = tasks.OrderBy(t => t.DueDate).ToList();
    tasksListBox.DisplayMember = "DescriptionAndDueDate";
}

 RefreshList 関数は tasksListBox の表示について整理する関数です。次の2つのことをしています。

  • 期日順にソート
  • 期日とタスクの内容を List 内に表示させる
ClearForm
private void ClearForm()
{
    dueDateTimePicker.Value = DateTime.Now;
    descriptionTextBox.Text = "";
}

 入力後に変更された descriptionTextBox や dueDateTimePicker の初期化をするための関数です。

関数の割り当て

 各ボタンの Click に各関数を割り当てます。また、tasksListBox の SelectedIndexChanged に tasksListBox_SelectedIndexChanged を割り当てます。

実行

 実行をクリックし、正常に動作するか確かめましょう。


ビルド

 ソリューションのビルドをクリックし、ビルドをしましょう。

 ビルドが正常に終了したら、このプロジェクトがあるフォルダ内の bin の中に exe フォルダが生成されているはずです。

 ビルド出力するディレクトリを変更することもできるようです。詳細はリンク先をご覧ください。
learn.microsoft.com

 exe ファイルを実行して、正常に動作すれば ok です。

最後に

 もっと機能を足したり、デザイン面を凝ったりしてもよいと思います。