20200101のC#に関する記事は7件です。

C# - AutomationElementの情報を取得するツールをつくった(Windows10)

機能

クリックした座標にあるコントロールの情報(実行パスとAutomationElementInformation)を取得します。
(ウィンドウハンドルを取得して、該当座標を含む、子AutomationElementを再帰的に探します。重なっている場合はどれか1つになります。)

AutomationElementの使い方は、手前味噌ですが
Windows10でBASIC認証画面にIDとパスワードを自動入力するソフトを作ったなどを参照ください。

注意事項

  • マルチスクリーン環境での動作は未確認です。
  • マウスのグローバルフックを使用しています。

スクリーンショット

例:getボタンを押して、コマンドプロンプトのタイトルバーをクリックした場合。
image.png

ソースコード

using System;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading;
using System.Windows.Automation;
using System.Windows.Forms;


public static class NativeMethods
{
    [StructLayout(LayoutKind.Sequential)]
    public struct POINT
    {
        public int x;
        public int y;
    }

    public const int WH_MOUSE_LL = 14;
    public delegate IntPtr HookProc(int code, IntPtr wParam, IntPtr lParam);

    [DllImport("user32.dll", SetLastError = true)]
    public static extern IntPtr SetWindowsHookEx(int idHook, HookProc callback, IntPtr hInstance, int threadId);

    [DllImport("user32.dll", SetLastError = true)]
    public static extern bool UnhookWindowsHookEx(IntPtr hHook);

    [DllImport("user32.dll")]
    public static extern IntPtr CallNextHookEx(IntPtr hHook, int nCode, IntPtr wParam, IntPtr lParam);

    public const int WM_MOUSEMOVE   = 0x0200;
    public const int WM_LBUTTONDOWN = 0x0201;
    public const int WM_LBUTTONUP   = 0x0202;
    public const int WM_RBUTTONDOWN = 0x0204;
    public const int WM_RBUTTONUP   = 0x0205;
    public const int WM_MOUSEWHEEL  = 0x020A;
    public const int WM_MOUSEHWHEEL = 0x020E;

    [DllImport("user32.dll",SetLastError = true)]
    public static extern IntPtr WindowFromPoint(POINT point);

    [DllImport("user32.dll",SetLastError = true)]
    public static extern IntPtr GetAncestor(IntPtr hWnd, int gaFlags);
    public const int GA_PARENT    = 1;
    public const int GA_ROOT      = 2;
    public const int GA_ROOTOWNER = 3;

    [DllImport("User32.dll")]
    public static extern IntPtr GetDC(IntPtr hwnd);

    [DllImport("User32.dll")]
    public static extern void ReleaseDC(IntPtr hwnd, IntPtr dc);

    [DllImport("user32.dll", SetLastError = true)]
    public static extern int GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool SetProcessDPIAware();
}


public class AutomationTest : Form
{
    IntPtr _hHook;
    NativeMethods.HookProc _handler;
    GCHandle _hookProcHandle;
    NativeMethods.POINT _lastPoint;
    bool _hookExitReq;

    System.Windows.Forms.Timer timer;
    Button btn;
    TextBox txtProc;
    ListView lsv;

    AutomationTest()
    {
        NativeMethods.SetProcessDPIAware();

        timer = new System.Windows.Forms.Timer();
        timer.Interval = 10;
        timer.Tick += (s,e)=>{Timer_Tick();};

        btn = new Button(){Text="get"};
        btn.Click += (s,e)=>{Btn_Click();};
        Controls.Add(btn);

        txtProc = new TextBox(){
            ReadOnly = true,
            Location = new System.Drawing.Point(100,0)
        };
        txtProc.KeyDown += (sender,e)=>{
            if (e.Control && e.KeyCode == Keys.A) { txtProc.SelectAll(); }
        };
        Controls.Add(txtProc);

        lsv = new ListView(){
            Location = new System.Drawing.Point(0, 40),
            FullRowSelect = true,
            GridLines = true,
            HideSelection = false,
            MultiSelect = false,
            View = View.Details
        };
        lsv.MouseDoubleClick += Lsv_MouseDoubleClick;
        lsv.Columns.AddRange(new ColumnHeader[]{
            new ColumnHeader(){Text="ClassName",Width=150},
            new ColumnHeader(){Text="AutomationId",Width=80},
            new ColumnHeader(){Text="ControlType",Width=100},
            new ColumnHeader(){Text="FrameworkId",Width=50},
            new ColumnHeader(){Text="Name",Width=150},
            new ColumnHeader(){Text="ItemType",Width=50},
            new ColumnHeader(){Text="X"     ,Width=50,TextAlign=HorizontalAlignment.Right},
            new ColumnHeader(){Text="Y"     ,Width=50,TextAlign=HorizontalAlignment.Right},
            new ColumnHeader(){Text="Width" ,Width=50,TextAlign=HorizontalAlignment.Right},
            new ColumnHeader(){Text="Height",Width=50,TextAlign=HorizontalAlignment.Right},
        });
        Controls.Add(lsv);

        this.Text = "AutomationElement Information Getter";
        this.ClientSize = new System.Drawing.Size(700, 500);

        FormClosed += (s,e)=>{UnHook();};

        Load += (s,e)=>{MyResize();};
        Resize += (s,e)=>{MyResize();};
        ResizeEnd += (s,e)=>{MyResize();};
    }

    void MyResize()
    {
        int w = ClientSize.Width - txtProc.Left;
        int h = ClientSize.Height - lsv.Top;
        if (w<50){w=50;}
        if (h<50){h=50;}
        lsv.Size = new System.Drawing.Size(ClientSize.Width, h);
        txtProc.Width = w;
    }

