20190915のC#に関する記事は6件です。

[C#][WindowForms]イベント受信の集約、イベント送信の分配

経緯

画面にボタンつけて、ボタンをダブルクリックするとイベントが自動で作られます。
開発者はそのあたりをあまり意識せずにプログラミングできます。
イベントを自作する必要があったり、UIとロジックを分離するなど、ひと手間かける必要があると
イベントを送信(発生)する方法や、イベントを受信(通知を受取)する方法の理解が必須になります。

深くなると、eventやdelegateといった単語が必要になりますが、今回はそのあたりはスルーした初級編です。
知りたい方はqiitaや公式にあると思いますのでどうぞ。

イベント受信の集約

画面上にボタン1、ボタン2があり、テキスト1、テキスト2にどちらのボタンが押されたか分かるように出力するとします。

このように書けば、テキストボックスにどちらのボタンイベントで通知されたか分かります。

Form1.cs
private void button1_Click(object sender, EventArgs e)
{
    textBox1.Text = "Button1_Click";
    textBox2.Text = "Button1_Click";
}

private void button2_Click(object sender, EventArgs e)
{
    textBox1.Text = "Button2_Click";
    textBox2.Text = "Button2_Click";
}

デザイナーファイルは以下のようになっています。

Form1.Designer.cs
    // 
    // button1
    // 
    this.button1.Location = new System.Drawing.Point(202, 186);
    this.button1.Name = "button1";
    this.button1.Size = new System.Drawing.Size(116, 26);
    this.button1.TabIndex = 0;
    this.button1.Text = "button1";
    this.button1.UseVisualStyleBackColor = true;
    this.button1.Click += new System.EventHandler(this.button1_Click);

    // 
    // button2
    // 
    this.button2.Location = new System.Drawing.Point(202, 219);
    this.button2.Name = "button2";
    this.button2.Size = new System.Drawing.Size(116, 26);
    this.button2.TabIndex = 3;
    this.button2.Text = "button2";
    this.button2.UseVisualStyleBackColor = true;
    this.button2.Click += new System.EventHandler(this.button2_Click);

ここに注目です。「this.button1.Click」に対して「+=」を使っています。
そして「this.button1_Click」メソッドがイベントハンドラーの引数にあります。

    this.button1.Click += new System.EventHandler(this.button1_Click);

つまり、ボタン2のクリックも「this.button1_Click」にすると、ボタン1でもボタン2でも同じメソッドが呼ばれます。
私はこれをイベント受信の集約と呼んでます。

Form1.Designer.cs
    this.button2.Click += new System.EventHandler(this.button1_Click);

以下のように変更すると、発生元の「sender」で判断が可能に。

Form1.cs
    private void button1_Click(object sender, EventArgs e)
    {
        var senderName = ((Button)sender).Text;
        textBox1.Text = senderName + "_Click";
        textBox2.Text = senderName + "_Click";
    }

使いどころとして、
異なる機能からそれぞれの機能のデータを送り、統一した処理を行うもの。例えばテーブル違いのデータ登録。

イベント配信の分配

こちらはちょっと特殊ケースです。
1つのボタンクリックでAとBそれぞれに通知したい場合です。
ボタン1のクリックはテキスト1に、ボタン2のクリックはテキスト2に出力するように変更します。

Form1.cs
    private void button1_Click(object sender, EventArgs e)
    {
        var senderName = ((Button)sender).Text;
        textBox1.Text = senderName + "_Click";
    }

    private void button2_Click(object sender, EventArgs e)
    {
        var senderName = ((Button)sender).Text;
        textBox2.Text = senderName + "_Click";
    }
Form1.Designer.cs
    // button1 省略
    this.button1.Click += new System.EventHandler(this.button1_Click);

    // button2 省略
    this.button2.Click += new System.EventHandler(this.button2_Click);

ボタン1クリックでもテキスト2に出力したい場合、「button2_Click」をイベントに追加します。
ボタン2クリックでもテキスト1に出力したい場合、「button1_Click」をイベントに追加します。

下記はコンストラクタで追加してますが、デザイナーでも問題ないです。

Form1.cs
    public Form1()
    {
        InitializeComponent();

        this.button1.Click += new EventHandler(button2_Click);
        this.button2.Click += new EventHandler(button1_Click);
    }

ボタン1をクリックすると、button1_Clickとbutton2_Clickに通知されます。
私はこれをイベント送信の分配と呼んでます。

使いどころとして、
1つの通知元から複数の機能へ通知をあげて処理を行うもの。例えばボタン1つ押すと各画面の初期化を行う。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

c#で正規表現の簡易テスタ作ってみた

やりたいこと

自分の書いた正規表現が意図通りに文字列にマッチするかを簡単にテストする。

画面キャプチャ

  • Match: 強調表示させる箇所を指定・・・何個目のmatchかを指定(1始まりで指定)。

  • Groups: 強調表示させる箇所を指定・・・Groupsをindexで指定。正規表現内の括弧()に対応。(0は全体をさす。)

(a)+みたいな指定に対してaaaをマッチさせると2個目以降は青字で表示する。

ソースコード

あまり汎用的なツールになってないので、目的によってソース改変して使うとよいかと。
2019.9.16: SelectionColorだと見づらいのでSelectionBackColorに変更しました。

using System;
using System.Drawing;
using System.Text.RegularExpressions;
using System.Runtime.InteropServices;
using System.Windows.Forms;

class Test:Form
{
    TextBox txtPtrn;
    TextBox txtPtrnCsharp;
    RichTextBox rtxt;
    SplitContainer spl;

    private const int EM_SETTABSTOPS = 0x00CB;
    private const long IMF_DUALFONT = 0x80;
    private const int WM_USER = 0x0400;
    private const int EM_SETLANGOPTIONS = WM_USER + 120;
    private const int EM_GETLANGOPTIONS = WM_USER + 121;

    [DllImport("user32")]
    public extern static IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);

    Color defaultBackColor;
    NumericUpDown nudM;
    NumericUpDown nudG;
    Font baseFont;
    Font boldFont;
    Font boldUnderFont;

    MatchCollection matches;

    Test()
    {
        spl = new SplitContainer();
        spl.Dock = DockStyle.Fill;
        spl.Orientation = Orientation.Horizontal;
        spl.IsSplitterFixed = true;
        Controls.Add(spl);

        txtPtrn = new TextBox();
        txtPtrn.TextChanged += TxtPtrn_TextChanged;
        txtPtrn.Multiline = false;
        txtPtrn.Size = new Size(200, 30);
        spl.Panel1.Controls.Add(txtPtrn);
        defaultBackColor = txtPtrn.BackColor;

        txtPtrnCsharp = new TextBox();
        txtPtrnCsharp.Location = new Point(0,30);
        txtPtrnCsharp.ReadOnly = true;
//      txtPtrnCsharp.TextChanged += TxtPtrnCsharp_TextChanged;
        txtPtrnCsharp.Multiline = false;
        txtPtrnCsharp.Size = new Size(200, 30);
        spl.Panel1.Controls.Add(txtPtrnCsharp);


        Label lblM = new Label();
        lblM.Top = txtPtrnCsharp.Bottom + 10;
        lblM.Text = "Match:";
        lblM.TextAlign = ContentAlignment.MiddleRight;
        lblM.Width = 50;
        spl.Panel1.Controls.Add(lblM);

        nudM = new NumericUpDown();
        nudM.Minimum =    1;
        nudM.Maximum =   99;
        nudM.Width = 50;
        nudM.Left = lblM.Right;
        nudM.Top = lblM.Top;
        nudM.ValueChanged += (sender,e)=>{UpdateColor();};
        spl.Panel1.Controls.Add(nudM);


        Label lblG = new Label();
        lblG.Left = nudM.Right;
        lblG.Top = lblM.Top;
        lblG.Text = "Groups:";
        lblG.TextAlign = ContentAlignment.MiddleRight;
        lblG.Width = 50;
        spl.Panel1.Controls.Add(lblG);

        nudG = new NumericUpDown();
        nudG.Minimum =    0;
        nudG.Maximum =   99;
        nudG.Width = 50;
        nudG.Top = lblM.Top;
        nudG.Left = lblG.Right;
        nudG.ValueChanged += (sender,e)=>{UpdateColor();};
        spl.Panel1.Controls.Add(nudG);


        rtxt = new RichTextBox();
        rtxt.Top = lblG.Bottom;
        rtxt.Multiline = true;
        rtxt.WordWrap = false;
        rtxt.AcceptsTab = true;
        rtxt.ScrollBars = RichTextBoxScrollBars.Both;
        rtxt.Dock = DockStyle.Fill;
        spl.Panel2.Controls.Add(rtxt);

        // 実行順序を変えるとtab幅がおかしくなるので注意
        baseFont = new Font("MS ゴシック",     12); // rtxt.Font.Size);
        boldFont = new Font(baseFont.FontFamily, baseFont.Size, baseFont.Style | FontStyle.Bold);
        boldUnderFont = new Font(baseFont.FontFamily, baseFont.Size, baseFont.Style | FontStyle.Bold | FontStyle.Underline);
        rtxt.Font = boldFont;
        SetFixFont(rtxt);
        SetTabStop(rtxt, 4);

        rtxt.TextChanged += (sender,e)=>{DoRegexTest();};

        // spl.IsSplitterFixed = true; はユーザがつかめないようにするだけで、固定してくれないっぽい。まぎらわしい。。
        Load      += (sender,e)=>{spl.SplitterDistance = nudG.Bottom;};
        Resize    += (sender,e)=>{spl.SplitterDistance = nudG.Bottom;};
        ResizeEnd += (sender,e)=>{spl.SplitterDistance = nudG.Bottom;};
    }


    void TxtPtrn_TextChanged(object sender, EventArgs e)
    {
        string s = txtPtrn.Text;
        txtPtrnCsharp.Text = "\"" + Regex.Replace(s, "[\\\\\"]","\\$&")+"\"";

        DoRegexTest();
    }

    void DoRegexTest()
    {
        Regex r;
        try {
            r = new Regex(txtPtrn.Text);
        }
        catch ( ArgumentException ) {
            txtPtrn.BackColor = Color.Red;
            matches = null;
            UpdateColor();
            return;
        }

        txtPtrn.BackColor = defaultBackColor;
        matches = r.Matches(rtxt.Text);
        UpdateColor();
    }

    void UpdateColor()
    {
        if (matches==null){
            return;
        }

        int pos = rtxt.SelectionStart;
        int len = rtxt.SelectionLength;

        rtxt.SelectAll();
        rtxt.SelectionBackColor = Color.White;
//      rtxt.SelectionColor = Color.Black;
        rtxt.SelectionFont  = boldFont;

        int targetG = (int)nudG.Value;
        int i=0;

        foreach (Match m in matches) {
            i++;
            if ( i == (int)nudM.Value ) {
                if ( m.Groups.Count > targetG ) {
                    Group g = m.Groups[targetG];
                    CaptureCollection cc = g.Captures;

                    bool firstFlag = true;
                    foreach (Capture c in cc) {
                        rtxt.Select(c.Index, c.Length);
                        rtxt.SelectionFont = boldUnderFont;
                        rtxt.SelectionBackColor = (firstFlag)?Color.LightGreen:Color.Blue;
//                      rtxt.SelectionColor = (firstFlag)?Color.Red:Color.Blue;
                        firstFlag = false;
                    }
                }
            }
        }
        rtxt.Select(pos, len);
    }

    static IntPtr SetFixFont(TextBoxBase t)
    {
        IntPtr lParam = SendMessage(t.Handle, EM_GETLANGOPTIONS, new IntPtr(0), new IntPtr(0));
        lParam = new IntPtr( ((long)lParam) & (~IMF_DUALFONT));
        return SendMessage(t.Handle, EM_SETLANGOPTIONS, new IntPtr(0), lParam);
    }

    static IntPtr SetTabStop(TextBoxBase t, int tabSize)
    {
        int[] tabarray = new int[] { tabSize*4 };
        int wparam = tabarray.Length;

        IntPtr parray = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(int)) * tabarray.Length);
        Marshal.Copy(tabarray, 0, parray, tabarray.Length);
        IntPtr ret = SendMessage(t.Handle, EM_SETTABSTOPS, new IntPtr(wparam), parray);
        return ret;
    }


    [STAThread]
    public static void Main(string[] args)
    {
        Application.Run(new Test());
    }
}

