【C#アプリ】C#歴3日でクイズアプリを作った話【ゼロ知識】

# はじめに
**―― C#, 1ミリもわかんない……！**

**―― でも、暇だし何かアプリ作ってみたい……！**

ということで、本当に C# に全く触れたことのない Python 使いが、C#アプリを0から作ってみた！
という話をお伝えしていきたいと思います～！

**どんな人向け記事か？** と言いますと…
- **プログラミング初心者・中級者**
- **C#初心者**
- 何でもいいから**アプリを作ってみたい**
- **C#でクイズアプリを作りたい**（ドンピシャ）

という人向けです！

## 注：プログラミング経験は、ありまぁす！
普段は Python を使って、学業に必要な程度のプログラムを組んで遊んでいます。ガチガチにはやってないです。
経験言語は、 Python, C++, Java, HTML/CSS, Fortran ですが、いわゆる “かじっただけ” の言語が大半です。
アプリ経験は、 Python の tkinter みたいなのを使って組んだ記憶がおぼろげながら浮かんできました。

とは言ったものの、アプリを作ったのも1,2年ほど前。もうすっかり忘れてしまってます。

ということで、**初動からググりまくるアプリ制作、スタートですっ！**

# どんなアプリを作ろうと思ったか？
- アニメの曲名を当てる**クイズアプリ**です
- **CSVからデータを読込み**ます
- **問題をシャッフル**します
- 答えボタンで答えが表示されます
- 次へボタンで次の問題に移ります

# なにからやるの？
初めにぶち当たった問題。それは…「**どの言語**を選べばよいか」。

どうやら Winアプリ を作るには C# が制作しやすそうだぞ？（**ホンマか？**）
ということで **C#** を使うことに決めました。

続いて遅いかかる　**環　境　構　築**　。
普段は VS Code を使っているのですが、今回はアプリ開発ということで、 Visual Studio を使用することになりました。
C# のキホンのキの字も知らないため、 VS Code での環境構築も済ませて、アプリ開発の途中で結局 **Hello World!** の通過儀礼もするハメになりました。

初めの感触としては、「なんとなく今までやった**何かの言語に似てる**気がする」でした。

## どうやってアプリを作るか？
さて、何をどうやればアプリになるものやら。
とりま具体例を知るべくググることにしました。すると、参考になりそうな動画が出るわ出るわ～😊

https://www.youtube.com/watch?v=9Rcw9_KaqlI&list=PLZV2kwgJ0MJH8z3ia7fDXmb6kNsqFot12&index=1

この再生リスト #1-#2 を参考にしたかなぁって感じです。

とりあえず、この動画を見れば画面にボタンやテキストを配置することが簡単だと分かりました。

:::note
**できたこと**
- アプリの骨組み作成（.exe ファイルの作成）
- ボタン・テキストの配置
:::

# メイン画面の作成
今回のメイン画面は、シンプルです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648376/7c195c8a-86d5-b95c-b73c-d3fe65c33c85.png)
**ツールボックスから Panel を選択**し、メインウィンドウに貼り付けました。

## なんでこうするの？
こうすることにより、次のことができるようになります。
- スライドショーのように**画面が切り替えられる**！

具体的には、
- 画面1: スタート画面の表示
- 画面2: 出題画面の表示
のように設定して、**画面1→画面2 という風に切り替わるように設定**しました。

参考にした記事はこれです。

https://punyo-er-met.hateblo.jp/entry/2016/04/10/103632

# 2つの画面の作成
## ユーザーコントロールの作成
先ほどの記事を参考に、**ユーザーコントロールを2つ作成**しておきます。

やり方は、
1. ソリューションエクスプローラー > 現在のプロジェクト親ファイル（"Properties"のすぐ上にあるはず） にカーソルを合わせて、右クリック
1. 追加 > ユーザーコントロール(Windowsフォーム) をクリック

で作成できます。

この方法でユーザーコントロールを2つ作っておきます。