    void Timer_Tick()
    {
        UnHook();
        if ( !timer.Enabled ) {
            return;
        }
        timer.Stop();
        btn.Enabled = true;

        var p = new System.Windows.Point(_lastPoint.x, _lastPoint.y);

        IntPtr hWnd = NativeMethods.WindowFromPoint(_lastPoint);
        hWnd = NativeMethods.GetAncestor(hWnd, NativeMethods.GA_ROOT);
        var elem = AutomationElement.FromHandle(hWnd);

        {
            int pid;
            NativeMethods.GetWindowThreadProcessId(hWnd, out pid);
            var proc = System.Diagnostics.Process.GetProcessById(pid);
            txtProc.Text = proc.MainModule.FileName;
        }

        if ( elem != null) {
            lsv.BeginUpdate();
            try {
                AutomationElement.AutomationElementInformation elemInfo;
                do {
                    try {
                        elemInfo = elem.Current;
                    }
                    catch( ElementNotAvailableException ) {
                        return;
                    }

                    lsv.Items.Add(AeToListItem(elemInfo));
                    elem = FindNextElementFromPoint(elem, p);
                }
                while(elem != null);

                DrawPointAndRectToScreen(p, elemInfo.BoundingRectangle);
            }
            finally {
                lsv.EndUpdate();
            }
        }
    }

    AutomationElement FindNextElementFromPoint(AutomationElement elem, System.Windows.Point p)
    {
        var childElements = elem.FindAll(TreeScope.Children, Condition.TrueCondition);

        foreach(AutomationElement childElem in childElements) {
            AutomationElement.AutomationElementInformation elemInfo;
            try {
                elemInfo = childElem.Current;
            }
            catch( ElementNotAvailableException ) {
                return null;
            }

            if ( elemInfo.BoundingRectangle.Contains(p) ) {
                return childElem;
            }
        }
        return null;
    }

    ListViewItem AeToListItem(AutomationElement.AutomationElementInformation a)
    {
        System.Windows.Rect r = a.BoundingRectangle;

        return new ListViewItem(new string[]{
            a.ClassName,
            a.AutomationId,
            a.ControlType.ToString(),
            a.FrameworkId,
            a.Name,
            a.ItemType,
            r.X.ToString(),
            r.Y.ToString(),
            r.Width.ToString(),
            r.Height.ToString()
        });
    }

    void Btn_Click()
    {
        try {
            _hookExitReq = false;
            SetHook();
        }
        catch (System.ComponentModel.Win32Exception e) {
            MessageBox.Show(e.ToString());
            return;
        }

        btn.Enabled = false;
        lsv.Items.Clear();
    }

    void Lsv_MouseDoubleClick(object sender, MouseEventArgs e)
    {
        ListViewHitTestInfo info = lsv.HitTest(e.Location);
        if ( info.SubItem != null ) {
            SubForm f = new SubForm(info.SubItem.Text);
            f.ShowDialog();
        }
    }

    void DrawPointAndRectToScreen(System.Windows.Point p, System.Windows.Rect rect)
    {
        IntPtr desktopDC = NativeMethods.GetDC(IntPtr.Zero);

        if (desktopDC == IntPtr.Zero) {
            // failed
            return;
        }

        try {
            var pen = new System.Drawing.Pen(System.Drawing.Color.Blue, 6.0f);
            using (var g = System.Drawing.Graphics.FromHdc(desktopDC)) {
                g.DrawLine(pen, (float)(p.X-5), (float)(p.Y-5), (float)(p.X+5), (float)(p.Y+5));
                g.DrawLine(pen, (float)(p.X-5), (float)(p.Y+5), (float)(p.X+5), (float)(p.Y-5));
                g.DrawRectangle(pen, (float)rect.X, (float)rect.Y, (float)rect.Width, (float)rect.Height);
            }
        }
        finally {
            NativeMethods.ReleaseDC(IntPtr.Zero, desktopDC);
        }
    }

    void SetHook()
    {
        IntPtr module = IntPtr.Zero;
        _handler = CallbackProc;
        _hookProcHandle = GCHandle.Alloc(_handler);
        _hHook = NativeMethods.SetWindowsHookEx(NativeMethods.WH_MOUSE_LL, _handler, module, 0);

        if ( _hHook == IntPtr.Zero ) {
            // failed
            int errorCode = Marshal.GetLastWin32Error();
            _hookProcHandle.Free();
            _handler = null;
            throw new System.ComponentModel.Win32Exception(errorCode);
        }
    }

    void UnHook()
    {
        if ( _hHook != IntPtr.Zero ) {
            NativeMethods.UnhookWindowsHookEx(_hHook);
            _hHook = IntPtr.Zero;
            _hookProcHandle.Free();
            _handler = null;
        }
    }

