キーボードのNumLock状態をタスクトレイで確認できるようにした

えー、フツーでしょ、My.Computerクラスとか、え? 使えないの? インスタンス取るの? メンドクセー!!! が第一声w

新しい作業用PC、画面大きいし、テンキーあるし、メモリあるし、今のところ不満――あるんだな、これが。

NumLockキーのインジケーターが存在しないw

つまり、NumLockキーON/OFFが目視できないんだわ、いや、あるの当たり前だと思ってたんだけどさ、なかったんだわ、このPC。

なので、タスクトレイにインジケータつけておけばよくね?という発想に至る。

まぁ、あれだ、VBだと、さくっと一行で解決するんだわ。

https://msdn.microsoft.com/ja-jp/library/microsoft.visualbasic.devices.keyboard(v=vs.110).aspx

My.Computer.Keyboardにずばり、NumLock のBooleanいるしね。さくっと書いて終わり。

んじゃ面白くないから、勉強かねてC#でやろうとしたらさ、そしたらさ、My.Computer がない。My空間そのものがない。

まじかーorz

しょうがないから、Microsoft.VisualBasic.Devices 参照追加して、自分で書くorz

んでもって、作ったのがこちら。長いよw

/// <summary>
/// タスクトレイ専用アイコン制御するフォームクラス
/// </summary>
internal class MainForm : Form
{
    //動的変更するのでコンテナ化しとく
    private IContainer components = null;

    //アイコンも固定で持つ
    private NotifyIcon icon;

    //終わるよメニューは固定でもつ
    private ContextMenuStrip menu;

    private ToolStripMenuItem menuItem;

    //VBer必須のMy.Computerクラス
    private Computer myComputer;

    //タイマー
    private Timer timer;

    public MainForm()
    {
        //一回だけ処理
        ShowInTaskbar = false;
        initialize();
    }

    private void Close_Click(object sender, EventArgs e)
    {
        // 終わるよイベント 
        timer.Stop();
        timer.Dispose();
        Application.Exit();
    }

    private void initialize()
    {
        //初期化処理
        myComputer = new Computer();
        components = new System.ComponentModel.Container();
        icon = new NotifyIcon(components);

        //メニュー構成
        setMenu();

        //通知アイコン設定
        setNotifyIcon();

        //タイマー初期化
        timer = new Timer();
        timer.Interval = 1000;
        timer.Tick += new EventHandler(intervalCheck);
        timer.Start();
    }

    private void intervalCheck(object sender, EventArgs e)
    {
        // タイマーイベント 
        timer.Stop();
        setNotifyIcon();
        Refresh();
        timer.Start();
    }

    private void setMenu()
    {
        //メニュー作成
        menu = new ContextMenuStrip();
        menuItem = new ToolStripMenuItem();

        // 簡単な単語の場合、リソース提供のテキストにしておくと、
        // あとでGlobal化するとき困らないw
        menuItem.Text = Properties.Resources.M0001;
        //イベントハンドラ登録
        menuItem.Click += new EventHandler(Close_Click);

        menu.Items.Add(menuItem);
    }

    private void setNotifyIcon()
    {
        //VBerなら当たり前のことをC#でもさせてみるw
        //まさか、自分でクラスインスタンスとらないと
        //My.Computer使えないとかびっくりしたよ
        if (myComputer.Keyboard.NumLock)
        {
            icon.Icon = Properties.Resources.On;
            icon.Text = "NumLock ON";
        }
        else
        {
            icon.Icon = Properties.Resources.Off;
            icon.Text = "NumLock OFF";
        }
        icon.ContextMenuStrip = menu;
        icon.Visible = true;
    }
}

/// <summary>
/// NumLockすぐにわかるよインジケータ
/// </summary>
internal class MainWindow
{
    private static void Main()
    {
        MainForm rm = new MainForm();
        Application.Run();
    }
}

実行結果がこちら

image

あとはスタートアップに突っ込んでしばらく使っていろいろとチューニングしてくw

NUnit 3.x で Private メソッドをテストできるようにした

PrivateObjectクラスってすげー。MSTestって地味に頑張ってるんだなぁと思った。