# スタート画面の作成
## 作成イメージ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648376/1a034a92-cdaf-d570-dd75-108ef6667f4f.png)
このような画面を作成していきます。


## ボタンとテキストの作成
まず、**ボタンを作成**します。
やり方は、 [さっきの動画のシリーズ](https://www.youtube.com/watch?v=9Rcw9_KaqlI&list=PLZV2kwgJ0MJH8z3ia7fDXmb6kNsqFot12&index=2 " #02 C# VisualStudioでつくる Windowsアプリ 10分で習得 ボタンイベント - YouTube") にもありますが、**ツールボックス > Button を選んで Drug&Drop する**だけです。
動画を参考に**テキストを変えて**あげましょう。

テキストも同様に貼り付けて はい、終わりで～す。

##  ユーザーコントロールの切り替え
ユーザーコントロールの切り替えは、

https://punyo-er-met.hateblo.jp/entry/2016/04/10/103632

を参考にしました。

### FormMain の設定
**FormMain.cs** に、**次のコードを加えます。**

```cs:FormMain.cs
namespace CharacterTypeQuiz
{
    public partial class FormMain : Form
    {
        //staticで宣言することでインスタンスを固定
        public static UserControl_StartPage ctr_S;
        public static UserControl_QuizPage ctr_Q;

        public FormMain()
        {
            InitializeComponent();
            ctr_S = new UserControl_StartPage();
            ctr_Q = new UserControl_QuizPage();

            //パネルにコントロールS,Qを追加
            MainPanel.Controls.Add(ctr_S);
            MainPanel.Controls.Add(ctr_Q);
            System.Diagnostics.Debug.WriteLine("Chinge");

            //スタート画面のみを見えるようにする
            ctr_S.Visible = true;
            ctr_Q.Visible = false;
        }
    }
```

ほとんどさっきのリンクの受け売りです。（よくわかってない顔）

### ボタンクリック時の設定
先ほど設定した **StartButton のクリック時**に起きるアクションに、**次のコードを加えます。**

```cs:UserControl_StartPage.cs
private void StartButton_Click(object sender, EventArgs e)
{
    FormMain.ctr_S.Visible = false;
    FormMain.ctr_Q.Visible = true;
    System.Diagnostics.Debug.WriteLine("Clicked"); //これは省略可（コンソールに"Clicked"と表示）

    //効果音（※後述）
    StopSound();
    PlaySound();
}
```
ほとんどさっきのリンクの受け売りです。（2回目）

これで、**ボタンがクリックされたらクイズ画面に遷移する**はずです。

### 余談
この作業がなかなか大変だった。「c# 遷移」でググったら別のWindowに遷移するやつばかり出てきて、うんざりした。
それがこの記事を書こうと思ったきっかけでも、あります（安倍晋三）。

# クイズ画面の作成
さて、ここからが大変ですよ…！ついてきて、くれますか…？！ _\Yeahhhhhh/_

## 完成イメージ
ということで完成イメージ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648376/a29b1eec-5c0e-2ea2-fb61-9d7a84d34e4f.png)
（試作段階ではヒントボタンは作ってません。押してもなんも起きません。ただのしかばねのようです。）