    IntPtr CallbackProc(int nCode, IntPtr wParam, IntPtr lParam)
    {
        if ( nCode < 0 || _hookExitReq ) {
            return NativeMethods.CallNextHookEx(_hHook, nCode, wParam, lParam);
        }
        else {
            if ( (long)wParam == NativeMethods.WM_MOUSEMOVE ) {
                return NativeMethods.CallNextHookEx(_hHook, nCode, wParam, lParam);
                // 注意:WM_MOUSEMOVE をキャンセルすると、カーソル表示が更新されない
            }
            else {
                if ( (long)wParam == NativeMethods.WM_LBUTTONDOWN ||
                     (long)wParam == NativeMethods.WM_RBUTTONDOWN ) {
                    _hookExitReq = true;
                    var p = Cursor.Position;
                    _lastPoint = new NativeMethods.POINT(){x=p.X, y=p.Y};
                    timer.Start();
                }
                // cancel
                return new IntPtr(1);
            }
        }
    }

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

internal class SubForm : Form
{
    internal SubForm(string text)
    {
        var txt = new TextBox(){
            Text = text,
            Multiline = true,
            ScrollBars = ScrollBars.Both,
            Dock = DockStyle.Fill
        };
        txt.KeyDown += (sender,e)=>{
            if (e.Control && e.KeyCode == Keys.A) { txt.SelectAll(); }
        };
        Controls.Add(txt);
    }
}

コンパイルバッチ

compile.bat
csc ^
 /r:C:\Windows\Microsoft.NET\assembly\GAC_MSIL\UIAutomationClient\v4.0_4.0.0.0__31bf3856ad364e35\UIAutomationClient.dll ^
 /r:C:\Windows\Microsoft.NET\assembly\GAC_MSIL\UIAutomationTypes\v4.0_4.0.0.0__31bf3856ad364e35\UIAutomationTypes.dll ^
 /r:C:\Windows\Microsoft.NET\assembly\GAC_MSIL\WindowsBase\v4.0_4.0.0.0__31bf3856ad364e35\WindowsBase.dll ^
 %*
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

cscの作法

概要

cscの作法、調べてみた。

環境

.NET Framework 2,0

写真

image

サンプルコード

using System;
using System.Windows.Forms;
using System.Drawing;

class form1: Form {
    form1() {
        Text = "hello";
        ClientSize = new Size(200, 200);
        Label la1 = new Label();
        la1.Location = new Point(50, 50);
        la1.Text = "Hello World!";
        Controls.AddRange(new Control[] {
            la1
        });
    }
    [STAThread]
    public static void Main() {
        Application.Run(new form1());
    }
}

以上。

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

【Unity Editor拡張 with Odin】 Addressable AssetReferenceのプレビューを表示する

環境

Unity 2019.2.17f1
Addressable 1.5
Odin Inspector 2.1.8 (有料プラグイン)
odin.png

AssetReferenceって何を参照しているのか分かりにくい。。。

名前を設定している場合はまだ分かりやすいですが、フォルダを指定している場合フルパスが表示されるのでかなり分かり辛いです。それを有料プラグインのOdinを使って解決してみようと思います。

まずは Before / After

Before...
Addressable.gif
After !!!!
AddressableWithOdin.gif
だいぶ分かりやすくなりましたね。
Prefab名をクリックするとProject内のPrefabをハイライトしてくれます。

Code

OdinWithAddressableAssetReference.cs
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.AddressableAssets;
using Sirenix.OdinInspector;

#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.AddressableAssets;
using UnityEditor.AddressableAssets.Settings;
#endif

[CreateAssetMenu(menuName = "Sample/OdinWithAddressableAssetReference")]
public sealed class OdinWithAddressableAssetReference : ScriptableObject
{
    [SerializeField, HideInInspector] AssetReferenceGameObject references;

    public AssetReferenceGameObject References => references;

    #region Inspector
#if UNITY_EDITOR
#pragma warning disable 0649
    [SerializeField, ValueDropdown("ReferenceDropDownEditorOnly"), OnValueChanged("ReferenceValueChangedEditorOnly"), LabelText("References")] string referenceGuidEditorOnly;
    [ShowInInspector, ReadOnly, InlineEditor(InlineEditorModes.LargePreview), ShowIf("ReferencePreviewShowIfEditorOnly")] GameObject PreviewOnEditor;
    [SerializeField, ValueDropdown("AssetGroupDropDownEditorOnly", IsUniqueList = true), LabelText("Addressable Group Filter")] string[] targetAssetGroupGuidsEditorOnly;
#pragma warning restore 0649

    IEnumerable ReferenceDropDownEditorOnly()
    {
        return GetAllAssetEntrys(targetAssetGroupGuidsEditorOnly)
                .Select(x => new ValueDropdownItem(x.AssetPath, x.guid));
    }

    IEnumerable AssetGroupDropDownEditorOnly()
    {
        return GetAllAssetGroups()
                .Select(x => new ValueDropdownItem(x.Name, x.Guid));
    }

    void ReferenceValueChangedEditorOnly(string guid)
    {
        var entrys = GetAllAssetEntrys(targetAssetGroupGuidsEditorOnly);
        var item = entrys.FirstOrDefault(x => x.guid == guid);

        if (item != default)
        {
            references = new AssetReferenceGameObject(item.guid);
            UpdatePreviewAssetEditorOnly();
        }
    }

    bool ReferencePreviewShowIfEditorOnly()
    {
        if(PreviewOnEditor == default)
        {
            UpdatePreviewAssetEditorOnly();
        }

        return PreviewOnEditor != default;
    }

    void UpdatePreviewAssetEditorOnly()
    {
        PreviewOnEditor = default;

        if(references.RuntimeKeyIsValid())
        {
            PreviewOnEditor = LoadAsset<GameObject>(references);
        }
    }

    /// <summary>
    /// アセットの取得
    /// </summary>
    /// <returns>The asset.</returns>
    /// <param name="reference">Reference.</param>
    /// <typeparam name="T">The 1st type parameter.</typeparam>
    public static T LoadAsset<T>(AssetReference reference) where T : Object
    {
        return reference.RuntimeKeyIsValid()
                      ? AssetDatabase.LoadAssetAtPath<T>(AssetDatabase.GUIDToAssetPath(reference.AssetGUID))
                      : default;
    }

    /// <summary>
    /// グループ一覧を取得
    /// </summary>
    /// <returns>The asset groups.</returns>
    public static List<AddressableAssetGroup> GetAllAssetGroups()
    {
        var setting = AddressableAssetSettingsDefaultObject.GetSettings(false);
        var gropus = new List<AddressableAssetGroup>();

        if (setting != default)
        {
            gropus = setting.groups;
        }

        return gropus;
    }