やりたかったこと。とあるクラスのPrivateメソッドをテストしたかった。

MSTestの場合、PrivateObjectという大変使える?クラスがいたので、それを使ってテスト対象クラスインスタンスからInvokeすれば解決してたんだよね。

https://msdn.microsoft.com/ja-jp/library/microsoft.visualstudio.testtools.unittesting.privateobject.aspx

まぁ、これに同等の機能クラスが実はNUnit3.xに存在しないってことを知ってしまったわけだ。

なので、作ったw

internal static class CommonModule
{
  static public Object PrivateMethodInvoke(Object testClass, String methodName, Object[] parameters)
  {
    if (string.IsNullOrWhiteSpace(methodName))
        Assert.Fail("No Name");

    MethodInfo method = testClass.GetType().GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance);

    if (method == null)
        Assert.Fail(string.Format("{0} is not found", methodName));

    try
    {
        return method.Invoke(testClass, parameters);
    }
    catch (TargetInvocationException ex)
    {
        throw ex.InnerException;
    }
  }
}

例外についても今まで通りの形でテストしたかったんで、ちょいテク。Try-Catch でInvoke例外とってその原因の例外を実行時例外に代替。ILコスト? まーいいんじゃない? テストコードだし、実行じゃないしw

で、テスト用にサンプルクラス作る。Privateメソッドでパラメータの文字列を数字にして「倍返し」。

public class ForPrivate
{
  private int TwoTimeString(String BaseNum)
  {
    if (string.IsNullOrWhiteSpace(BaseNum))
        throw new InvalidCastException();

    int ret = int.MinValue;

    if (int.TryParse(BaseNum, out ret))
    {
        return ret * 2;
    }

    throw new InvalidCastException();
  }
}

そして、テストコードを書いてみる。正常ケースと例外ケースを。

public class ForPrivateTest
{
  [Test, Sequential]
  public void TwoTimeStringTest([Values("1", "4", "88", "-4", "0")] String prm1,
                                [Values(2, 8, 176, -8, 0)] int Exp1)
  {
    ForPrivate forPrivate = new ForPrivate();
    Assert.NotNull(forPrivate);

    int act = (int)CommonModule.PrivateMethodInvoke(forPrivate, "TwoTimeString", new object[] { prm1 });
    Assert.AreEqual(Exp1, act);
  }

  [Test, Sequential]
  public void TwoTimeStringExceptionTest([Values("", " ", "0.00004", null)] String prm1)
  {
    ForPrivate forPrivate = new ForPrivate();
    Assert.Throws<InvalidCastException>(() => CommonModule.PrivateMethodInvoke(forPrivate, "TwoTimeString", new object[] { prm1 }));
  }
}

実行してみた。

image

グリーンだよv

C#でEnum型用の拡張メソッドを作ってみた

JavaだとEnumの中にかけるから、基底Enum作って継承すれば良いんだけどさ。不便よね、もうw

C#やVBでのEnum型って所詮は Int だらけの構造体w<やめい

なので、汎用的にごにょりたいときは拡張メソッド使うw

使うEnum型はこういうの。

public enum ColorDefine
{
    [Description("不明")]
    None = 0,

    [Description("緑")]
    Green = 1,

    [Description("赤")]
    Red = 2,

    [Description("青")]
    Blue = 3,

    [Description("黄")]
    Yellow = 4
}

このEnumに日本語名称をつけて、これを拡張メソッドで取れるようにする。

public static string GetProperName<T>(this T Value) 
{
    if (!(typeof(T).IsEnum))
    {
        throw new InvalidEnumArgumentException();
    }

    FieldInfo fieldInfo = Value.GetType().GetField(Value.ToString());
    if (fieldInfo == null) return null;

    var attr = (DescriptionAttribute)fieldInfo.GetCustomAttribute(typeof(DescriptionAttribute));
    if (attr == null) return "";
    return attr.Description;
}

といいつつ、この拡張メソッドはすべての型に適用可能になっててインテリセンスにも出てくる。使っちゃうと例外かっとぶのでそれはそれで問題。なのでEnum型に絞ることを考えてみる。