ここで作るものを整理しましょうか。
- **問題文**：ボタンの指令によって表示を変える。
- 問題文をリストや配列に格納し、**シャッフル**する
- **答えボタン**：問題文に答えを表示
- **次へボタン**：問題文に次の問題文を表示
- **戻るボタン**：お好みで。（次へボタンが作れたら作れるはずだから、本記事では省略。）
- **テキスト i** : 今、何問目？かを表示。（作り方は問題文と同様なので、本記事では省略。）
- **テキスト n** : 全部で何問？かを表示。（同上。）
- **テキスト /** : 作るだけです。それだけです。

## 問題文
問題文の **Label を**ユーザーコントロールに**貼り付けましょう**。

## 問題文をリストや配列に格納
これも1番大変だったものの1つです。

そもそも、C# には、リストと配列があります。それぞれ結構似ていますが、できることは違ったりします。
Python ではリストしか無かったので、結構戸惑いました。

### 具体的な概略
1. 問題文と答えをCSVファイルで作成しておく（これは慣れてるPythonでやりました🙏）
1. C# で、CSVを読み込み、配列に格納
1. 問題番号の配列 {0, 1, 2,...,N-1} を作り、シャッフルする
1. 問題番号配列のi番目の問題番号を使って、出題する問題を呼んでくる

では、やぁりましょう！ (by Hikakin)

### 問題データCSVの作成
**どんなCSV**を作成したかを載せておきます。
また、CSVファイルと言えば通常 ", " 区切りですが、問題文形式の都合上、今回はタブ区切りで作りました。
以下は "," 区切りの例で載せておきます。

```text:Problem.csv
問題1, 問題2, 問題3, ...
答え1, 答え2, 答え3, ...
```
というように、今回は横に長く作りました。
もしかすると上級者になれば縦に長い方がやりやすいのかもしれませんが、初心者なのでお許しください。

### 問題データを配列に格納
#### CSVを読み込むクラスの作成
ここは**ほぼコピペ**です。
今回は適当に FormMain.cs に書いてしまいましたが、別に他の.csでも動くと思います（適当）

```cs:FormMain.cs
//データの読み込みクラス
class CsvRead
{
    //関数を生成
    public List<string[]> CsvReader()
    {
        string fileName = "CharTypeConvertedSongData.csv";
        StreamReader sr = new StreamReader(@fileName);
        List<string[]> lists = new List<string[]>();

        //1行ずつ処理
        while (!sr.EndOfStream)
        {
            string line = sr.ReadLine();

            //カンマで配列の要素として分ける
            string[] values = line.Split('\t'); //今回はタブ区切り"\t"。通常は","でOK。
            // 配列からリストに格納する
            lists.Add(values);
            // Console.WriteLine(String.Join(",", values)); //読み込めてるか確認。コンソールに表示される。
        }

        return lists;
        
    }
```
何をやっているかというと、
- CSVを読み込む
- 1行ずつ配列に格納
- 配列をリストに追加
- `List<string[]>` 型の `lists` を戻り値として返す

という感じです。

#### 問題データを分けて格納するクラスを作成
曲名クイズなので、 `class Song` という名前にしました。
また、問題を `Converted()` という名前にしたのはちょっと分かりにくかったですね。ゴメンナサイ。

```cs:FormMain.cs
class Song
{
    private readonly CsvRead data = new CsvRead();
    //答え
    public string[] Title()
    {
        List<string[]> lists = data.CsvReader();
        return lists[0];
    }
    //問題
    public string[] Converted()
    {
        List<string[]> lists = data.CsvReader();
        return lists[1];
    }
    //曲数
    public int Number()
    {
        List<string[]> lists = data.CsvReader();
        return lists[0].Length;
    }
}
```

ここでやっていること、初心者にはマジでキツすぎたので解説します。

##### 他のクラスの戻り値を読み込む
`class HogeHoge` の次行に、 **`private 読み込みたいclass名 tmp = new 読み込みたいclass名();` と入力することで、他のクラスの戻り値を利用できる**ようになります。 `tmp` は何でもいいです。 `unko` でも `RealDonaldTrump` でも。まあ、分かりやすいのにしましょう。ここでは `data` という風に名前を付けました。

また、`readonly` は read only のことで、読み取り専用オプションです。（最初読まれへんかった）
なんか付けといた方がええで～ってVisual Studioさんに言われてしもたんで、とりあえず付けときました。

##### 関数を作ろう
**他の関数を読み込む行為は**動的(?)な行為だからか、**関数の中でしか読み込めません**。（重要）
これが原因で何度も Visual Studio さんに叱られました。

というわけで、関数（c# では**メソッド**と呼ぶらしい）を作っていきましょか～。
**メソッドは、 `public 戻り値の型 作成する関数名() {}` で定義できます！**
そして、 `{}` の中に、処理内容を書いていきましょう！

**変数の宣言**
関数の中身は、 **`代入する型 hoge = 代入したいもの;`** というように、ここで**変数の宣言**を行っています。
変数の宣言とは、具体的にいうと `int x = 3;` とか `string s = "Hello World!";` みたいなやつです。
詳しくは以下のリンクを見ればいいんじゃないでしょうか。

https://ufcpp.net/study/csharp/st_variable.html

**リストの要素を取り出す**
そして、**リスト `lists` の中の要素を `lists[0]` や `lists[1]` で取り出し**ています。（この書き方は Python と一緒ですね。）

ちなみに、配列も同様に `[数字]` でいけます。

そして、これを戻り値としています。

**曲数**
曲数、もとい、**問題数も取り出し**ています。
これは**配列の長さを返す**ことで実現しています。

配列の長さは、 **`array.Length`** で取り出せます。
（Python の `len(array)` に相当します。まあ Python では配列ではなくリストなんですが。）

#### シャッフル配列を作成
問題番号の**配列 {0, 1, 2,...,N-1} を作り、シャッフルします**。とりま、コードはこんな感じです。

```cs:FormMain.cs
class Randomized
{
    private readonly Song song = new Song();
    public int[] Id()
    {
        int N = song.Number(); //N曲あるよと伝える
        int[] ary1 = Enumerable.Range(0, N).ToArray(); //N個の数字の配列を作る
        int[] id = ary1.OrderBy(i => Guid.NewGuid()).ToArray(); //これをシャッフルする（問題番号）
        return id;
    }
}
```

`private readonly Song song = new Song();` の箇所の説明は大丈夫ですよね。**さっきと同じ**です。
`public int[] Id() {}` で、関数を作ってます。これもさっき説明しました。戻り値が、整数配列 `int[]` となっていることに注意です。

関数の中身は、
1. さっき作った **`Number()` 関数を利用して、問題数を `N` とする**。
1. **配列 `{0, 1, 2, 3,...,N-1}` を作る**。（[参考](https://qiita.com/koara-local/items/534376eabd11c64d31af "[C#] 連番の作成 (Enumerable.Rangeの利用） - Qiita")）
1. この**配列をシャッフル**する。

という感じです。シャッフルのところはコピペですが、偏りにくいシャッフルがこれで実現するようです。

これで、 **`title[id[i]]` とすれば、第 $i$ 問目の答えが出てくる**ようになります！

よっしゃ～！これでひと段落！！！データの処理フェーズは終わりました！お疲れ様です！！

## クイズページのユーザーコントロールに書いておくもの
もうあとはボタンクリックの処理を書いていくだけなのですが、その前に、やっておかないといけない処理があるのでやっておきましょう！

またしてもコードから。
```cs:UserControl_QuizPage.cs
public partial class UserControl_QuizPage : UserControl
{
    public static int q_cnt = 0; //今○問目。
    public static int N;
    public static int[] id; //シャッフルした固有のidリスト


    public UserControl_QuizPage()
    {
        InitializeComponent();

        //Randomized Class と、idリストの読み込み
        Randomized id_lis = new Randomized();
        id = id_lis.Id();

        //Song Classの読み込み
        Song song = new Song();

        //問題の読み込み
        string[] cnv = song.Converted();
        //1問目をテキストに表示させる
        this.QuizText.Text = cnv[id[q_cnt]];

        //以下、解説略箇所
        this.q_cntLabel.Text = (q_cnt+1).ToString();
        N = song.Number();
        this.nLabel.Text = N.ToString();
    }
```

**`public static` を付けて宣言**することで、**グローバル変数のように扱う**ことができるようになります！
したがって、最初にこの宣言をしておきましょう！
また、シャッフルしたidリストも、いちいち読み込んでいては、シャッフルにシャッフルを重ねてしまい、idとしての意味を成さなくなってしまうため、これもグローバル変数としておきましょう！

※正確にいうと、C#にはグローバル変数という概念は無いようです。（だからめっちゃ苦労した…😭）

まず出てくる **`q_cnt`** というのは、**今何問目かを記憶しておく変数**です。 `i` とか `cnt` でもいいんですが、固有性を持たせたいのでこの名前にしました。（次は `N` になってるくせに)

次に、UserControl_QuizPage が呼び出された際に行う処理を記述しています。やる処理は以下です。
- **シャッフルしたidリストの読み込み**
- Song Classを読み込んで、**1問目を問題文Labelのテキストに表示**

これで下準備OK。

## 答えボタンの処理
もうあとは怖いものなしです。答えボタンの処理をやっていきましょう！

```cs:UserControl_QuizPage.cs
/// <summary>
/// 答えボタンクリック時
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void AnswerButton_Clicked(object sender, EventArgs e)
{
    Song song = new Song(); //Song Class呼び出し
    string[] title = song.Title(); //title を宣言
    this.QuizText.Text = title[id[q_cnt]]; //問題文を答えに書き換える
```

ほらね。もう**今までやってきたことだけでできる**でしょう？

## 次へボタンの処理

```cs:UserControl_QuizPage.cs
/// <summary>
/// 次へボタンクリック時
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void NextButton_Clicked(object sender, EventArgs e)
{
    if (q_cnt == N-1)
    {
        ;
    }
    else
    {
        q_cnt++;
    }
    Song song = new Song();
    string[] cnv = song.Converted();
    this.QuizText.Text = cnv[id[q_cnt]];
    this.q_cntLabel.Text = (q_cnt + 1).ToString(); //idリストが0から始まっているため+1して表示

    //効果音（※後述）
    StopSound();
    PlaySound();
}
```

どうでしょう？ほぼ**答えボタンの処理と同じ**ですね！

相違点は、
- `q_cnt` を +1 している点
- N問目のときは次に進まないようにしている点

だけです！

ちなみに言っておくと、 `x++` は `x = x+1` と全く同じ意味（と思ってよい）です。これを**インクリメント**と言います。

## おまけ：効果音もつけたくな～い？WWO
効果音をつけたくなりました。出題音です。\ﾃﾞﾃﾞﾝｯ!/ってやつです。あまりにもさびしいので。

これもほぼコピペなんですが、うまくいきました。
```cs.UserControl_QuizPage.cs
/// <summary>
/// 音声を鳴らすやつ
/// </summary>
private System.Media.SoundPlayer player = null;
string SoundFile = "Quiz-Question03-1.wav";

private void StopSound()
{
    if (player != null)
    {
        player.Stop();
        player.Dispose();
        player = null;
    }
}

private void PlaySound()
{
    player = new System.Media.SoundPlayer(SoundFile);
    player.Play();
}
```
以上のコードを、 `public partial class UserControl_QuizPage : UserControl {}` と、`public partial class UserControl_StartPage : UserControl {}` の `{}` 内にそれぞれ書いています。（グローバルな処理にするのが面倒だった）

このコードは、以下から拝借しました。

https://qiita.com/Oichan/items/b93e8e8ba8211b925d0a


注意点は、**wavファイルしか再生できない**点です。なので、mp3ファイルをwavファイルに変換して使いました。
ちなみに、使った効果音は[フリー音源](https://otologic.jp/free/se/quiz01.html "フリー効果音：クイズ｜OtoLogic")です。

# これにて終了～！
これで**クイズアプリが完成しました！**
やはり、初めての言語ってワクワクしますね！

今回はグローバル変数が無いという壁にぶち当たり、アプリの完成は本当に無理かと思いました…。
それでもなんとか作ることができて、感無量です🥺✨

小学生並みの感想になってしまいましたが、C# でアプリを作る際に**参考になれば幸いです。**

また、**ご指摘やご感想など**ございましたら、ぜひ**残していただけますと嬉しいです。**
このメモが誰かのお役に立てましたら幸いです。