    /// <summary>
    /// 全エントリーを取得
    /// </summary>
    /// <returns>The asset entrys editor only.</returns>
    /// <param name="groupFilterGuids">Group filter.</param>
    public static List<AddressableAssetEntry> GetAllAssetEntrys(string[] groupFilterGuids = null)
    {
        var setting = AddressableAssetSettingsDefaultObject.GetSettings(false);
        var entrys = new List<AddressableAssetEntry>();

        if (setting != default)
        {
            foreach (var group in setting.groups)
            {
                if (groupFilterGuids != null
                    && groupFilterGuids.Length > 0
                    && !groupFilterGuids.Any(guid => guid == group.Guid))
                {
                    continue;
                }

                var _entrys = new List<AddressableAssetEntry>();
                group.GatherAllAssets(_entrys, false, true, false);
                entrys.AddRange(_entrys);
            }
        }

        return entrys;
    }
#endif
    #endregion

}

実装のポイント

AddressableAssetSettingsの取得方法が変わりました

OdinWithAddressableAssetReference.cs
var setting = AddressableAssetSettingsDefaultObject.GetSettings(false);

プレビュー用GameObjectをシリアライズされないようにする

プレビューを表示するためのGameObjectがシリアライズされないようにprivateでShowInInspectorアトリビュートを使用しています。
publicにしたい場合は、[System.NonSerialized]を追加。

更に、プレビューは参照だけで使用されることが無いので#pragma warningでCS0649警告が表示されないようにしています。

OdinWithAddressableAssetReference.cs
[ShowInInspector, ()] GameObject PreviewOnEditor;

OdinでAddressableは非対応なのでGuidを保存する

Reference.png
ドロップダウンは実はテキストです(string referenceGuidEditorOnly)。
表示するリストを生成する際(ReferenceDropDownEditorOnly())、表示用のアセットパスと保存用のGuidをOdinのValueDropdownItemに代入しています。
そして、Guidに変更があった場合(ReferenceValueChangedEditorOnly(string guid))でプレビューを更新する仕様です。

OdinWithAddressableAssetReference.cs
[SerializeField, ValueDropdown("ReferenceDropDownEditorOnly"), OnValueChanged("ReferenceValueChangedEditorOnly"), LabelText("References")] string referenceGuidEditorOnly;

IEnumerable ReferenceDropDownEditorOnly()
{
    return GetAllAssetEntrys(targetAssetGroupGuidsEditorOnly)
            .Select(x => new ValueDropdownItem(x.AssetPath, x.guid));
}

void ReferenceValueChangedEditorOnly(string guid)
{
    var entrys = GetAllAssetEntrys(targetAssetGroupGuidsEditorOnly);
    var item = entrys.FirstOrDefault(x => x.guid == guid);

    if (item != default)
    {
        references = new AssetReferenceGameObject(item.guid);
        UpdatePreviewAssetEditorOnly();
    }
}

/// <summary>
/// 全エントリーを取得
/// </summary>
/// <returns>The asset entrys editor only.</returns>
/// <param name="groupFilterGuids">Group filter.</param>
public static List<AddressableAssetEntry> GetAllAssetEntrys(string[] groupFilterGuids = null)
{
    var setting = AddressableAssetSettingsDefaultObject.GetSettings(false);
    var entrys = new List<AddressableAssetEntry>();

    if (setting != default)
    {
        foreach (var group in setting.groups)
        {
            if (groupFilterGuids != null
                && groupFilterGuids.Length > 0
                && !groupFilterGuids.Any(guid => guid == group.Guid))
            {
                continue;
            }

            var _entrys = new List<AddressableAssetEntry>();
            group.GatherAllAssets(_entrys, false, true, false);
            entrys.AddRange(_entrys);
        }
    }

    return entrys;
}

グループフィルターに対応

Group.png
フィルタ機能もつけてみました。
ほんとはLabel用のフィルタ機能もつけたかったのですが、 LabelTableがprivateの為一覧取得ができず断念しました。

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

C# - キー操作ランチャーをつくってみた

機能

準備としてテキストファイルにコマンドを登録しておく。登録した該当キーを押すとコマンドを実行し、自身のプログラムは終了する。

画面キャプチャ

image.png

やってること

  • キーボードフック
  • 多重起動防止

登録済みのキーが押されたら、タイマーを仕掛け1、フックを外すとともに、事前登録された処理を実行する。

ソースコード

ShortcutKeyboard.cs
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Windows.Forms;

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

    public const int WH_KEYBOARD_LL = 13;
    public delegate IntPtr HookProc(int code, IntPtr wParam, IntPtr lParam);

    [DllImport("user32.dll", SetLastError = true)]
    public static extern IntPtr SetWindowsHookEx(int idHook, HookProc callback, IntPtr hInstance, int threadId);

    [DllImport("user32.dll", SetLastError = true)]
    public static extern bool UnhookWindowsHookEx(IntPtr hHook);

    [DllImport("user32.dll")]
    public static extern IntPtr CallNextHookEx(IntPtr hHook, int nCode, IntPtr wParam, IntPtr lParam);

    public const int  WM_KEYDOWN = 0x0100;
}


public class MyCommand
{
    public string CommandType{get;set;}
    public string CommandContent{get;set;}

    public MyCommand(string type, string content)
    {
        CommandType = type;
        CommandContent = content;    
    }

    public bool Execute()
    {
        if (CommandType == "OPEN") {
            try {
                Process.Start(CommandContent);
            }
            catch ( System.ComponentModel.Win32Exception e ) {
                MessageBox.Show(e.ToString());
            }
            catch ( ObjectDisposedException e ) {
                MessageBox.Show(e.ToString());
            }
            catch ( FileNotFoundException e ) {
                MessageBox.Show(e.ToString());
            }
            return true;
        }
        return false;
    }
}

public class ShortcutKeyboard : Form
{
    static readonly string mutexName = "kob58im_ShortcutKeyboard";

    const int VK_0 = 0x30;
    const int VK_9 = 0x39;
    const int VK_A = 0x41;
    const int VK_Z = 0x5A;
    readonly string[] lineKeys = new string[]{"1234567890","QWERTYUIOP","ASDFGHJKL","ZXCVBNM"};
    readonly string commandFile = "commands.txt";

    IntPtr _hHook;
    NativeMethods.HookProc _handler;
    NativeMethods.KBDLLHOOKSTRUCT _lastKey;
    GCHandle _hookProcHandle;
    bool _hookExitReq;
    Dictionary<int,MyCommand> _commands;

    System.Windows.Forms.Timer timer;
    PictureBox pct;
    readonly int W = 450;
    readonly int H = 200;