感想

RichTextBoxの癖の強さよ。
スペースとかタブとか改行とかを表示したいが挫折。。

参考サイト

こんなチェッカもあるらしい

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

using エイリアス ディレクティブを入れ子にする (c#)

前提

  • c# 5~

試したこと

  • using エイリアス ディレクティブを使用して、型の別名を定義し、その別名を使用して新たに別名を定義してみました。

エラー

using SpreadSheet = Matrix<string>;
using SpreadSheetBook = Dictionary<string, SpreadSheet>;
namespace SpreadSheetManager {
namespace SpreadSheetManager {
    using SpreadSheet = Matrix<string>;
    using SpreadSheetBook = Dictionary<string, SpreadSheet>;

可能

using SpreadSheet = Matrix<string>;
namespace SpreadSheetManager {
    using SpreadSheetBook = Dictionary<string, SpreadSheet>;
namespace SpreadSheetManager {
    using SpreadSheet = Matrix<string>;
    namespace xxx {
        using SpreadSheetBook = Dictionary<string, SpreadSheet>;

正解

namespace SpreadSheetManager {
    using SpreadSheet = Matrix<string>;
    using SpreadSheetBook = Dictionary<string, Matrix<string>>;

解ったこと

  • 同じnamespace階層では、定義した別名は、他の別名の定義で使用できません。
  • 親のnamespace階層で定義した別名は、子階層の別名定義で使用可能です。
  • using エイリアス ディレクティブを、入れ子にする必要はありません。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C#の正規表現Regexの内部を覗いてみた(RegexCode)

目的

正規表現でやらかすことがそこそこあるので、c#のRegexが解釈した正規表現を可視化したい。
図示はできていないが、内部構造を出力できた。

アプローチ

マッチした部分を可視化しようと思ったが(※)、オートマトン的なものが表示できたほうがうれしい気がしたので、ILSpy.exeでRegexクラス周辺をあさってみた。
(※:結局やってみた)

private Regex(string pattern, RegexOptions options, TimeSpan matchTimeout, bool useCache)

に下記のようなコードがあったので、下記3行目のcodeが使えそうと判断した。

Regexのコードから抜粋
// 注意:要所だけ抜粋して、間のコードも消してます。
1 if (cachedCodeEntry == null) {
2     RegexTree regexTree = RegexParser.Parse(pattern, roptions);
3     code = RegexWriter.Write(regexTree);
4     regexTree = null;
5 }
6 if (UseOptionC() && factory == null) {
7     code = null;
8 }

で、このメンバ codeRegexinternal なフィールドなのですが、
リフレクションで強引にアクセスできます。(普通はやっちゃダメ)

codeのクラスRegexCodeを見てみると、

internal const int Goto = 38;
internal int[] _codes;
internal string[] _strings;

オペコードっぽい定義がある。
というわけでこれを表示してみます。

出力結果

正規表現 abc(de)f を分析にかけてみた。
※ [a-c]とかをかけると、stringsに表示できないデータが含まれるので注意。

>regexReflectionTest.exe abc(de)f
--codes--
n:17
0:Lazybranch(16)
2:Setmark
3:Multi(0)
5:Setmark
6:Multi(1)
8:Capturemark(1,-1)
11:One(102)
13:Capturemark(0,-1)
16:Stop
--strings--
n:2
0:abc
1:de

どうやら
Multi は部分文字列を示す。(直後の_code[i+1]_stringsのindex)
One は文字を示す。(直後の_code[i+1]が文字コード)
SetmarkとCapturemarkはMatchGroupsで取り出せる情報と関係していそう。
といった感じ。

環境

RegexCodeクラスのコードが変わると正常に動作しない懸念があります。
下記で特定できているのか分からんですが、環境を晒しておきます。

C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe
Microsoft (R) Visual C# Compiler version 4.8.3752.0
for C# 5

ソースコード

ソースコード
regexReflectionTest.cs
using System;
using System.Drawing;
using System.Text.RegularExpressions;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Reflection;

class Test
{
    enum RegexCodeOpCode {
        Onerep = 0,
        Notonerep = 1,
        Setrep = 2,
        Oneloop = 3,
        Notoneloop = 4,
        Setloop = 5,
        Onelazy = 6,
        Notonelazy = 7,
        Setlazy = 8,
        One = 9,
        Notone = 10,
        Set = 11,
        Multi = 12,
        Ref = 13,
        Bol = 14,
        Eol = 15,
        Boundary = 16,
        Nonboundary = 17,
        Beginning = 18,
        Start = 19,
        EndZ = 20,
        End = 21,
        Nothing = 22,
        Lazybranch = 23,
        Branchmark = 24,
        Lazybranchmark = 25,
        Nullcount = 26,
        Setcount = 27,
        Branchcount = 28,
        Lazybranchcount = 29,
        Nullmark = 30,
        Setmark = 31,
        Capturemark = 32,
        Getmark = 33,
        Setjump = 34,
        Backjump = 35,
        Forejump = 36,
        Testref = 37,
        Goto = 38,
        Prune = 39,
        Stop = 40,
        ECMABoundary = 41,
        NonECMABoundary = 42,
        Mask = 63,
        Rtl = 64,
        Back           = 128,  // bit to indicate that we're backtracking.
        Back2          = 256,  // bit to indicate that we're backtracking on a second branch.
        Ci             = 512,  
    }


    [STAThread]
    public static void Main(string[] args)
    {
        if(args.Length==0)return;

        Regex r=null;

        try{
            r = new Regex(args[0]);
        }
        catch(Exception e){
            Console.WriteLine(e);
            return;
        }

        Type typeR = r.GetType();
        FieldInfo fieldRC = typeR.GetField("code", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);

        dynamic rc = fieldRC.GetValue(r);

        Type typeRC = rc.GetType();
        FieldInfo fieldRcCodes  = typeRC.GetField("_codes", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);
        FieldInfo fieldRcString = typeRC.GetField("_strings", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);

        MethodInfo miOpcodeSize = typeRC.GetMethod("OpcodeSize", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);


        int[] t    = (int[])fieldRcCodes.GetValue(rc);
        string[] s = (string[])fieldRcString.GetValue(rc);

        Console.WriteLine("--codes--");

        Console.Write("n:");
        Console.WriteLine(t.Length);
        for(int i=0;i<t.Length;i++) {
            int x = t[i];
            Console.Write(i);
            Console.Write(":");
            Console.Write((RegexCodeOpCode)x);

            int argCount = ((int)miOpcodeSize.Invoke(null, new object[] {x} )) - 1;

            if (argCount > 0) {
                Console.Write("(");
                for(int j=i+1;j<=i+argCount&&j<t.Length;j++) {
                    if(j>i+1){Console.Write(",");}
                    Console.Write(t[j]);
                }
                Console.Write(")");
                i += argCount;
            }
            Console.WriteLine();
        }

        Console.WriteLine("--strings--");

        Console.Write("n:");
        Console.WriteLine(s.Length);
        for(int i=0;i<s.Length;i++) {
            Console.Write(i);
            Console.Write(":");
            Console.WriteLine(s[i]??"<<NULL>>");
        }
    }
}

参考サイト

分析追記

a|bb を分析してみた。

--codes--
n:15
0:Lazybranch(14)
2:Setmark
3:Lazybranch(9)
5:One(97)
7:Goto(11)
9:Multi(0)
11:Capturemark(0,-1)
14:Stop
--strings--
n:1
0:bb
↓の図解は手書き。

さらにその後

グラフ表示させてみた。※箱は手動でドラッグして配置
using System;
using System.Drawing;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Reflection;


public class MyNode
{
    int _codeIndex; // RegexCode._codeのindex
    int _selfIndex; // nodeとしてのindex
    List<int> _destCodeIndices;
    int[] _destNodeIndices; // nodeとしてのindex
    string _textOpCode;
    string _text;
    bool _charClassNotFlag;

    public int CodeIndex{get{return _codeIndex;}}
    public int[] NextIndices{get{return _destNodeIndices;}} // 横着...

    public bool CharClassNotFlag{get{return _charClassNotFlag;}}

    public string Text{get{return _text;}}
    public string TextOpCode{get{return _textOpCode;}}

    public enum RegexCodeOpCode {
        Onerep = 0,//
        Notonerep = 1,
        Setrep = 2,//
        Oneloop = 3,//
        Notoneloop = 4,
        Setloop = 5,
        Onelazy = 6,//
        Notonelazy = 7,
        Setlazy = 8,//
        One = 9,//
        Notone = 10,//
        Set = 11,//
        Multi = 12,//
        Ref = 13,
        Bol = 14,
        Eol = 15,
        Boundary = 16,
        Nonboundary = 17,
        Beginning = 18,
        Start = 19,
        EndZ = 20,
        End = 21,
        Nothing = 22,
        Lazybranch = 23,//
        Branchmark = 24,//
        Lazybranchmark = 25,
        Nullcount = 26,
        Setcount = 27,
        Branchcount = 28,
        Lazybranchcount = 29,
        Nullmark = 30,//
        Setmark = 31,//
        Capturemark = 32,//
        Getmark = 33,
        Setjump = 34,
        Backjump = 35,
        Forejump = 36,
        Testref = 37,
        Goto = 38,//
        Prune = 39,
        Stop = 40,//
        ECMABoundary = 41,
        NonECMABoundary = 42,
        Mask = 63,
        Rtl = 64,
        Back           = 128,  // bit to indicate that we're backtracking.
        Back2          = 256,  // bit to indicate that we're backtracking on a second branch.
        Ci             = 512,  
    }


    public MyNode(int selfIndex, int codeIndex, RegexCodeOpCode opCode, int[] args, string[] strings)
    {
        _selfIndex = selfIndex;
        _codeIndex = codeIndex;
        _text = "";
        _textOpCode = "";

        switch ( opCode ) {
        case RegexCodeOpCode.Notone:/* through */
            _charClassNotFlag = true;
            break;
        default:
            _charClassNotFlag = false;
            break;
        }

        _destCodeIndices = new List<int>();

        switch ( opCode ) {
        case RegexCodeOpCode.Lazybranch:/* through */
        case RegexCodeOpCode.Branchmark:
            _destCodeIndices.Add(codeIndex + args.Length + 1);
            _destCodeIndices.Add(args[0]);
            break;
        case RegexCodeOpCode.Goto: 
            _destCodeIndices.Add(args[0]);
            break;
        case RegexCodeOpCode.Stop:
            break;
        default:
            _destCodeIndices.Add(codeIndex + args.Length + 1);
            break;
        }

        _textOpCode = opCode.ToString();

        switch ( opCode ) {
        case RegexCodeOpCode.Lazybranch:
            _textOpCode = "LazyBr";
            break;
        case RegexCodeOpCode.One:/* through */
        case RegexCodeOpCode.Notone:
            _text = MyParseCharClass(((char)args[0]).ToString());
            break;
        case RegexCodeOpCode.Onerep: // 指定回数繰り返し
            _textOpCode += "{"+args[1].ToString()+"}";
            _text = MyParseCharClass(((char)args[0]).ToString());
            break;
        case RegexCodeOpCode.Setrep: // 指定回数繰り返し
            _textOpCode += "{"+args[1].ToString()+"}";
            _text = MyParseCharClass(strings[args[0]]);
            break;
        case RegexCodeOpCode.Oneloop: // 0~最大で指定回数繰り返し
            _textOpCode += "{0 to "+args[1].ToString()+"}";
            _text = MyParseCharClass(((char)args[0]).ToString());
            break;
        case RegexCodeOpCode.Onelazy: // 0~最大で指定回数繰り返し (最短マッチ)
            _text = MyParseCharClass(((char)args[0]).ToString());
            break;
        case RegexCodeOpCode.Setlazy: // 0~最大で指定回数繰り返し (最短マッチ)
            _text = MyParseCharClass(strings[args[0]]);
            break;
        case RegexCodeOpCode.Set:
            _text = MyParseCharClass(strings[args[0]]);
            break;
        case RegexCodeOpCode.Multi:
            _text = MyParseCharClass(strings[args[0]]);
            break;
        default:
            break;
        }
    }

    public void UpdateDestIndices(List<MyNode> nodes)
    {
        _destNodeIndices = new int[_destCodeIndices.Count];
        for ( int i = 0 ; i < _destCodeIndices.Count ; i++ ) {
            _destNodeIndices[i] = -1;
            foreach ( MyNode node in nodes ) {
                if ( node._codeIndex == _destCodeIndices[i] ) {
                    _destNodeIndices[i] = node._selfIndex;
                    break;
                }
            }
            if ( _destNodeIndices[i] == -1 ) {
                throw new Exception("bug!!!");
            }
        }
    }

    public static string MyParseCharClass(string s)
    {
        StringBuilder sb = new StringBuilder();

        foreach ( char c in s ) {
            if ( (int)c<0x20 || ((int)c>=0x7F && (int)c<=0xFF) ) {
                sb.Append("[x" + ((int)c).ToString("X02")+"]");
            }
            else {
                sb.Append(c);
            }
        }
        return sb.ToString();
    }


    public static List<MyNode> CreateNodes(Regex r)
    {
        var nodes = new List<MyNode>();

        Type typeR = r.GetType();
        FieldInfo fieldRC = typeR.GetField("code", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);

        dynamic rc = fieldRC.GetValue(r);

        Type typeRC = rc.GetType();
        FieldInfo fieldRcCodes  = typeRC.GetField("_codes", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);
        FieldInfo fieldRcString = typeRC.GetField("_strings", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);

        MethodInfo miOpcodeSize = typeRC.GetMethod("OpcodeSize", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);


        int[] opCodes = (int[])fieldRcCodes.GetValue(rc);
        string[] strings = (string[])fieldRcString.GetValue(rc);

        int nodeCount = 0;

        for ( int i=0 ; i<opCodes.Length ; i++ ) {
            int x = opCodes[i];
            int argCount = ((int)miOpcodeSize.Invoke(null, new object[] {x} )) - 1;
            if ( argCount < 0 ) {
                throw new Exception("Unexcepted opcode size.");
            }

            int[] args = new int[argCount];

            for ( int j = 0 ; j < argCount ; j++ ) {
                if ( i+j+1 >= opCodes.Length ) {
                    throw new Exception("Argument parse error.");
                }
                args[j] = opCodes[i+j+1];
            }

            nodes.Add(new MyNode(nodeCount, i, (RegexCodeOpCode)x, args, strings));

            nodeCount++;
            i += argCount;
        }

        foreach ( var node in nodes ) {
            node.UpdateDestIndices(nodes);
        }

        return nodes;
    }
}


// 描画用
public class MyEdge
{
    int _fromIndex;
    int _toIndex;
    Point[] _travelPoints; // 経由点の座標 (両端座標はダミー)

    public Point[] TravelPoints{get{return _travelPoints;}} // ちょっと横着..


    public MyEdge(int fromI, int toI)
    {
        _fromIndex = fromI;
        _toIndex = toI;
        _travelPoints = new Point[2];
    }

    public void UpdateTerminalPoints(MyGraph graph)
    {
        _travelPoints[0] = graph.CenterPointOf(_fromIndex);
        _travelPoints[_travelPoints.Length-1] = graph.CenterPointOf(_toIndex);
        Point tmp0 = graph.MyIntersect(_fromIndex, _travelPoints[1]);
        Point tmp1 = graph.MyIntersect(_toIndex  , _travelPoints[_travelPoints.Length-2]);
        _travelPoints[0]                      = tmp0;
        _travelPoints[_travelPoints.Length-1] = tmp1;
    }
}

public class MyGraph
{
    List<MyNode> _nodes;
    Rectangle[] _rects; // same length of _nodes

    List<MyEdge> _edges;

    public Point CenterPointOf(int i)
    {
        return new Point( ( _rects[i].Left + _rects[i].Right  ) / 2 ,
                          ( _rects[i].Top  + _rects[i].Bottom ) / 2 );
    }

    public MyGraph(List<MyNode> nodes)
    {
        _nodes = nodes;

        _rects = new Rectangle[nodes.Count];

        for ( int i = 0 ; i < _rects.Length ; i++ ) {
            _rects[i] = new Rectangle(new Point(5+i*60, 100), new Size(50,50));
        }
        if ( _rects.Length > 0 ) {
            _rects[0].Y -= 50;
        }
        if ( _rects.Length >= 2 ) {
            _rects[_rects.Length-1].Y -= 50;
        }

        _edges = new List<MyEdge>();

        for ( int i=0 ; i<nodes.Count ; i++ ) {
            foreach ( int toI in nodes[i].NextIndices ) {
                _edges.Add(new MyEdge(i,toI));
            }
        }
    }


    public void DrawGraph(Graphics g, Font fnt)
    {
        Pen pen = new Pen(Color.Blue, 1.5f);
        pen.CustomEndCap = new System.Drawing.Drawing2D.AdjustableArrowCap(4, 4); // 矢印

        foreach ( MyEdge edge in _edges ) {
            edge.UpdateTerminalPoints(this);
            g.DrawLines(pen, edge.TravelPoints);
        }

        for ( int i = 0 ; i < _rects.Length ; i++ ) {
            Rectangle rect = _rects[i];
            if ( rect.Width >= 1 ) {
                g.FillRectangle(Brushes.White, rect);
                g.DrawRectangle(Pens.Blue,     rect);
                g.DrawString(_nodes[i].CodeIndex.ToString(), fnt, Brushes.Blue,  rect.Left+2, rect.Top);
                g.DrawString(_nodes[i].TextOpCode,           fnt, Brushes.Blue,  rect.Left+2, rect.Top+18);
                g.DrawString(_nodes[i].Text,                 fnt, Brushes.Black, rect.Left+2, rect.Top+36);
            }
        }
    }

    public int MyHitTest(Point p, out Point location)
    {
        // 昇順で検索すると、z-order(描画の順序)が奥のほうを先に拾ってしまうので、降順で検索する
        for ( int i = _rects.Length - 1 ; i>=0 ; i-- ) {
            if ( _rects[i].Contains(p) ) {
                location = _rects[i].Location;
                return i;
            }
        }
        location = new Point(0,0);
        return -1;
    }

    public void MoveNodeTo(int nodeIndex, Point p)
    {
        _rects[nodeIndex].X = p.X;
        _rects[nodeIndex].Y = p.Y;
    }

    // a が -intmax の場合はダメだけど..
    private int MyAbsInt(int a)
    {
        return (a<0)?(-a):a;
    }

    // p <---> CenterPointOf(nodeIndex)(=cと置く) をつなぐ線L(端点をcとする半直線)と、rectの交点を求める
    public Point MyIntersect(int nodeIndex, Point p)
    {
        // まず、cを中央にもつ長方形rectと、半直線Lの交点がどの線と交わるかを判定する
        // (memo: y座標は数学の座標系で考える)
        //
        // \1 /
        // 2 c 0
        // / 3\
        //
        // 傾きと、x同士,y同士の大小関係から判定する。ゼロ除算に注意する。

        Point t = new Point();
        Point c = CenterPointOf(nodeIndex);
        int W = _rects[nodeIndex].Width;
        int H = _rects[nodeIndex].Height;

        if ( W == 0 || H == 0 ) { // 例外処理
            return c;
        }
        if ( p.X == c.X && p.Y == c.Y ) { // 例外処理
            return c;
        }

        //   abs((p.Y-c.Y)/(p.X-c.X)) <= H/W  なら  領域0か2
        if ( MyAbsInt(p.Y-c.Y)*(long)W <= MyAbsInt(p.X-c.X)*(long)H ) {
            int dY = (int)((((long)W/2) * (p.Y-c.Y))/(p.X-c.X));
            // p.X==c.X なら、p.Y==c.Yであり、先の例外処理でこのifには入らないのでゼロ除算は回避できる(W>0が前提)

            if ( c.X < p.X ) {
                t.X = c.X + W/2;
                t.Y = c.Y + dY;
            }
            else{
                t.X = c.X - W/2;
                t.Y = c.Y - dY;
            }
        }
        else {
            int dX = (int)((((long)H/2) * (p.X-c.X))/(p.Y-c.Y));

            if ( c.Y < p.Y ) {
                t.X = c.X + dX;
                t.Y = c.Y + H/2;
            }
            else{
                t.X = c.X - dX;
                t.Y = c.Y - H/2;
            }
        }

        return t;
    }
}



class Test : Form
{
    MyGraph graph;
    PictureBox pct;
    const int WIDTH = 800;
    const int HEIGHT = 300;
    Font fnt;
    Point dragStartPoint;
    Point dragInitialNodeLocation;
    int dragStartNodeIndex;

    Test(Regex r)
    {   
        fnt = this.Font;
        dragStartNodeIndex = -1;

        ClientSize = new Size(WIDTH, HEIGHT);

        pct = new PictureBox();
        pct.Dock = DockStyle.Fill;
        pct.Image = new Bitmap(WIDTH, HEIGHT);
        Controls.Add(pct);

        Console.WriteLine("Creating regex nodes...");
        List<MyNode> nodes = MyNode.CreateNodes(r);
        Console.WriteLine("done.");

        graph = new MyGraph(nodes);

        Load += (sender, e)=>{RedrawGraph();};
        pct.MouseDown += Pct_MouseDown;
        pct.MouseMove += Pct_MouseMove;
        pct.MouseUp   += (sender,e)=>{dragStartNodeIndex = -1;};
    }

    void RedrawGraph()
    {
        Graphics g = Graphics.FromImage(pct.Image);
        g.Clear(Color.LightGray);
        graph.DrawGraph(g, fnt);
        g.Dispose();
        pct.Refresh();
    }

    void Pct_MouseDown(object sender, MouseEventArgs e)
    {
        if ( (e.Button & MouseButtons.Left) == MouseButtons.Left ) {
            dragStartPoint = e.Location;
            dragStartNodeIndex = graph.MyHitTest(dragStartPoint, out dragInitialNodeLocation);
        }
    }

    void Pct_MouseMove(object sender, MouseEventArgs e)
    {
        if ( (e.Button & MouseButtons.Left) == MouseButtons.Left ) {
            if ( dragStartNodeIndex >= 0 ) {
                if ( e.X > 0 && e.Y > 0 && e.X < WIDTH && e.Y < HEIGHT ) {
                    Point p = new Point();
                    p.X = dragInitialNodeLocation.X + (e.X - dragStartPoint.X);
                    p.Y = dragInitialNodeLocation.Y + (e.Y - dragStartPoint.Y);
                    graph.MoveNodeTo(dragStartNodeIndex, p);
                    RedrawGraph();
                }
            }
        }
    }

    [STAThread]
    public static void Main(string[] args)
    {
        if(args.Length==0)return;

        Regex r=null;

        Console.WriteLine("Creating regex...");
        try{
            r = new Regex(args[0]);
            Console.WriteLine("done.");
        }
        catch(Exception e){
            Console.WriteLine(e);
            return;
        }

        Application.Run(new Test(r));
    }
}

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C#でKeyHookする

押したキーのコードをConsoleに出力します。

出力例

key:0x0041:A
scanCode:0x001e
key:0x00a4:LMenu
scanCode:0x0038
key:0x005b:LWin
scanCode:0x005b
key:0x00ad:VolumeMute
scanCode:0x0020
key:0x0070:F1
scanCode:0x003b
key:0x001b:Escape
scanCode:0x0001

ソースコード

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

public class MyKeyHook
{
    private const int WH_KEYBOARD_LL = 13;

    public delegate IntPtr KeyboardHookCallback(int nCode, uint msg, ref KBDLLHOOKSTRUCT kbdllhookstruct);

    [DllImport("user32.dll")]
    private static extern IntPtr SetWindowsHookEx(int idHook, KeyboardHookCallback lpfn, IntPtr hMod, uint dwThreadId);

    [DllImport("user32.dll")]
    public static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, uint msg, ref KBDLLHOOKSTRUCT kbdllhookstruct);

    [StructLayout(LayoutKind.Sequential)]
    public struct KBDLLHOOKSTRUCT
    {
        public uint vkCode;
        public uint scanCode;
        public uint flags;
        public uint time;
        public IntPtr dwExtraInfo;
    }

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool UnhookWindowsHookEx(IntPtr hhk);

    public static IntPtr SetHook(KeyboardHookCallback callbackFunc)
    {
//        IntPtr h = Marshal.GetHINSTANCE(typeof(KeyboardHook).Assembly.GetModules()[0]);
        return SetWindowsHookEx(WH_KEYBOARD_LL, callbackFunc, IntPtr.Zero, 0);
    }

    public static void UnHook(ref IntPtr hookHandle)
    {
        if ( hookHandle != IntPtr.Zero ) {
            UnhookWindowsHookEx(hookHandle);
            hookHandle = IntPtr.Zero;
        }
    }
}

class TestKeyHook : Form
{
    private const int WM_KEYDOWN = 0x100;
    private const int WM_KEYUP   = 0x101;
    private const int WM_SYSKEYDOWN = 0x104;
    private const int WM_SYSKEYUP = 0x105;

    MyKeyHook.KeyboardHookCallback hookCallback; // SetWindowsHookEx に渡すコールバック関数が勝手にGCされないようにメンバ宣言する
    IntPtr hookHandle;

    private IntPtr HookProcedure(int nCode, uint msg, ref MyKeyHook.KBDLLHOOKSTRUCT s)
    {
        // Fnキーは捕捉できないようである そもそもOSとして認識できないっぽい
        if (nCode >= 0 ) {
            if ( msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN) {
                // AltはWM_SYSKEYDOWNのようである
                // AltのkeyupはWM_KEYUPでくるっぽい(状況による?)
                Console.Write( "key:" );
                Console.Write( "0x" + s.vkCode.ToString("x04")+":" );
                Console.WriteLine( (System.Windows.Forms.Keys)s.vkCode );
                Console.Write( "scanCode:" );
                Console.WriteLine( "0x" + s.scanCode.ToString("x04") );
            }
            // return (IntPtr)1; // cancel
        }
        return MyKeyHook.CallNextHookEx(hookHandle, nCode, msg, ref s);
    }

    TestKeyHook()
    {
        hookCallback = HookProcedure;
        Load += (sender, e)=>{hookHandle = MyKeyHook.SetHook(hookCallback);};
        Closed += (sender, e)=>{MyKeyHook.UnHook(ref hookHandle);};
    }

    [STAThread]
    static void Main()
    {
        Application.Run(new TestKeyHook());
    }
}

注意事項

  • フック関数はソースコード中のコメントにもある通り、メンバー宣言してください。
    そうしないと、暫く経つとGCによって回収されて動かなくなる(例外すら吐かない)ことがあります。
    (どこかのサイトで解説見ましたが、どこだったか忘れました。)

  • SendKeysと組み合わせると無限ループしたりキューがおかしくなったりしがちなので注意。
    (そもそもSendKeysは単品でも安定しないのでお勧めできない、、、代わりないのかな、、)

参考サイト

参考というか、使いたい部分を抜粋編集させて頂きました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Ubuntu18.04, C#】UbuntuでC#プログラムをコンパイル・実行したい

目的

WindowsPCでC#プログラムを作成したが、それをUbuntuの仮想マシンで実行したくなった。
さらにソースコードにちょこちょこ変更を加えながら実行したくもなった。
よってC#プログラムを仮想マシン上でコンパイル・実行だけできるような環境を作ろうとした。

前提

・Windows10のPCでUbuntu18.04の仮想マシンを作成し、そこで作業

環境構築手順

やることの概略

「Mono」という、異なるプラットフォームで動く.NETアプリを作るためのソフトをUbuntuにインストールする。

手順

1.Ubuntuにログインする。

2.Mono公式の「Download」のページのLinuxタブを開いたところに実行すべきコマンドが記載されているので、ぞの通りに実行する。
記事執筆時点でのコマンドは以下の通りであった。

sudo apt install gnupg ca-certificates
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF
echo "deb https://download.mono-project.com/repo/ubuntu stable-bionic main" | sudo tee /etc/apt/sources.list.d/mono-official-stable.list
sudo apt update

 

サイトはこんな感じになっている↓

 
で、自身は1行目の「sudo apt install gnupg ca-certificates」で早速こけた。

root@ubuntu-VirtualBox:/home/ubuntu# apt install gnupg ca-certificate
Reading package lists... Done
Building dependency tree       
Reading state information... Done
E: Unable to locate package ca-certificate

 
この場合は、「apt install gnupg ca-certificate」する前に「apt-get update」する必要があるのだそう
askubuntuの:"Ubuntu Docker Container update-ca-certificates: command not found"より)。
で、その通りにやってみたらうまくいった↓

コマンド実行し直し・・・

sudo apt-get update
sudo apt install gnupg ca-certificates

結果は?

root@ubuntu-VirtualBox:/home/ubuntu# apt install gnupg ca-certificates
Reading package lists... Done
Building dependency tree       
Reading state information... Done
ca-certificates is already the newest version (20180409).
gnupg is already the newest version (2.2.4-1ubuntu1.2).
0 upgraded, 0 newly installed, 0 to remove and 15 not upgraded.

 

3.以下のコマンドを実行してMonoをインストール。
しばらくログがだ~っと出力されて、いつの間にかインストールが終わる。

sudo apt install mono-devel

 

4.monoコマンドが使えるようになっているか確認するため、「mono --version」を実行する。
以下のような感じに出力されればOK。

コンパイル・実行の動作確認

適当な場所に簡単なC#ソースコードを作成して試してみる。
今回は「/home/[ユーザ名]/」配下に「Sources」ディレクトリを作り、その中にソースコードを置くことにした。

1.ディレクトリを移動してからviコマンドで新規ファイルを.cs拡張子で作り・・・

cd /home/[ユーザ名]/Sources
vi Hello.cs

 

2.手抜きコードを書いて保存して閉じる

using System;

namespace Hello
{
    class HelloWorld
    {
        static void Main()
        {
            Console.WriteLine("Hello World!!");
            Console.ReadLine();
        }
    }
}

 
3.以下のコマンドを実行してソースコードをコンパイルする。

msc Hello.cs
コマンド実行後に現在のディレクトリを見てみると、新たにソースコードのファイル名と同名のexeファイルが作られていることがわかる。

 
4.そしていよいよexeファイルを実行する

mono Hello.exe
ざわざわ・・・

((゚∀゚))

参考にさせて頂いたサイト

Monoの公式サイト
LinuxでもC#プログラミング(導入編)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む