Visual C# で簡易サイネージアプリを作成してみた


最近、Visual C#でデスクトップアプリの作成方法について勉強し始めた初心者です。
いろいろ不備あるかもしれませんが、ご了承ください。

# 作ったアプリの機能

このあたりの機能があるとよいので、デスクトップアプリ初心者によいかと思って作りました。

- フォルダを選択ダイアログから指定できる。
- そのフォルダ内の画像を全画面でスライドショーする。
- esc キーでスライドショーを終了する。
- スライドショー中にフォルダの内容が変わったら検知して表示に反映する。
- 指定した時刻にPCをシャットダウンしてくれる。

# バージョン

Microsoft Visual Studio Community 2022
C# : 12.0
.NET : 8.0.10

# プロジェクト作成

Visual Studio から「Windows フォームアプリ」を選択してプロジェクト作成

![img01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/13d02f2b-7688-27f1-05b0-45210b1c8b41.png)


# 画面作成

### フォルダ選択画面作成

位置やサイズ調整は、Visual Studio のデザインの画面でマウス操作で行いました。

![img08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/44a6e49a-1b43-4d1c-ea68-af0a496f28b3.png)


必要なコントロールを配置してプロパティウィンドウから設定をしました。

| コントロール名 | プロパティ名 | 値 | VisualStudioで自動生成したイベント |
| --- | --- | --- | --- |
| Form | Text | 簡易サイネージ |
| TextBox | (Name) | textBoxFolder |
| | ReadOnly | True |
| Button | (Name) | buttonSelectFolder | buttonSelectFolder_Click |
| | Text | ... |
| CheckBox | (Name) | checkBox1 | checkBox1_CheckedChanged |
| | Text | シャットダウン時刻を設定する |
| DateTimePicker | (Name) | dateTimePicker1 |
| | Format | Time |
| | ShowUpDown | True |
| Button | (Name) | buttonStart | buttonStart_Click |
| | Text | 開始 |
| FolderBrowserDialog | (Name) | folderBrowserDialog1 |

```csharp
namespace Signage
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            // デフォルトでピクチャフォルダ選択状態にする
            string pictureFolderPath = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures);
            textBoxFolder.Text = pictureFolderPath;

            // 時刻の設定を有効にするか
            changeTimeEnableByCheckBox();
        }

        private void changeTimeEnableByCheckBox()
        {
            // チェックがついていたら時刻設定を有効にする
            dateTimePicker1.Enabled = checkBox1.Checked;
        }

        private void buttonSelectFolder_Click(object sender, EventArgs e)
        {
            // 画像表示するフォルダを選択のボタンを押したときの関数

            DialogResult result = folderBrowserDialog1.ShowDialog();
            if (result != DialogResult.OK)
            {
                return;
            }

            string selectedPath = folderBrowserDialog1.SelectedPath;
            textBoxFolder.Text = selectedPath;
        }

        private void checkBox1_CheckedChanged(object sender, EventArgs e)
        {
            changeTimeEnableByCheckBox();
        }

        private void buttonStart_Click(object sender, EventArgs e)
        {
            // スライドショー用の画面ボタンを押したときの関数

            Form2 frmForm2 = new(textBoxFolder.Text, checkBox1.Checked, dateTimePicker1.Value);
            if (frmForm2.IsDisposed == false)
            {
                frmForm2.ShowDialog();
            }
        }
    }
}
```

### スライドショー画面面作成

メニューの「プロジェクト追加」 > 「フォームの追加(Windows フォーム)」を選択

「Form2.cs」でフォームを追加しました。

![img09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/60646d48-470f-9e0b-dce1-f7ddb91c77ca.png)


PictureBox を画面に追加して、<br>
画像が全画面表示になるように Visual Studio のプロパティウィンドウから各コントロールを設定しました。

| コントロール名 | プロパティ名 | 値 | VisualStudioで自動生成したイベント |
| --- | --- | --- | --- |
| Form | (Name) | Form2 | Form2_KeyPress |
| | FormBorderStyle | None |
| | WindowState | Maximized |
| PictureBox | Dock | Fill |
| | SizeMode | Zoom |
| | BackColor | Black |
| Timer | (Name) | timer1 | timer1_Tick |
| | Interval | 5000 |
| Timer | (Name) | timerShutdown | timerShutdown_Tick |
| | Interval | 60000 |
| FileSystemWatcher | (Name) | fileSystemWatcher1 |

全画面の PictureBox の画面ができました。