    ShortcutKeyboard()
    {
        timer = new System.Windows.Forms.Timer();
        timer.Interval = 50;
        timer.Tick += (s,e)=>{Timer_Tick();};

        Load += (s,e)=>{Form_Load();};
        FormClosed += (s,e)=>{UnHook();};

        this.Text = "ShortcutKeyboard";
        // this.Opacity = 0.85;
        this.StartPosition = FormStartPosition.CenterScreen;
        this.ClientSize = new Size(W, H);
        this.TopMost = true; // 最前面表示
        this.FormBorderStyle = FormBorderStyle.FixedSingle;
        this.MaximizeBox = false; // 最大化禁止
        this.MinimizeBox = false; // 最小化禁止

        pct = new PictureBox(){
            Size = new Size(W, H),
        };
        pct.Image = new Bitmap(W, H);
        Controls.Add(pct);

        _commands = LoadCommands();
    }

    void MyDraw()
    {
        Color backColor = Color.White;
        Brush foreBrush = Brushes.Black;
        Brush nodataBrush = Brushes.LightGray;
        Pen forePen = Pens.Black;
        Pen nodataPen = new Pen(Color.LightGray, 1.0f);
        /*
        Color backColor = Color.Black;
        Brush foreBrush = Brushes.White;
        Brush nodataBrush = Brushes.Gray;
        Pen forePen = Pens.White;
        Pen nodataPen = new Pen(Color.Gray, 1.0f);
        nodataPen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash;
        */
        Font font = new Font("Arial", 20.0f, GraphicsUnit.Pixel);
        using ( Graphics g = Graphics.FromImage(pct.Image) ) {
            g.Clear(backColor);
            foreach ( char c in string.Join("", lineKeys) ) {
                Rectangle rect = RectFromChar(c);
                if ( _commands.ContainsKey((int)c) ) {
                    g.DrawRectangle(forePen, rect);
                    g.DrawString(c.ToString(), font, foreBrush, rect.Location);
                }
                else {
                    g.DrawRectangle(nodataPen, rect);
                    g.DrawString(c.ToString(), font, nodataBrush, rect.Location);
                }
            }
        }
        pct.Refresh();
    }

    void IndicesFromChar(char c, out int index, out int lineNo)
    {
        for ( lineNo=0 ; lineNo<lineKeys.Length ; lineNo++ ) {
            index = lineKeys[lineNo].IndexOf(c);
            if ( index >= 0 ) {
                return;
            }
        }
        index = -1;
        lineNo = -1;
    }

    Rectangle RectFromChar(char c)
    {
        int xi;
        int yi;
        IndicesFromChar(c, out xi, out yi);
        return new Rectangle(20 + xi*40 + yi*12, 20 + yi*40, 35, 35);
    }

    void Timer_Tick()
    {
        bool isFirst = timer.Enabled;
        timer.Stop();
        UnHook();

        if ( !isFirst ) {
            return;
        }

        _commands[_lastKey.vkCode].Execute();
        this.Close();
    }

    void Form_Load()
    {
        MyDraw();

        try {
            SetHook();
        }
        catch (System.ComponentModel.Win32Exception e) {
            MessageBox.Show(e.ToString());
            return;
        }
    }

    void SetHook()
    {
        IntPtr module = IntPtr.Zero;
        _handler = CallbackProc;
        _hookProcHandle = GCHandle.Alloc(_handler);
        _hHook = NativeMethods.SetWindowsHookEx(NativeMethods.WH_KEYBOARD_LL, _handler, module, 0);

        if ( _hHook == IntPtr.Zero ) {
            // failed
            int errorCode = Marshal.GetLastWin32Error();
            _hookProcHandle.Free();
            _handler = null;
            throw new System.ComponentModel.Win32Exception(errorCode);
        }
    }

    void UnHook()
    {
        if ( _hHook != IntPtr.Zero ) {
            NativeMethods.UnhookWindowsHookEx(_hHook);
            _hHook = IntPtr.Zero;
            _hookProcHandle.Free();
            _handler = null;
        }
    }


    IntPtr CallbackProc(int nCode, IntPtr wParam, IntPtr lParam)
    {
        if ( nCode < 0 || _hookExitReq ) {
            return NativeMethods.CallNextHookEx(_hHook, nCode, wParam, lParam);
        }
        else {
            if ( (long)wParam == NativeMethods.WM_KEYDOWN ) {
                _lastKey = (NativeMethods.KBDLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(NativeMethods.KBDLLHOOKSTRUCT));

                if ( ( _lastKey.vkCode >= VK_0 && _lastKey.vkCode <= VK_9 ) ||
                     ( _lastKey.vkCode >= VK_A && _lastKey.vkCode <= VK_Z ) ) {

                    if ( _commands.ContainsKey(_lastKey.vkCode) ) {
                        _hookExitReq = true;
                        timer.Start();
                    }
                    // cancel
                    return new IntPtr(1);
                }
            }

            return NativeMethods.CallNextHookEx(_hHook, nCode, wParam, lParam);
        }
    }

    Dictionary<int,MyCommand> LoadCommands()
    {
        string[] ss;
        try {
            ss = File.ReadAllLines(commandFile); //, Encoding.GetEncoding("SHIFT_JIS"));
        }
        catch ( IOException e ) {
            MessageBox.Show(e.ToString());
            return null;
        }

        var dict = new Dictionary<int,MyCommand>();
        Regex r = new Regex(@"^([A-Za-z0-9])\s+([A-Za-z0-9_]+)\s+(.*)$");

        foreach (string s in ss) {
            Match m = r.Match(s);
            if (m.Success) {
                char key = (m.Groups[1].Value.ToUpperInvariant())[0];
                string type = m.Groups[2].Value.ToUpperInvariant();
                string content = m.Groups[3].Value;
                dict.Add((int)key, new MyCommand(type,content));
            }
        }
        return dict;
    }

    [STAThread]
    static void Main()
    {
        var mutex = new System.Threading.Mutex(false, mutexName);

        bool hasHandle = false;
        try {
            try {
                //ミューテックスの所有権を要求する
                hasHandle = mutex.WaitOne(0, false);
            }
            catch (System.Threading.AbandonedMutexException) {
                // 別のアプリケーションがミューテックスを解放しないで終了した時
                hasHandle = true;
            }
            if (hasHandle == false) {
                MessageBox.Show("多重起動はできません。");
                return;
            }
            Application.Run(new ShortcutKeyboard());
        }
        finally {
            if (hasHandle) {
                mutex.ReleaseMutex();
            }
            mutex.Close();
        }
    }
}

