[C#]なんでも実行ありのプログレスバーFormを作成する。

時間がかかる処理とか大量の処理がある場合、実行しているときはプログレスバー表示して、で、ついでに途中でキャンセルしたい、ああ、今なにしているかもみたい、、なんてのはよくある話です。

キャンセルボタンをつける場合、スレッド処理を考えますが、今のC#ではいろいろな実現方法が考えられます。
僕がはじめ作成していたのは、BackgroundWorkerクラスを使用して、あらかじめ実行する関数の型定義を固定(引数int,戻り値intなど)して決めておき、それを順次実行させる、というものでした。

もどかしかったのは実行させる関数定義が固定されているので、引数を追加したい場合、関係ない関数の定義を変えたり、また新たな関数定義を実行できるようにコーディングしたりしたところでした。
そこで、よく見かけるInvokeメソッドをヒントになんでも実行できるプログレスバーFormを作ってみた。

Control.Invokeメソッド定義

Control.Invoke (Delegate)
Control.Invoke (Delegate, Object[])

↑これを参考に次の関数をデリゲートと引数に分解する。
※Func<>,Action<>はDelegateのtypedefみたいなものです。

実行させたい関数定義の例

int Func1();                 // Delegate -> Func<int>,            object[] -> null
string Func2(int v);         // Delegate -> Func<string,int>,     object[] -> object[]{"string"}
int Func3(int v, string s);  // Delegate -> Func<int,string,int>, object[] -> object[]{123, "string"}
int Func4(ParamClass param); // Delegate -> Func<ParamClass,int>, object[] -> object[]{param}
void Func5();                // Delegate -> Action,               object[] -> null

やりたいこと
戻り値はint(0正常,0以外異常)だけど、引数可変の処理が幾つかがあるが、これをまるごとずばっとプログレスバーFormで処理したい。

MiniProgressForm.csコントロール

MiniProgressFormクラスソース

        /// <summary>
        /// 関数と引数とラベルを格納
        /// </summary>
        public List<Tuple<Delegate, object[],string>> FuncList { get; set; }

        /// プログレスバーに関するプロパティ値
        public ProgressBarStyle ProgBarStyle { get; set; }
        public bool CancelEnable { get; set; }
        public bool LabelEnable { get; set; }
        
        ///  キャンセル押下用
        private bool _cancel = false;

        /// <summary>
        /// コンストラクタ
        /// </summary>
        public MiniProgressForm()
        {
            InitializeComponent();

            FuncList = new List<Tuple<Delegate, object[], string>>();
            CancelEnable = false;
            LabelEnable = false;

            /// ThreadTask実行中に閉じられると困るので操作させない。
            this.ControlBox = false;
        }

        /// <summary>
        /// 画面がロードされた際に、1度だけ実行される
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void MiniProgressForm_Load(object sender, EventArgs e)
        {
            button1.Enabled = CancelEnable;
            label1.Visible = LabelEnable;
            progressBar1.Style = ProgBarStyle;
            if (ProgBarStyle != ProgressBarStyle.Marquee)
            {
                progressBar1.Minimum = 0;
                progressBar1.Maximum = FuncList.Count;
            }

            /// 処理実行
            ThreadTask();
        }

        /// <summary>
        /// 処理を別スレッドで実行させる関数
        /// </summary>
        private void ThreadTask()
        {
            var task = Task.Factory.StartNew(() =>
            {
                ///
                /// スレッドからFormのコントロールを操作するためのデリゲートを定義
                ///

                /// Formを閉じる
                Action EndForm = () => this.Close();
                /// ラベルを変える
                Action<string> LabelChange = (s) => label1.Text = s;
                /// プログレスバーの進捗を変更する
                Action<int> ProgressChange = (p) => progressBar1.Value = p;

                ///
                /// 順次処理開始
                /// 
                for (int i = 0; i < FuncList.Count; i++)
                {
                    var funcSet = FuncList[i];
                    
                    /// ラベルを変更
                    if (LabelEnable)
                    {
                        this.Invoke(LabelChange, new object[] { funcSet.Item3 });
                    }

                    /// プログレスバーを変更
                    if (ProgBarStyle != ProgressBarStyle.Marquee)
                    {
                        this.Invoke(ProgressChange, new object[] { i });
                    }
                    
                    /// retには戻り値があるメソッドが値を返す。戻りがない関数(void型)を実行した場合nullになる。
                    /// FuncListに格納されているメソッドは必ず0正常,0以外異常の関数が格納されているとする
                    var ret = funcSet.Item1.DynamicInvoke(funcSet.Item2);
                    /// 戻りがnull以外で戻り値が0以外の時、エラー
                    if ((ret != null) && ((int)ret != 0))
                    {
                        MessageBox.Show("エラーが発生しました。");
                        break;
                    }

                    /// キャンセルボタンが押されていたら途中で終了する
                    if (_cancel)
                    {
                        MessageBox.Show("キャンセルされました");
                        break;
                    }
                }

                /// 処理が終了すると画面が閉じられる
                this.Invoke(EndForm);
            });

        }

        private void button1_Click(object sender, EventArgs e)
        {
            /// キャンセルボタンの押下フラグON
            _cancel = true;
        }
    }