```csharp
using System.Data;
using System.Diagnostics;

namespace Signage
{
    public partial class Form2 : Form
    {
        private string textBoxFolderText;
        private bool shutdownChecked;
        private DateTime shutdownTime;

        private string[] imageFiles;
        private int i = 0;

        public Form2(
            string textBoxFolderText,
            bool shutdownChecked,
            DateTime shutdownTime
        )
        {
            InitializeComponent();

            this.textBoxFolderText = textBoxFolderText;
            this.shutdownChecked = shutdownChecked;
            this.shutdownTime = shutdownTime;

            // 指定のフォルダから画像のリストを作成する
            var res = loadImageFiles();
            if (imageFiles == null || imageFiles.Length == 0)
            {
                MessageBox.Show("画像が1枚以上あるフォルダを選択してください");
                Close();
                return;
            }

            // 監視するフォルダのパスを設定
            fileSystemWatcher1.Path = textBoxFolderText;
            // ファイル作成、変更、削除のイベントハンドラを設定
            fileSystemWatcher1.Created += onFileChanged;
            fileSystemWatcher1.Changed += onFileChanged;
            fileSystemWatcher1.Deleted += onFileChanged;
            fileSystemWatcher1.Renamed += onFileChanged;

            showImg(); // 最初の画像はすぐに表示
            timer1.Start(); // 一定間隔で画像を切り替える
            timerShutdown.Start(); // 指定時間にシャットダウンするか判定
        }


        private void onFileChanged(object sender, FileSystemEventArgs e)
        {
            var res = loadImageFiles();
            if (imageFiles == null || imageFiles.Length == 0)
            {
                timer1.Stop();
                timerShutdown.Stop();
                MessageBox.Show("画像が1枚以上あるフォルダを選択してください");
                Close();
                return;
            }
        }

        private bool loadImageFiles()
        {
            // 今回は png 画像のみで取得
            imageFiles = Directory.GetFiles(textBoxFolderText, "*.png", SearchOption.AllDirectories);

            // 画像の表示順をランダムにする
            Random rng = new Random();
            imageFiles = imageFiles.OrderBy(_ => rng.Next()).ToArray();

            return imageFiles.Length > 0;
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            // timer1に設定した定期実行の関数

            showImg();
        }

        private void showImg()
        {
            string imageFile = imageFiles[i % imageFiles.Length];
            i++;

            pictureBox1.ImageLocation = imageFile;
        }

        private void timerShutdown_Tick(object sender, EventArgs e)
        {
            // timerShutdownに設定した定期実行の関数

            if (shutdownChecked == false)
            {
                return;
            }

            // DateTimePickerコントロールから時刻を取得
            TimeSpan st = shutdownTime.TimeOfDay;

            // 現在の時刻を取得
            DateTime currentTime = DateTime.Now;
            TimeSpan ct = currentTime.TimeOfDay;

            // 現在の時刻とシャットダウン時刻を比較
            if (currentTime >= shutdownTime)
            {
                // パソコンをシャットダウン
                Process.Start("shutdown", "/s /t 0");
            }

        }

        private void Form2_KeyPress(object sender, KeyPressEventArgs e)
        {
            // フォームに設定した、esc キーを押したか判定する関数

            if (e.KeyChar == (char)Keys.Escape)
            {
                timer1.Stop();
                timerShutdown.Stop();
                Close();
            }
        }
    }
}
```




簡易サイネージアプリができました

設定画面のスクショ

![img10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/6e1da02a-657e-60e1-b850-8de704a318d1.png)


わかりにくいですが、全画面で画像表示してる時のスクショです

画像はいらすとやのものです。

![img11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/eca3aef6-7621-541f-9324-298c3a39914b.png)


最後まで見ていただいてありがとうございました。

---

参考

Windowsアプリケーションをフルスクリーンで表示するには？
https://atmarkit.itmedia.co.jp/ait/articles/0408/27/news105.html

[C#] FolderBrowserDialogの使い方と実装方法
https://af-e.net/csharp-how-to-use-folderbrowserdialog/

[C#] ESCキーを押すことでFormを閉じる方法
https://csharp.programmer-reference.com/form-esc-close/

作って覚えるVisual C# 2022　デスクトップアプリ超入門
https://www.amazon.co.jp/%E4%BD%9C%E3%81%A3%E3%81%A6%E8%A6%9A%E3%81%88%E3%82%8BVisual-C-2022-%E3%83%87%E3%82%B9%E3%82%AF%E3%83%88%E3%83%83%E3%83%97%E3%82%A2%E3%83%97%E3%83%AA%E8%B6%85%E5%85%A5%E9%96%80-%E8%8D%BB%E5%8E%9F%E8%A3%95%E4%B9%8B/dp/4798068330

Visual C# 2022パーフェクトマスター (Perfect Master 186)
https://www.amazon.co.jp/Visual-2022%E3%83%91%E3%83%BC%E3%83%95%E3%82%A7%E3%82%AF%E3%83%88%E3%83%9E%E3%82%B9%E3%82%BF%E3%83%BC-Perfect-Master-186/dp/4798066192