設定ファイル

commands.txt
J open C:\SvnLocal\trunk\VisualShortcutKeyboard
C open c:\windows\system32\cmd.exe
p open C:\Program Files

参考サイト - キーボードフック

参考サイト - その他


  1. フック関数内で時間のかかる処理をしたくないのと、フック関数内でフックを解除するのが不安なため。 

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

いいねが欲しけりゃコードを書くな

分析適当なので,面白半分で読んでください.タイトルは興味を持ってもらえるうように強い書き方をしました.

いいね数が多い記事ってコード少ないな??

暇なときにQiitaのトレンドを見ることが多いのですが,ふとしたときに,記事に与えられたいいね数と掲載されているソースコード数は正の相関関係にないのでは?という疑問がわきました.Qiitaには実装上のテクニックを教わったりもするので,実装中心の記事が埋もれているのはかわいそうだなあという思いも多少あったりもします.

気になったら分析したくなっちゃうのが研究職の性,年末年始に寝て起きるだけの体たらくにならないためにも,この仮定をざっくりとでも検証してすっきりしたいところ.

そんなわけで,Qiitaに投稿された記事をAPIを使用して取得し,記事に掲載されているソースコード数と与えられたいいね数の相関を確認してみることにしました.ここでソースコード数とは,コードブロック(```からはじまり```で終わる区間)の数として定義します.

ざっくり結論

fig.png

いいね数とコードブロック数は反比例の関係に見える.使用したデータは2019/12/15~2019/12/31の,9,500件.

詳細な手続き

  • QiitaAPIを使用し,記事9,500件を取得
  • 各記事に対して,いいね数とコードブロックの数をカウント
  • いいね数 - コードブロック数の散布図を描画
  • 簡単に考察

投稿された記事の取得

各所を参考にさせていただき,以下のコードにより記事を取得しました.筆者はなんでもかんでもC#で書きたがります.

KijiWoDownloadSuruTokoro.cs
const int DocumentPerPage = 100;
const string token = "Your Token";

using( var client = new WebClient() ) {
  client.Headers.Add(
    HttpRequestHeader.Authorization,
    $"Bearer {token}"
  );

  for( int i = 5; i < 100; i++ ) {
    var results = client.DownloadString( $@"https://qiita.com:443/api/v2/items?page={(i+1)}&per_page={DocumentPerPage}" );
    System.Threading.Thread.Sleep( 500 );
  }
}

このソースコードにより,記事が9,500件取得できました.範囲は2019/12/31の10時から2019/12/15の11時までのようです.ちなみに変数iを0スタートにしなかったのは,i=0の時点では,スパム記事がかなり多いように見えたため.

変数resultsには,json形式で記事データが格納されていますので,適当にパースしてください.筆者はJson.Netを使いました.また,パースされたJsonデータを受け付けるクラスの定義は,https://quicktype.io/を使用し自動生成してもらいました.便利な世の中ですね.

ちなみに,jsonデータをパースしたコードは以下です.パースされたjsonデータはクラスItemに変換しています.DeserializeObjectの第二引数は,jsonデータ中に含まれる時刻のデータをうまくパースするためのテクニックです.便利ですね.

JsonNoParse.cs
var items = JsonConvert.DeserializeObject<Item[]>(
 results,
 new IsoDateTimeConverter { DateTimeFormat = @"yyyy-MM-ddTHH:mm:ss+09:00" }
);

記事ごとのいいね数とコードブロック数のカウント

記事に与えられたいいね数はjsonデータ中に含まれているので,jsonをパースした段階で取得可能です.一方コードブロックの数は,記事のデータからうまくカウントする必要があります.以下のコードで実現しました.

CodeBlockNoKazu.cs
var document = items[0].body;
var matches = Regex.Matches( document, @"```[\w|\W]*?```" );
var countOfCodeBlock = matches.Count;

やり口はシンプルで,任意の記事における,```から始まり```で終わる箇所を数えただけです.``````の間には改行文字も含めるいかなる文字が入るため,.*にせず[\w|\W]*にしました.ださいけどまあいいや.?は最短一致.

いいね数 - コードブロック数の散布図を描画,考察

後はエクセルにでも持ち込んで散布図にします.結果は下の図です.横軸はいいね数,縦軸はコードブロック数です.

fig.png

いいね数とコードブロック数は反比例の関係に見えます.

簡単な考察

まず初めに断っておくべきこととして,今回使用したデータは2019/12/15~2019/12/31の,9,500件だけです.データは偏っているかもしれません.

今回この結果からは,コードをたくさん書くことといいねが付くことには正の相関関係にないことが示唆されましたが,ある種当然の結果なのかもしれません.ここで以下の表に,今回の収集したデータのうち,いいねが多くついた上位10の記事と,そのいいね数を示します.

記事名 いいね数(2019/12/31時点)
同じチームにいて最高に心強かったエンジニアの特徴をまとめてみた 1595
なんとなくで書かないで!HTML5を書く時に気にしてみたいタグごとのお約束 1400
[和訳] Dropboxアカウントのせいで胃潰瘍になった 1386
君はまだ平成のアーキテクチャを使ってるのか?僕はFirebaseと令和の時代に行くぞ。 1285
【数学】NHKから国民を守る党からNHKを守る党からNHKから国民を守る党を守る党 1084
エンジニア採用面接での「質問項目」と「意図」を公開する 976
ラーメンで理解するasync/await 966
Unity臭さを消す方法10連発 871
研究室を IoT 化したら守衛さんを監禁してしまった話 708
数値計算の研究をしている学生が"数値計算に潜むとんでもないリスク"について話してみる 699

