開発時にツール系を作ることってよくあるよね
どこの現場で仕事をしても、純粋に案件のプロダクトだけを作っていればいいってことは実はあんまりない。
プロダクトとは直接関係しないが開発中にあると便利なツールみたいなのを作らないといけないことが往々にしてある。
過去には例えば、証明書ファイルを作る機能や、ソケットプログラムの対向側をエミュレートする機能なんかを作ったりしたものです。
いわゆる ちょいプロ なんて呼ばれたりするやつですね。
なくても、大抵の場合何とかなるので、誰も進んで作らないし、そんな工数がもらえることもないんだけど、みんな「誰か作ってくれるといいなぁ」と思っていたりする。
で、時間を見つけて作ってみたりすると、ありがたがって使ってくれるのだけど、そのうち
「あんな機能も欲しいな」
「こっちでも使いたいんだけど、少し改造できる?」
ってな感じで、ちょいプロだったはずのものはどんどん肥大化して、色々なものがごちゃごちゃと詰まった使いづらいツールになってった、というオチ。
ツール系をConsoleアプリで作ると
ツール系って作り始める時はあくまでもちょいプロのつもりで「凝ったGUIもいらないよね」ってことで、私の場合Consoleアプリにして作ったりすることが多いです。
.NETが使えるなら、.NETのConsoleアプリを選択しますね。
で、例のごとく後からどんどん機能が追加されることになるわけです。
そうなってくると、次々詰め込まれる数ある機能の中から実行時にユーザーが機能を選択するUIが欲しくなってくるもの。
そうやって、毎回メニュー機能を実装することになります。
例えばこんな感じで実装するのはよくあるパターン。
Console.WriteLine("機能を選択してください。");
Console.WriteLine("1:機能1");
Console.WriteLine("2:機能2");
Console.WriteLine("9:キャンセル");
Console.Write("> ");
do
{
switch (Console.ReadLine())
{
case "1":
Func1();
return;
case "2":
Func2();
return;
case "9":
Console.WriteLine("終了します");
return;
default:
Console.WriteLine("メニューの中から選択してください。");
Console.Write("> ");
break;
}
} while (true);
void Func1()
{
Console.WriteLine("機能1が選択されました。");
}
void Func2()
{
Console.WriteLine("機能2が選択されました。");
}
Console.WriteLine
で1行ずつメニューを表示しています。
ユーザーには「1」か「2」か「9」を入力してもらって、機能を選択してもらうインターフェイスになっています。
1、2、9以外が入力されたら再入力を促します。
Consoleアプリで機能を選択してもらう場合、よく見る実装なんじゃないかなと思います。
これはこれで悪くないんですけど、ちょっとメンテナンスが面倒。
もう少し簡単にメニューが作れるといいのにと、いつも思います。
矢印キーで選択するメニュー機能
「1」や「2」のようなキー入力と機能を割り当てるのは悪く無いアイディアだと思うけれど、キーと機能の割り当てを管理しないといけないので、正直なところちょっと面倒だと思う。
矢印キーでカーソルを上下させてメニューを選択させられたら、それでいいんじゃない?
というわけで早速実装。
コピペして使いやすいように、staticな1クラスで実装してみました。
public static class MConsole
{
public static int Menu(params string[] menu)
{
var curTop = Console.CursorTop;
var curLeft = Console.CursorLeft;
var selectedIndex = 0;
var exit = false;
do
{
// show menu
Console.CursorTop = curTop;
Console.CursorLeft = curLeft;
for (var i = 0; i < menu.Length; i++)
{
var selected = selectedIndex == i ? ">" : " ";
Console.WriteLine("{0} {1}", selected, menu[i]);
}
// read input
var key = Console.ReadKey(true);
// move cursor or exit
switch (key.Key)
{
case ConsoleKey.Enter:
exit = true;
break;
case ConsoleKey.UpArrow:
selectedIndex = Math.Max(0, selectedIndex - 1);
break;
case ConsoleKey.DownArrow:
selectedIndex = Math.Min(menu.Length - 1, selectedIndex + 1);
break;
}
} while (!exit);
return selectedIndex;
}
}
使い方はこんな感じ。
var menu = new[] { "機能1", "機能2" };
var func = new[] { Func1, Func2 };
Console.WriteLine("機能を選択してください。");
var selected = MConsole.Menu(menu.Append("キャンセル").ToArray());
if (func.Length > selected)
{
func[selected]();
}
else
{
Console.WriteLine("終了します");
}
void Func1()
{
Console.WriteLine("機能1が選択されました。");
}
void Func2()
{
Console.WriteLine("機能2が選択されました。");
}
実行してみると上下の矢印キーでメニューの左に表示される「>」がカーソルのように上下すると思います。
選択したいメニューにカーソルを合わせてEnterキーを入力すると、メニューの選択が確定します。
MConsole
というクラスにMenu()
というstaticなメソッドとして実装しました。
極力シンプルなインターフェイスになるようにしています。
そのため、引数はstring[]
(表示したいメニューのリスト)、戻り値はint
(選択されたメニューのインデックス)としています。
あくまでもひな形として、扱いやすいようにしました。
この実装をベースにして、カスタマイズするといいかもしれません。
まとめ
今回のコードは正直、紹介するほど大層なものでもないかもしれません。
誰にでも実装できるだろうと思います。
ただ、いちいち自分で実装するのはちょっと面倒だし、そこまでして実現したいという程でもない機能って感じだと思います。
なので、コピペで使いやすいように、できるだけシンプルな実装にしてみました。
自分で使いやすいように色々とインターフェイスをいじって、アレンジしてみてもいいんじゃないかなと思います。
例えばDictionary<string, Action>
を引数にしてみるとかね。
よかったら参考にしてみてください。
C#の開発に人手が入用ならシステムクラフトがお手伝いできます!
是非ご相談ください。
コメント