はじめに
―― 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! の通過儀礼もするハメになりました。
初めの感触としては、「なんとなく今までやった何かの言語に似てる気がする」でした。
どうやってアプリを作るか?
さて、何をどうやればアプリになるものやら。
とりま具体例を知るべくググることにしました。すると、参考になりそうな動画が出るわ出るわ~😊
この再生リスト #1-#2 を参考にしたかなぁって感じです。
とりあえず、この動画を見れば画面にボタンやテキストを配置することが簡単だと分かりました。
できたこと
- アプリの骨組み作成(.exe ファイルの作成)
- ボタン・テキストの配置
メイン画面の作成
今回のメイン画面は、シンプルです。
ツールボックスから Panel を選択し、メインウィンドウに貼り付けました。
なんでこうするの?
こうすることにより、次のことができるようになります。
- スライドショーのように画面が切り替えられる!
具体的には、
- 画面1: スタート画面の表示
- 画面2: 出題画面の表示
のように設定して、画面1→画面2 という風に切り替わるように設定しました。
参考にした記事はこれです。
2つの画面の作成
ユーザーコントロールの作成
先ほどの記事を参考に、ユーザーコントロールを2つ作成しておきます。
やり方は、
- ソリューションエクスプローラー > 現在のプロジェクト親ファイル("Properties"のすぐ上にあるはず) にカーソルを合わせて、右クリック
- 追加 > ユーザーコントロール(Windowsフォーム) をクリック
で作成できます。
この方法でユーザーコントロールを2つ作っておきます。
スタート画面の作成
作成イメージ
ボタンとテキストの作成
まず、ボタンを作成します。
やり方は、 さっきの動画のシリーズ にもありますが、ツールボックス > Button を選んで Drug&Drop するだけです。
動画を参考にテキストを変えてあげましょう。
テキストも同様に貼り付けて はい、終わりで~す。
ユーザーコントロールの切り替え
ユーザーコントロールの切り替えは、
を参考にしました。
FormMain の設定
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 のクリック時に起きるアクションに、次のコードを加えます。
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/
完成イメージ
ということで完成イメージ。
(試作段階ではヒントボタンは作ってません。押してもなんも起きません。ただのしかばねのようです。)
ここで作るものを整理しましょうか。
- 問題文:ボタンの指令によって表示を変える。
- 問題文をリストや配列に格納し、シャッフルする
- 答えボタン:問題文に答えを表示
- 次へボタン:問題文に次の問題文を表示
- 戻るボタン:お好みで。(次へボタンが作れたら作れるはずだから、本記事では省略。)
- テキスト i : 今、何問目?かを表示。(作り方は問題文と同様なので、本記事では省略。)
- テキスト n : 全部で何問?かを表示。(同上。)
- テキスト / : 作るだけです。それだけです。
問題文
問題文の Label をユーザーコントロールに貼り付けましょう。
問題文をリストや配列に格納
これも1番大変だったものの1つです。
そもそも、C# には、リストと配列があります。それぞれ結構似ていますが、できることは違ったりします。
Python ではリストしか無かったので、結構戸惑いました。
具体的な概略
- 問題文と答えをCSVファイルで作成しておく(これは慣れてるPythonでやりました🙏)
- C# で、CSVを読み込み、配列に格納
- 問題番号の配列 {0, 1, 2,...,N-1} を作り、シャッフルする
- 問題番号配列のi番目の問題番号を使って、出題する問題を呼んでくる
では、やぁりましょう! (by Hikakin)
問題データCSVの作成
どんなCSVを作成したかを載せておきます。
また、CSVファイルと言えば通常 ", " 区切りですが、問題文形式の都合上、今回はタブ区切りで作りました。
以下は "," 区切りの例で載せておきます。
問題1, 問題2, 問題3, ...
答え1, 答え2, 答え3, ...
というように、今回は横に長く作りました。
もしかすると上級者になれば縦に長い方がやりやすいのかもしれませんが、初心者なのでお許しください。
問題データを配列に格納
CSVを読み込むクラスの作成
ここはほぼコピペです。
今回は適当に FormMain.cs に書いてしまいましたが、別に他の.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()
という名前にしたのはちょっと分かりにくかったですね。ゴメンナサイ。
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!";
みたいなやつです。
詳しくは以下のリンクを見ればいいんじゃないでしょうか。
リストの要素を取り出す
そして、リスト lists
の中の要素を lists[0]
や lists[1]
で取り出しています。(この書き方は Python と一緒ですね。)
ちなみに、配列も同様に [数字]
でいけます。
そして、これを戻り値としています。
曲数
曲数、もとい、問題数も取り出しています。
これは配列の長さを返すことで実現しています。
配列の長さは、 array.Length
で取り出せます。
(Python の len(array)
に相当します。まあ Python では配列ではなくリストなんですが。)
シャッフル配列を作成
問題番号の配列 {0, 1, 2,...,N-1} を作り、シャッフルします。とりま、コードはこんな感じです。
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[]
となっていることに注意です。
関数の中身は、
- さっき作った
Number()
関数を利用して、問題数をN
とする。 -
配列
{0, 1, 2, 3,...,N-1}
を作る。(参考) - この配列をシャッフルする。
という感じです。シャッフルのところはコピペですが、偏りにくいシャッフルがこれで実現するようです。
これで、 title[id[i]]
とすれば、第 $i$ 問目の答えが出てくるようになります!
よっしゃ~!これでひと段落!!!データの処理フェーズは終わりました!お疲れ様です!!
クイズページのユーザーコントロールに書いておくもの
もうあとはボタンクリックの処理を書いていくだけなのですが、その前に、やっておかないといけない処理があるのでやっておきましょう!
またしてもコードから。
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。
答えボタンの処理
もうあとは怖いものなしです。答えボタンの処理をやっていきましょう!
/// <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]]; //問題文を答えに書き換える
ほらね。もう今までやってきたことだけでできるでしょう?
次へボタンの処理
/// <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
効果音をつけたくなりました。出題音です。\デデンッ!/ってやつです。あまりにもさびしいので。
これもほぼコピペなんですが、うまくいきました。
/// <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 {}
の {}
内にそれぞれ書いています。(グローバルな処理にするのが面倒だった)
このコードは、以下から拝借しました。
注意点は、wavファイルしか再生できない点です。なので、mp3ファイルをwavファイルに変換して使いました。
ちなみに、使った効果音はフリー音源です。
これにて終了~!
これでクイズアプリが完成しました!
やはり、初めての言語ってワクワクしますね!
今回はグローバル変数が無いという壁にぶち当たり、アプリの完成は本当に無理かと思いました…。
それでもなんとか作ることができて、感無量です🥺✨
小学生並みの感想になってしまいましたが、C# でアプリを作る際に参考になれば幸いです。
また、ご指摘やご感想などございましたら、ぜひ残していただけますと嬉しいです。
このメモが誰かのお役に立てましたら幸いです。