さっそく使ってみる
次の3つの関数を実行させたい。

        /// <summary>
        /// 引数なし、int型の戻り値の関数
        /// </summary>
        /// <returns></returns>
        private int Func1()
        {
            System.Threading.Thread.Sleep(1);

            Trace.WriteLine("Func1>");

            return 0;
        }

        /// <summary>
        /// int型引数を1つ、int型の戻り値の関数
        /// </summary>
        /// <param name="val"></param>
        /// <returns></returns>
        private int Func2(int val)
        {
            System.Threading.Thread.Sleep(1);

            Trace.WriteLine("Func2>val = " + val.ToString());

            return 0;
        }

        /// <summary>
        /// string型引数を1つ、int型の戻り値の関数
        /// </summary>
        /// <param name="str"></param>
        /// <returns></returns>
        private int Func3(string str)
        {
            System.Threading.Thread.Sleep(1);

            Trace.WriteLine("Func3>str = " + str.ToString());

            return 0;
        }

上記、3つの関数を実行している間、プログレスバーを表示する。

            /// プログレスバーFormのインスタンス
            var miniPrgBar = new MiniProgressForm();

            /// プログレスバーForm内で実行させるための関数、引数、実行時ラベルのセット
            List<Tuple<Delegate, object[], string>> SetFunc = new List<Tuple<Delegate, object[], string>>();

            /// いっぱい実行してみる
            for (int i = 0; i < 1000; i++)
            {
                /// 実行のポイント
                /// デリゲートはFunc<>,Action<>を使用して、自分が使いたい関数とその関数の引数を指定する。
                /// ちょうど、Invoke()メソッドと似た使い方ができる。
                /// ここでは、必ずint値を戻す関数をデリゲートとして定義するものとする
                /// Tupleを使用しているがクラスを定義することもできる。てかクラスのほうがわかりやすい。
                /// 例)
                /// class SetFunc
                /// {
                ///     public Delegate Method;
                ///     public object[] Params;
                ///     public string Label;
                /// }

                SetFunc.Add(Tuple.Create<Delegate, object[], string>(new Func<int>(() => Func1()), null, "Func1()を実行しています"));
                SetFunc.Add(Tuple.Create<Delegate, object[], string>(new Func<int, int>((p) => Func2(p)), new object[] { 123 }, "Func2()を実行しています"));
                SetFunc.Add(Tuple.Create<Delegate, object[], string>(new Func<string, int>((p) => Func3(p)), new object[] { "(--;" }, "Func3()を実行しています"));
                               
                
                SetFunc.Add(Tuple.Create<Delegate, object[], string>(new Func<int>(() => Func1()), null, "Func1()を実行しています"));
                SetFunc.Add(Tuple.Create<Delegate, object[], string>(new Func<int, int>((p) => Func2(p)), new object[] { 456 }, "Func2()を実行しています"));
                SetFunc.Add(Tuple.Create<Delegate, object[], string>(new Func<string, int>((p) => Func3(p)), new object[] { "(;;" }, "Func3()を実行しています"));
                SetFunc.Add(Tuple.Create<Delegate, object[], string>(new Func<int>(() => Func1()), null, "Func1()を実行しています"));
                SetFunc.Add(Tuple.Create<Delegate, object[], string>(new Func<int, int>((p) => Func2(p)), new object[] { 789 }, "Func2()を実行しています"));
                SetFunc.Add(Tuple.Create<Delegate, object[], string>(new Func<string, int>((p) => Func3(p)), new object[] { "(^^;" }, "Func3()を実行しています"));
            }

            ///
            /// プログレスバーFormの設定
            ///

            /// 実行関数をぶっこむ
            miniPrgBar.FuncList = SetFunc;

            /// プログレスバースタイルを指定
            miniPrgBar.ProgBarStyle = ProgressBarStyle.Continuous;

            /// ラベルの変更ありなし
            miniPrgBar.LabelEnable = true;

            /// 途中でキャンセルありなし
            miniPrgBar.CancelEnable = true;

            /// プログレスバーの実行
            miniPrgBar.ShowDialog();

        }