これら記事はどちらかというと技術解説のための記事や,読み物として興味深い記事が多く,テクノロジーに興味がある多くの読者から評価されたのだと考えられます.

一方でコードが記述された記事は読者を限定することが多く,その特定の技術に興味がない読者からは見向きもされないのでは?と思われます(筆者の主観).またそのような記事は掲載されているコードだけそっとコピペされ,いいねされずに閉じられているのかもしれません.かわいそう.

ちなみにタイトル,いいねが欲しけりゃコードを書くなは,まるでコードを書くことといいねが付くことが因果関係にあるような書き方ですが,当然その因果関係などわかるはずはなく,実際に確認できたのは,記事についたいいねの数と掲載されたコードブロックの数は反比例の関係にある,だけです.

結論

  • 記事についたいいねの数と掲載されたコードブロックの数は反比例の関係にあることが示唆された
  • 多くいいねがつく記事は多くの読者を対象にした,解説記事や読みもの?
  • 多くのコードブロックを持つ記事は,特定の読者を対象にする?

雑魚い感想ですが,技術的に参考にできる記事はちゃんといいねしようと思いました.

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

.NET Core 3.1 GUIコントロール

■初めに

.NET Core 3.1Visual Studioで基本的なコントロールを使ってみます。

■環境

  • Windows 10
  • Visual Studio 2019
  • .NET Core 3.1

■画面作成

まず、こちら を参考に「準備」と「プロジェクトの作成」をしてください。

そして以下のようにコントロールを配置し、コードを書きます。

001.png

◇コントロール

Button(ボタン)

普通のボタンです。

サンプルでは押すとメッセージボックスを表示します。
002.png

RepeatButton(リピートボタン)

長押しボタンです。押している間、クリックイベントが連続して発生します。
ツールボックスには表示されていないのでタグ手打ちで追加します。

サンプルでは押している間、タイトルバーに★を追加し続けます。
003.png

ToggleButton(トグルボタン)

ON/OFFを表せるボタンです。通常の状態と押されたままの状態をとることができます。
機能的にチェックボックスでも代用できます。
ツールボックスには表示されていないのでタグ手打ちで追加します。

004.png

CkeckBox(チェックボックス)

トグルボタンと同様、チェックが付いた状態とチェックなしの状態をとることができます。
005.png

ComboBox(コンボボックス)

複数の選択肢の中から項目を選択できます。
ドロップダウンリストは通常閉じているので省スペースです。
006.png

ListBox(リストボックス)

スペースに余裕がある場合はコンボボックスよりこちらの方がリストを開く手間が省けます。
007.png

Label(ラベル)

表示用のテキストコントロールです。
項目の名前表示などに使います。
008.png

TextBox(テキストボックス)

テキスト入力用のコントロールです。
009.png

RadioButton(ラジオボタン)

複数の選択肢の中から1つを選ぶときに使います。
010.png

◇レイアウト用コントロール

Grid(グリッド)

格子状に領域を区切って配置できます。

サンプルでは列の分割は使っておらず、RowDefinitionで3行に分割しています。

StackPanel(スタックパネル)

コントロールを縦並びまたは横並びに配置できます。

DockPanel(ドックパネル)

コントロールを上、下、左、右、その他に配置できます。

サンプルではテキストボックスを画面サイズに合わせて表示するために使用しています。
011.png
012.png

WrapPanel(ラップパネル)

コントロールを左から右に配置していき、右に入らなかったら折り返して次の行に配置します。

上の黄色背景が横配置のStackPanel、下の青背景がWrapPanelです。
013.png
幅が狭くなった時、StackPanelは見切れてしまいますが、WrapPanelは折り返して表示されます。
014.png

◇ウィンドウ

Window(フォーム、画面)です。

サンプルではウィンドウのサイズ変更がしやすいようにグリップを表示するよう、
ResizeModeプロパティにCanResizeWithGripを設定しています。
015.png

細いウィンドウ縁を掴まなくても、右下の三角でサイズ変更できるようになります。
016.png

◇スタイル

複数の同種のコントロールに同じプロパティ設定をする場合、スタイルとして定義して適用すると楽です。
利用するときは、定義したスタイルの名前(x:Key)をコントロールのStyleプロパティに指定してやります。

◇コード