まぁ、Whereで制約をつければ良いんだけどね。

https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/generics/constraints-on-type-parameters

image

じゃぁ、Enum型にはどの制約をつければいいのか、って時に、Docs大事。

https://docs.microsoft.com/ja-jp/dotnet/api/system.enum?view=netstandard-2.0

image

この記載をみれば、Where定義に何を書けばいいのかが判る。

ちな、ValueTypeはWhere定義では叱られるけど、ここは値型のstructでOK。だって所詮はInt型の(ry。というわけでリファクタリング。

public static string GetProperName<T>(this T Value) 
    where T : struct, IComparable, IConvertible, IFormattable
{
    if (!(typeof(T).IsEnum))
    {
        throw new InvalidEnumArgumentException();
    }

    FieldInfo fieldInfo = Value.GetType().GetField(Value.ToString());
    if (fieldInfo == null) return null;

    var attr = (DescriptionAttribute)fieldInfo.GetCustomAttribute(typeof(DescriptionAttribute));
    if (attr == null) return "";
    return attr.Description;
}

制約入れたのに、まだEnum型かどうかのチェックをしているのは、Enum型が所詮は Int の列挙型にすぎないので、この制約でもInt型変数に対してメソッドが使用可能となるから。なのでそれもあわせてテストに書いて

[Test, Sequential]
public void GetProperNameColorDefine([Values]ColorDefine prm1,
                          [Values("不明", "緑", "赤","青","黄")] string exp1)
{
    var act = Extenstions.GetProperName(prm1);
    Assert.AreEqual( exp1, act);
}
[Test]
public void GetProperNameColorDefineException()
{
    int Err = 200;
    Assert.Throws<InvalidEnumArgumentException>(() => Err.GetProperName());
}

実行したら

image

グリーンだよv

.NET Standard ライブラリ内のクラスコンストラクタで例外してみた

クラスの用途が明確で、呼び出し元クラスがどんな呼び出ししてくるかわかんない、ならコンストラクタで歯止めすれば自クラスはしんどくないよねw

規定サイズの配列をもつクラスのコンストラクタで、配列サイズを必ず受け取って、それが規定内でないなら例外しようと思ったの。

ま、そう考えた経緯はこの例外クラス、TypeInitializationException を見つけたからなんだけど。

https://docs.microsoft.com/ja-jp/dotnet/api/system.typeinitializationexception?view=netstandard-2.0

今回の場合、InnerExceptionに ArgumentOutOfRangeException を仕掛けるのも筋だよね。

で、つくってみた。中身は-1で初期化なw どっかの話で書いた一行コードw

public class TypeInitSampleWithList
{
  private int[] _myList;
  public int[] MyList => _myList;
  public TypeInitSampleWithList(int size)
  {
    if ((size < 1) || (16 < size))
    {
       throw new System.TypeInitializationException (typeof(TypeInitSampleWithList).FullName,
                  new System.ArgumentOutOfRangeException());
    }
    _myList = Enumerable.Repeat(1, size).Select(f => -1).ToArray();
  }
}

んでもって、テストコードは2つ。ふつーにインスタンス返すのと、例外するのと。NUnitって書くコード少なくていいわぁ。

[Test]
public void SampleConstructorTest([Range(2,16)]int prm1)
{
  TypeInitSampleWithList act = new TypeInitSampleWithList(prm1);

  Assert.NotNull(act);
  Assert.AreEqual(act.MyList.Length, prm1);
  Assert.IsTrue(act.MyList.All(f => f == -1));
}
[Test]
public void SampleConstructorExceptionTest([Values(-1, 0, 17)]int prm1)
{
  TypeInitializationException ex = Assert.Throws<TypeInitializationException>(() => new TypeInitSampleWithList(prm1));
  Assert.AreEqual(ex.TypeName, typeof(TypeInitSampleWithList).FullName);
  Assert.That(ex.InnerException, Is.TypeOf<ArgumentOutOfRangeException>());
}

作ってビルドしたらそのまま自動実行。dotnet test コマンド様様v

image

結果はグリーンだよ。うひょひょ。