実行画面

今回作成したプロジェクトはこちら(自己責任で)
MiniProgress.zip

[C#]配列型を特定のデータでずばっと初期化する。

C#でバイナリデータを扱う場合は、List<byte>型変数を使用したりします。
データに特定の値(たとえば’K’)を与えたい場合、次のように書くことができます。

            var buff = new List<byte>();
            Enumerable.Range(0,1024).ToList().ForEach(p => buff.Add((byte)'K'));

[上原ひろみ]ひろみちゃんのこと。

なれなれしくひろみちゃんとか言ってるけど、知り合いではありませんw
僕は上原ひろみさんがとっても大好きなんです。


この画像はモスクワでの写真。胸に手を当て、感謝の気持ちを噛み締めているところが印象的です。

今はすごく有名になりました。僕がひろみちゃんを知ったのは大学の頃。TBSの情熱大陸を見たのがきっかけでした。結構たくさんいると思います。情熱大陸を見たのがきっかけって。僕は情熱大陸のVTRをもっていたりするのですが、若かりし頃(ええ、今も若いですよ)のひろみちゃんは、テレビやMCのときと印象がとっても違う。なんというか、貪欲ですごい一生懸命なひろみちゃんなんですね。なんとも形容しがたいけど、主張するひろみちゃんというか、、。でも、そこが本人のパワー。マネージャーつけないで、日々海外を飛び回っているひろみちゃんって、ほんとすごいなって思うんです。

今年も年の瀬が近づいて来ました。ひろみちゃんの年末の日本ツアーは僕の毎年の行事です。今年もひろみちゃんのライブ楽しみだなー♪

今日のひろみちゃん。
創造のるつぼ:新盤発表、旅するジャズピアニスト・上原ひろみ 毎日新聞 2012年11月07日 大阪夕刊
http://mainichi.jp/feature/news/20121107ddf012040023000c.html

[C#]objectとJSONとXMLの変換。

JSON,object,XMLの変換のお話。

ちょっとした設定データやデータベースを作成するほどの量でもないデータにはXMLやJSONにシリアライズして保存すると便利。JSONはWebの言語では当たり前かもしれないけど、C#プログラマーにとってはまだまだマイナー。
でも、JSONはXMLと違ってタグと値がシンプルなので、その分データ量がXMLより少なくできる。データ量が多量になるとXMLではタグがある分、データ量が肥大するので、ネットワーク越しのやりとりにもJSON(文字列)するとまた便利。

JSONとXMLはデータ構造を表現するクラスと相性が良い。

例えば、次のような個人データクラスを作ったとする。

    class Person
    {
        public int Number { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Address { get; set; }
        public string TEL { get; set; }
    }

このクラスのデータを複数保持できるような形にして、XMLとJSONに保存すると次のようになる。
・XML

<?xml version="1.0" encoding="utf-8"?>
<manage>
  <persons>
    <Number>0</Number>
    <FirstName>YOSHIKO</FirstName>
    <LastName>AOKI</LastName>
    <Address>TOKYO</Address>
    <TEL>1234578</TEL>
  </persons>
</manage>

・JSON

{"manage":{"persons":[{"Number":0,"FirstName":"YOSHIKO","LastName":"AOKI","Address":"TOKYO","TEL":"1234578"}]}}

データ量はJSONのほうが少なくできる。可視性はXMLのほうが優れているけど。

で、本題。自分で作成したクラスをJSONもしくはXMLにシリアライズするとする。ライブラリは.Net界では有名なJson.Netを使用する。.Net FrameworkのDataContractJsonSerializerはクラスに属性を付加しないといけないので、ちょっとめんどくさい。他にもDynamicJsonなどがある。

参照
.NET FrameworkでJSONデータを処理する
http://codezine.jp/article/detail/5868

Json.Net
http://json.codeplex.com/

DynamicJson
http://dynamicjson.codeplex.com/

Json(string)からobject,Json(string)からXMLなどはユーティリティとして関数化してもいいけど、拡張メソッドにするとちょっと便利。

ここでは、ExtensionsClassとして次のような拡張メソッドを定義した。

    /// <summary>
    /// object <-> JSON <-> XML の相互変換用の拡張メソッド
    /// </summary>
    public static class ExtensionsClass
    {
        /// <summary>
        /// object -> JSON(string)
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public static string Object2Json(this object obj)
        {
            return Newtonsoft.Json.JsonConvert.SerializeObject(obj);
        }

        /// <summary>
        /// JSON(string) -> object
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="str"></param>
        /// <returns></returns>
        public static object Json2Object<T>(this string str)
        {
            return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(str);
        }

        /// <summary>
        /// JSON(string) -> XmlDocument
        /// </summary>
        /// <param name="str"></param>
        /// <returns></returns>
        public static XmlDocument Json2XmlDocument(this string str)
        {
            return Newtonsoft.Json.JsonConvert.DeserializeXmlNode(str);
        }

        /// <summary>
        /// JSON(string) -> XDocument
        /// </summary>
        /// <param name="str"></param>
        /// <returns></returns>
        public static XDocument Json2XDocument(this string str)
        {
            return Newtonsoft.Json.JsonConvert.DeserializeXNode(str);
        }

        /// <summary>
        /// XmlDocument -> XDocument
        /// </summary>
        /// <param name="xmldoc"></param>
        /// <returns></returns>
        public static XDocument XmlDocument2XDocument(this XmlDocument xmldoc)
        {
            return XDocument.Load(new XmlNodeReader(xmldoc));
        }

        /// <summary>
        /// object -> XDocument
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public static XDocument Object2XDocument(this object obj)
        {
            return Json2XDocument(Object2Json(obj));
        }

        /// <summary>
        /// XDocument -> JSON(string)
        /// </summary>
        /// <param name="xdoc"></param>
        /// <returns></returns>
        public static string XDocumnet2Json(this XDocument xdoc)
        {
            return Newtonsoft.Json.JsonConvert.SerializeXNode(xdoc);
        }

        /// <summary>
        /// XDocument -> object
        /// </summary>
        /// <param name="xdoc"></param>
        /// <returns></returns>
        public static object XDocument2Object<T>(this XDocument xdoc)
        {
            return Json2Object<T>(XDocumnet2Json(xdoc));
        }
    }

上記のように汎用的な型(object,string)に拡張メソッドを書くのはちょっと、、、ってときは、ダミーのクラスを作成して、データクラスのベースクラスにするといいと思う(拡張メソッドの引数のthis の次の型にダミークラスを指定する)。

簡易的な拡張メソッドなので、使用にはちょっと注意が必要。データのフォーマットが間違っているとすぐふっとぶ。ライブラリを使用するときはtry-catchを入れておくのは必要かもね。ここでは省略しちゃうけど。

ここではJSONとXMLの両方を扱うので、両方のフォーマットに適合するようにデータクラスを定義する必要がある。Personクラスを複数保持できるデータクラスは次のようになる。

    /// <summary>
    /// 個人データ
    /// </summary>
    class Person
    {
        public int Number { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Address { get; set; }
        public string TEL { get; set; }

        public Person()
        {
            Number = 0;
            FirstName = LastName = Address = TEL = "";
        }

        public Person(int number,string fname, string lname, string addr, string tel)
        {
            Number = number;
            FirstName = fname;
            LastName = lname;
            Address = addr;
            TEL = tel;
        }
    }

    /// <summary>
    /// Personクラスの配列を保持
    /// </summary>
    class Persons
    {
        public List<Person> persons { get; set; }
        
        public Persons()
        {
            persons = new List<Person>();
        }
    }

    /// <summary>
    /// ROOTノードクラス
    /// ※XML形式用にTop階層用に必要
    /// このクラスは1つのプロパティ要素しか持たない
    /// </summary>
    class Manage
    {
        public Persons manage { get; set; }

        public Manage()
        {
            manage = new Persons();
        }
    }

ManageクラスはXMLで表すところのroot(TOP階層)になるので、XML化では必須。ちなみに、JSONのみ扱う場合は必要ない。

データを用意する
テストデータとして、次のようなデータを用意した。


        Manage _manage = new Manage();

        /// <summary>
        /// 初期値展開用
        /// </summary>
        void DataSet()
        {
            _manage.manage.persons.Add(new Person(0,"YOSHIKO", "AOKI", "TOKYO", "1234578"));
            _manage.manage.persons.Add(new Person(1,"MAMI", "MANAKA", "TOKYO", "9999999"));
            _manage.manage.persons.Add(new Person(2,"SHIGEAKI", "ITOI", "OSAKA", "33333333"));
            _manage.manage.persons.Add(new Person(3,"AI", "YOMOGI", "OKIANAWA", "444444444"));
            _manage.manage.persons.Add(new Person(4,"KEN", "AOSHIMA", "NAGANO", "000000000"));
        }

ManageクラスをJSON形式に変換して保存する
拡張メソッド(ExtensionsClass)を使用してManageクラスのデータ(_manage)をJSON形式にシリアライズしてファイルに保存してみる。JSONはstringになるので、扱いはとっても楽。

System.IO.File.WriteAllText("persons.json", _manage.Object2Json());

ManageクラスをXML形式に変換して保存する
XMLでの保存も、拡張メソッド(JSON経由だけど^^;)を使用してXDocumentクラスに変換することで、保存が非常に楽にできる。

_manage.Object2XDocument().Save("persons.xml");

JSON形式をManageクラスに変換する
今度は逆にファイルから読み取ったデータをデシリアライズしてデータクラスに変換する。これもJSONライブラリで一発。うーんなんて便利なんでしょう。

// JSON形式をデシリアライザしてManageクラスにキャストして読み込み
_manage = (Manage)System.IO.File.ReadAllText(ofd.FileName).Json2Object<Manage>(); 

XML形式をManageクラスに変換する
XMLからの読み取りも拡張メソッドを組み合わせてManageクラスに変換する。

// XML形式をデシリアライザしてManageクラスにキャストして読み込み
_manage = (Manage)((XDocument.Load(ofd.FileName).XDocument2Object<Manage>()));

C#ではデータクラスはとっても意義がある気がする
このように、objectとJSONとXMLへの変換はこうしてライブラリによって平和に解決することができる。JSONとXMLは似ていて、Json.Netとかも内部的にはXMLのようなデータ構造をもっていると思う(たぶん)。また、クラスのプロパティへのバインドも動的なものだし、正直、速度的なパフォーマンスは期待できない。よって、軽量なデータなどには向いているけど、大量のデータを扱うにはそれなりに設計が必要かも。

LINQtoObjectではデータしかもたないクラスへの操作はとっても便利。objectからJSONとかXMLとか、汎用的なフォーマットに簡単に変換できるのってのはとっても意義がある気がする。汎用的なフォーマットへの変換はC#だけではなく、他の言語とかでも使用出来るので、ネットワーク越しのフォーマットにも使用出来るし。例えばクライアントはC#,VBとかでサーバサイドはPythonやRubyとかで。

今回作成したプロジェクトはこちら(自己責任で)
DataIroiro.zip

[C#]すばやく画面を切り替える。

実家で暮らしていると、突然家族に自分の部屋のドアをあけられるかもわかりません。

そこで、ショートカットキーを使って、すばやくパソコンの画面を切り替えるプログラムを書いてみた。

ポイントとなるのは、バックグランドで起動させておき、いつでも反応させることと、音をミュートにしてしまうところ。
はじめはESCキー連打で切り替えを行おうかと思ってたけど、最終的に選んだのはSpaceキー+ESCキー同時押し。この組み合わせが押しやすかったので。
問題点は、キーフックを強制している画面(ゲームとか)だと利かないってところかな。

キーボードのグローバルフックとサウンドのMUTE操作は意外と面倒なのでネットで参照したライブラリを使用。
今回使用したShell32.dllとサウンドのAPIとグローバルキーフックのdllはほかにも何かに使えそう。
どうやって可能にしているかってのを押さえておくのは大事なことだけど、既存の組み合わせでコーディングが少ないのってやっぱりいいね。

開発環境
Windows7 Home Premium(64bit)
Visual Studio 2010 C#

作成方法

1.C#のWindowsフォームアプリケーションを新規作成する。
2.参照dllに次のものを追加する。

[サウンド操作用]
Vista Core Audio API Master Volume Control
http://www.codeproject.com/Articles/18520/Vista-Core-Audio-API-Master-Volume-Control
CoreAudioApi.dllを参照に追加。

[キーフック操作用]
Processing Global Mouse and Keyboard Hooks in C#
http://www.codeproject.com/Articles/7294/Processing-Global-Mouse-and-Keyboard-Hooks-in-C
Gma.UserActivityMonitor.dllを参照に追加。

[デスクトップの表示用]
COM参照からMicrosoft Shell Controls And Automation(C:\Windows\SysWOW64\shell32.dll)を追加。

3.Formクラスのソースのusingに次を追加する。

using Shell32;
using Gma.UserActivityMonitor;
using CoreAudioApi;

4.Formクラスのプライベート変数として次のコードを追加する。

        // Spaceキー + Escapeキー の判定用
        bool _flag = false;

        // サウンドデバイス操作
        MMDevice _soundDev;

5.Formクラスのコンストラクタに次のコードを追加する。


            // サウンドデバイス操作用APIの初期化
            var devEnum = new MMDeviceEnumerator();
            _soundDev = devEnum.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia);

            // KeyDownイベントのグローバルフック処理追加
            HookManager.KeyDown += (s, e) =>
            {

                // Spaceの押下ON
                if (e.KeyCode == Keys.Space)
                {
                    // フラグON
                    _flag = true;
                }

                // Escapceの押下ON
                if (e.KeyCode == Keys.Escape)
                {
                    // Spaceが押下されている
                    if (_flag)
                    {
                        // デスクトップの表示・非表示の切り替え
                        var shell = new Shell();
                        shell.ToggleDesktop();

                        // サウンドをMUTE ON/OFFする
                        _soundDev.AudioEndpointVolume.Mute ^= true;
                    }
                }
            };

            // KeyUpイベントのグローバルフック処理追加
            // Spaceキーの押下が終了したときは何もしないように
            // フラグをOFFにする
            HookManager.KeyUp += (s, e) =>
            {
                // Escapceの押下解除
                if (e.KeyCode == Keys.Space)
                {
                    // フラグOFF
                    _flag = false;
                }
            };

            // 起動時はFormを非表示にする、ように見せる 
            this.ShowInTaskbar = false;
            this.WindowState = FormWindowState.Minimized;

6.ビルドの構成マネージャーを開き、プラットフォームを[Any CPU]に変更する。
(これをしないとShell32が動かない)

7.二重起動防止策として、Program.csに以下を追加する。

            // Mutexにより二重起動防止
            // "KeyHook"の部分は任意の文字列
            var mutex = new System.Threading.Mutex(false, "KeyHook");
            if (mutex.WaitOne(0, false) == false)
            {
                return;
            }

            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new KeyHook());

            // Mutex開放
            mutex.ReleaseMutex();

8.ビルドして終了。
スタートアップにショートカットのコピーをおいておけば、起動時から反応してくれるから楽。なお、強制終了したい場合は、タスクマネージャーを開いて強制終了させるしかないけど。

今回作成したプロジェクトはこちら(自己責任で)
keyhook.zip