MainWindow.xaml
<Window x:Class="WpfAppControls.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfAppControls"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800" ResizeMode="CanResizeWithGrip">
    <Window.Resources>
        <!-- 入力エリアコンテナのスタイル -->
        <Style x:Key="InputContainer" TargetType="DockPanel">
            <Setter Property="LastChildFill" Value="True"/>
        </Style>
        <!-- 入力エリア見出しのスタイル -->
        <Style x:Key="InputTitleStyle" TargetType="Label">
            <Setter Property="Margin" Value="5,5,5,0"/>
            <Setter Property="Width" Value="60"/>
            <Setter Property="DockPanel.Dock" Value="Left"/>
        </Style>
        <!-- 入力エリア入力部のスタイル -->
        <Style x:Key="InputTextStyle" TargetType="TextBox">
            <Setter Property="Margin" Value="0,5,5,0"/>
            <Setter Property="DockPanel.Dock" Value="Right"/>
        </Style>
        <!-- ラジオボタンの既定スタイル -->
        <Style TargetType="RadioButton">
            <Setter Property="Margin" Value="10"/>
            <Setter Property="Foreground" Value="Blue"/>
        </Style>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <StackPanel Background="Honeydew">
            <Button Content="Button" Width="100" Click="Button_Click"/>
            <RepeatButton Content="リピートボタン" Width="100" Click="RepeatButton_Click"/>
            <ToggleButton Content="トグルボタン1" Width="100"/>
            <ToggleButton Content="トグルボタン2" Width="100"/>
            <ToggleButton Content="トグルボタン3" Width="100"/>

            <CheckBox Content="チェックボックス" IsChecked="True" Margin="5"/>
            <ComboBox SelectedIndex="0" Width="150" HorizontalAlignment="Left" Margin="5">
                <ComboBoxItem Content="イチゴ"/>
                <ComboBoxItem Content="キウイ"/>
                <ComboBoxItem Content="メロン"/>
            </ComboBox>
            <ListBox SelectedIndex="0" Width="200" Height="55" HorizontalAlignment="Left" Margin="5">
                <ListBoxItem Content="あいうえお"/>
                <ListBoxItem Content="かきくけこ"/>
                <ListBoxItem Content="さしすせそ"/>
                <ListBoxItem Content="たちつてと"/>
            </ListBox>

            <DockPanel Style="{StaticResource InputContainer}">
                <Label Content="名前:" Style="{StaticResource InputTitleStyle}"/>
                <TextBox Text="" Style="{StaticResource InputTextStyle}"/>
            </DockPanel>
            <DockPanel Style="{StaticResource InputContainer}">
                <Label Content="住所:"  Style="{StaticResource InputTitleStyle}"/>
                <TextBox Text="" Style="{StaticResource InputTextStyle}"/>
            </DockPanel>
            <DockPanel Style="{StaticResource InputContainer}">
                <Label Content="メモ欄:"  Style="{StaticResource InputTitleStyle}"/>
                <TextBox Text="" Style="{StaticResource InputTextStyle}"/>
            </DockPanel>
        </StackPanel>

        <StackPanel Orientation="Horizontal" Grid.Row="1" Background="LightYellow">
            <RadioButton GroupName="g1" Content="ラジオボタンA" IsChecked="True"/>
            <RadioButton GroupName="g1" Content="ラジオボタンB"/>
            <RadioButton GroupName="g1" Content="ラジオボタンC"/>
            <RadioButton GroupName="g1" Content="ラジオボタンD"/>
            <RadioButton GroupName="g1" Content="ラジオボタンE"/>
        </StackPanel>

        <WrapPanel Grid.Row="2" Background="AliceBlue">
            <RadioButton GroupName="g2" Content="ラジオボタン1"/>
            <RadioButton GroupName="g2" Content="ラジオボタン2" IsChecked="True"/>
            <RadioButton GroupName="g2" Content="ラジオボタン3"/>
            <RadioButton GroupName="g2" Content="ラジオボタン4"/>
            <RadioButton GroupName="g2" Content="ラジオボタン5"/>
        </WrapPanel>
    </Grid>
</Window>
MainWindow.xaml.cs
using System.Windows;

#nullable enable

namespace WpfAppControls
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        /// <summary>
        /// ボタンクリック時の処理
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            MessageBox.Show("ボタンが押されました");
        }

        /// <summary>
        /// リピートボタンクリック時の処理
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void RepeatButton_Click(object sender, RoutedEventArgs e)
        {
            // タイトルバーに★を追加
            Title += "★";
        }
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

c# (dotnet core 3.1) であけましておめでとうございます!

あけましておめでとうございます。
毎年、適当な言語を選んで、あけましておめでとうメールを送っています。
今年は c# にしてみました。

Environment

  • 開発環境: Windows 10 pro
  • 開発ツール: Visual Studio 2019 + dotent core sdk 3.1
  • 実行環境: Ubuntu 18.04.1 LTS x64

Code

SmtpClientクラスはもう廃止なんですね。

https://docs.microsoft.com/ja-jp/dotnet/api/system.net.mail.smtpclient?view=netframework-4.8
重要
新しい開発には SmtpClient クラスを使用しないことをお勧めします。 SmtpClient は多くの最新プロトコルをサポートしていないためです。 代わりにMailkitまたはその他のライブラリを使用してください。 詳細については、GitHub でSmtpclient を使用しないことをお勧めします。

MSの公式ドキュメントにはMailkitを使えと。オープンな社風になってしまったMSに私は戸惑いを隠しきれません。
というので nuget で Mailkit をインストールしてgithubのサンプルコードを使いました。

using System;
using MailKit.Net.Smtp;
using MailKit;
using MimeKit;
using System.Collections.Generic;

namespace mail
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var msg = new CustomMessage()
            {
                Subject = "I wish you a Happy new year 2020",
                From = "dharry@example.co.jp",
                SmtpServer = "mail.example.co.jp",
                AuthUser = "dharry",
                AuthPass = "mypass",
            };

            msg.To = new List<KeyValuePair<string, string>>() {
                new KeyValuePair<string, string>("山田","yamada@example.com"),
                new KeyValuePair<string, string>("斎藤","saito@example.com")
                new KeyValuePair<string, string>("佐藤","sato@example.com")
            };
            msg.Body = body;
            msg.Send();
        }

        private const string body = @"2020年01月01日になりました。
あけましておめでとうございます。
今年もよろしくお願いします。
";
    }


    public class CustomMessage
    {
        public List<KeyValuePair<string, string>> To { get; set; }
        public string Subject { get; set; }
        public string From { get; set; }
        public string Body { get; set; }
        public string SmtpServer { get; set; }
        public int SmtpPort { get; set; } = 587;
        public string AuthUser { get; set; }
        public string AuthPass { get; set; }

        public void Send()
        {
            foreach (var p in To)
            {
                var message = new MimeMessage();
                message.From.Add(new MailboxAddress(From));
                message.To.Add(new MailboxAddress(p.Key, p.Value));
                message.Subject = Subject;
                message.Body = new TextPart("plain")
                {
                    Text = $"{p.Key}さん\n\n{Body}"
                };

                using (var client = new SmtpClient())
                {
                    // For demo-purposes, accept all SSL certificates (in case the server supports STARTTLS)
                    client.ServerCertificateValidationCallback = (s, c, h, e) => true;
                    client.Connect(SmtpServer, SmtpPort, false);
                    // Note: only needed if the SMTP server requires authentication
                    client.Authenticate(AuthUser, AuthPass);
                    client.Send(message);
                    client.Disconnect(true);
                    Console.WriteLine($"sendmessage.. {p.Value}");
                }
            }
        }
    }
}

Execute

ビルド->発行して、Linuxに持っていき、実行。
毎回思うのだが、dotnetコマンドの引数にdllというのが違和感ありすぎて困ります。

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