20200126のC#に関する記事は5件です。

[C#]WPFでヘッダー右クリック時のメニューを変更する

右クリック時のメニュー(コンテキストメニュー)

image.png

こういうメニューのことをコンテキストメニューと言います。

今回は自作アプリのヘッダー部を右クリックしたときのメニューを変更する方法を記載します。

上記画面のメニューに追加するパターンと完全に自作のメニューを表示するパターンの2つを作ります。

  • パターン1
    image.png

  • パターン2
    image.png

こういう感じになります。

既存のコンテキストメニューに要素を追記する方法

Win32APIを使います。
まずはWin32API使えるようにし、使用する関数を宣言します。

        [DllImport("user32.dll")]
        private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);
        [DllImport("user32.dll")]
        private static extern bool InsertMenu(IntPtr hMenu, Int32 wPosition, Int32 wFlags, Int32 wIDNewItem,
                string lpNewItem);

次に使用する定数を宣言します。

        private readonly Int32 MF_BYPOSITION = 0x400;
        private readonly Int32 MF_SEPARATOR = 0x800;
        private const Int32 ITEMONEID = 1000;
        private const Int32 ITEMTWOID = 1001;

        private readonly Int32 WM_SYSCOMMAND = 0x112;

ロード時に処理を書きます。

        public MainWindow()
        {
            InitializeComponent();
            Loaded += MainWindow_Loaded;
        }

        private void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            IntPtr windowhandle = new WindowInteropHelper(this).Handle;
            HwndSource hwndSource = HwndSource.FromHwnd(windowhandle);
            IntPtr systemMenuHandle = GetSystemMenu(windowhandle, false);
            InsertMenu(systemMenuHandle,5, MF_BYPOSITION| MF_SEPARATOR, 0, string.Empty);
            InsertMenu(systemMenuHandle,6, MF_BYPOSITION, ITEMONEID, "Item 1");
            InsertMenu(systemMenuHandle,7, MF_BYPOSITION, ITEMTWOID, "Item 2");

            hwndSource.AddHook(new HwndSourceHook(WndProc));
        }

        private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam, ref bool handled)
        {
            if(msg == WM_SYSCOMMAND)
            {
                switch(wparam.ToInt32())
                {
                    case ITEMONEID:
                    {
                        MessageBox.Show("Item 1 was clicked");
                        handled = true;
                        break;
                    }
                    case ITEMTWOID:
                    {
                        MessageBox.Show("Item 2 was clicked");
                        handled = true;
                        break;
                    }
                }
            }

            return IntPtr.Zero;
        }

自作のコンテキストメニューを表示する方法

xaml側にリソースを定義します。
とりあえず、Item1の時だけ実装。

    <Window.Resources>
        <ContextMenu x:Key="contextMenu" >
            <MenuItem Header="Item 1" Click="MenuItem_OnClick"></MenuItem>
            <MenuItem Header="Item 2"></MenuItem>
            <MenuItem Header="Item 3"></MenuItem>
        </ContextMenu>
    </Window.Resources>

コードビハインド側

        public MainWindow()
        {
            InitializeComponent();
            Loaded += MainWindow_Loaded;
        }
        private void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            IntPtr windowhandle = new WindowInteropHelper(this).Handle;
            HwndSource hwndSource = HwndSource.FromHwnd(windowhandle);

            hwndSource.AddHook(new HwndSourceHook(WndProc));
        }
        private void MenuItem_OnClick(object sender, RoutedEventArgs e)
        {
            var item = sender as MenuItem;
            MessageBox.Show(item.Header.ToString());
        }
        private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam, ref bool handled)
        {
            if(msg == 0xa4)
            {
                ShowContextMenu();
                handled = true;
            }
            return IntPtr.Zero;
        }

        private void ShowContextMenu()
        {
            var contextMenu = Resources["contextMenu"] as ContextMenu;
            contextMenu.IsOpen = true;
        }

ソースコード

https://github.com/KoMMet/WpfContext

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

【Unity】国産揺れものアセット『Magica Cloth』を試してみた Bone Cloth編

Magica Clothとは

●特徴(公式サイトより引用)
・Unity Job system + Burstコンパイラによる高速なクロスシミュレーション
・WebGLを除くすべてのプラットフォームで動作可能
・ボーンで駆動するBone Clothとメッシュで駆動するMesh Clothを装備
・Mesh Clothはスキニングメッシュ上でも動作可能
・簡単で直感的なインターフェースによりすぐにセットアップ可能
・スローなどの時間操作が可能
・フルソースコード付き

詳細は公式サイトを見て下さい。国産アセットなので日本語です。

使い方

とりあえず触ってみたい方には、以下の記事がオススメです
【unity】スカートはこれで決まり!「Magica Cloth」導入編(MeshCloth)【アセット】

詳しい使い方は、マニュアルが日本語なのでそちらを読みましょう。

パフォーマンス比較

私的には「Unity Job system + Burstコンパイラによる高速なクロスシミュレーション」ってところが気になるので、Bone Clothと有名な揺れ物アセットの『DynamicBone』とでFPSに差が出るか検証してみました。
自作のモデルに同じような見た目になるようにセットアップ、VSyncをオフにしてBuildし、6万フレームの平均を計測しました。
MagicaClothTest.gif

結果

FPS
揺れ物なし 604
DynamicBone 590
MagicaColoth 570

使い方によるのかもしれませんが、DynamicBoneの方が速かったです。

まとめ

個人的にはDynamicBoneよりちょっと使いやすいと思ったんですが、DynamicBoneの速いという結果になってしまいましたね。

Unity標準のClothコンポーネントとMesh Clothの比較もいずれやってみたいと思います。

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

【Unity(C#)】Extenject(Zenject)使って入力機能を切り離したものを管理してみた

はじめに

Extenject(Zenject)、設計、インターフェース等々、理解が浅い部分が多いので
誤りがあれば遠慮なくご指摘ください。

Extenject(Zenject)って?

依存性の注入のためのフレームワークです。

【参考リンク】:Zenject入門その1 疎結合とDI Container

疎結合な設計をする際に発生する問題に対して、
いろいろと手助けをしてくれる最強ライブラリです。

ExtenjectはZenjectのメンテナンス用として作られたそうですが、
なんやかんやあって今はExtenjectらしいです。

Extenject is the continuation of the Zenject Project!

【引用元】:Extenject Dependency Injection IOC

密結合

疎結合の反対は密結合ですが、なぜダメなのでしょう。
私が陥った状況に沿って説明していきます。


下記のGIFのようなゲームを趣味で作ってました。

CubeAdventure3.gif

Virtual JoyStickと呼ばれる、
スマホゲームでよく見かける入力インターフェースを実装してあります。

ただ、このGIFはスマホ用で、最終的にはWebGLでも遊べるようにしたかったです。
WebGLで遊ぶならキー入力の方が操作しやすいので
実装を変更するつもりでした。

もちろん、プラットフォーム判定とコピペを駆使すれば、
後から付け加えることも不可能ではありませんが、
1つのUpdate内に大量の分岐処理を書き込んだり、
他の入力系との参照関係が生まれることは予期せぬ挙動を生み出す可能性があり、
あまり好ましくありません。

オープンクローズド原則1にも反するので、
もし今後、ステージのギミックで"入力が反転する"などを
実装したくなった際にスパゲッティコードになります。

疎結合

先述の問題を解消するためにExtenjectを使います。
そのために入力機能を疎結合にするのですが、
めちゃくちゃ簡単に言うと、
入力機能をごっそり差し替えても問題なく動くような仕組みにする
ってことです。

図にするとこんな感じです。
本当はクラス図書くときは矢印の種類やら
いろいろとルールがあるんですが、
この図の矢印は単純に知ってるか知らないかの方向を
表しているものとします。
Interface.PNG

要するに中央のIInputProviderというインターフェースを使って、
MoveCubeというクラスが、一番下のInput(入力機能)を知らない状態を作り出せば、
入力機能を差し替えても、MoveCubeに変更を与える必要はない
ということです。

コード

まずはIInputProviderを作ります。

 public interface IInputProvider
{
    bool InputLeft(bool isSpaceDirection);
    bool InputRight(bool isSpaceDirection);
    bool InputUp(bool isSpaceDirection);
    bool InputDown(bool isSpaceDirection);
}

次にIInputProviderを実装したKeyInputProviderを作成します。

ステージのギミックで"入力が反転する"という実装をあらかじめ仕込んでます。

using UnityEngine;

public class KeyInputProvider : IInputProvider
{
    public bool InputUp(bool isSpaceDirection)
    {
        if (isSpaceDirection)
        {
            return Input.GetKey(KeyCode.UpArrow);
        }
        else
        {
            return Input.GetKey(KeyCode.DownArrow);
        }
    }

    public bool InputDown(bool isSpaceDirection)
    {
        if (isSpaceDirection)
        {
            return Input.GetKey(KeyCode.DownArrow);
        }
        else
        {
            return Input.GetKey(KeyCode.UpArrow);
        }
    }

    public bool InputLeft(bool isSpaceDirection)
    {
        if (isSpaceDirection)
        {
            return Input.GetKey(KeyCode.LeftArrow);
        }
        else
        {
            return Input.GetKey(KeyCode.RightArrow);
        }
    }

    public bool InputRight(bool isSpaceDirection)
    {
        if (isSpaceDirection)
        {
            return Input.GetKey(KeyCode.RightArrow);
        }
        else
        {
            return Input.GetKey(KeyCode.LeftArrow);
        }
    }
}

同様に差し替え機能の一つとしてJoyStickInputProviderを用意します。

やっていることはKeyInputProviderと同じで、
ジョイスティックの入力具合に応じてbool値を返すだけです。

public class JoyStickInputProvider : IInputProvider
{
    float joyStickSensitivity = 0.7f;

    public bool InputUp(bool isSpaceDirection)
    {
        if (isSpaceDirection)
        {
            return SimpleTouchController.GetTouchPosition.y > joyStickSensitivity;
        }
        else
        {
            return SimpleTouchController.GetTouchPosition.y < -joyStickSensitivity;
        }
    }

    public bool InputDown(bool isSpaceDirection)
    {
        if (isSpaceDirection)
        {
            return SimpleTouchController.GetTouchPosition.y < -joyStickSensitivity;
        }
        else
        {
            return SimpleTouchController.GetTouchPosition.y > joyStickSensitivity;
        }

    }

    public bool InputLeft(bool isSpaceDirection)
    {
        if (isSpaceDirection)
        {
            return SimpleTouchController.GetTouchPosition.x < -joyStickSensitivity;
        }
        else
        {
            return SimpleTouchController.GetTouchPosition.x > joyStickSensitivity;
        }
    }

    public bool InputRight(bool isSpaceDirection)
    {
        if (isSpaceDirection)
        {
            return SimpleTouchController.GetTouchPosition.x > joyStickSensitivity;
        }
        else
        {
            return SimpleTouchController.GetTouchPosition.x < -joyStickSensitivity;
        }
    }
}

Extenject

そして、ここからExtenjectの力を借ります。

導入までは下記参考リンクで完璧です。
導入以降もめちゃくちゃわかりやすいので100回ぐらい見た方がいいです。

【参考リンク】:Zenject入門その1 疎結合とDI Container

※AssetStoreではExtenjectと検索すれば出てきます


MoveCubeIInputProviderを使うわけですが、
このままだとnullになってしまいます。

IInputProviderってどこ?どれ?って状態です。

MoveCube
    //MoveCubeはどこのどのIInputProviderを使えばいいかわからない
    IInputProvider inputProvider;

    [SerializeField]
    bool tmpFlag = true;

    void Update()
    {
        //Right 
        if (inputProvider.InputRight(tmpFlag))
        {
            //適当な処理
        }
        //Left 
        if (inputProvider.InputLeft(tmpFlag))
        {
           //適当な処理
        }
        //Up 
        if (inputProvider.InputUp(tmpFlag))
        {
            //適当な処理
        }
        //Down 
        if (inputProvider.InputDown(tmpFlag))
        {
            //適当な処理
        }
    }

そこで、[Inject]を使います。

インターフェースに[Inject]というアトリビュートを与えることで、
MoveCubeが「IInputProviderを使いたい!」となった際に、
Extenjectが「お前が使いたいのはこれかい?」
といった感じに手助けしてくれます。

 [Inject]
 IInputProvider inputProvider;

Extenjectに"これ"がなんなのかあらかじめ設定しておく

先ほど

Extenjectが「お前が使いたいのはこれかい?」
といった感じに手助けしてくれます。

と書きましたが、Extenjectに助けてもらうには
まずはこちら側で"これ"が一体何のことを示しているのか
あらかじめ教えておくことが必要です。

Installer

Extenjectに助けてもらうためにInstallerを作ります。

CreateZenjectInstaller で作れます。

ZenjectInstaller.png

中身はこんな感じにします。
それぞれ、IInputProviderが呼ばれた際に、
To以下のジェネリクスで指定したProvider"これ"」になるよ~
って設定してます。

using Zenject;

public class KeyInputInstaller : Installer<KeyInputInstaller>
{
    public override void InstallBindings()
    {
        Container
            .Bind<IInputProvider>()
            .To<KeyInputProvider>()
            .AsCached();
    }
}
using Zenject;

public class JoyStickInputInstaller : Installer<JoyStickInputInstaller>
{
    public override void InstallBindings()
    {
        Container
            .Bind<IInputProvider>()
            .To<JoyStickInputProvider>()
            .AsCached();
    }
}

さらに、今回はプラットフォーム判定を利用して
より簡単に差し変わるようにしてみました。

using Zenject;

public class InputInstaller : MonoInstaller
{
    public override void InstallBindings()
    {
        //スマホ用
#if UNITY_ANDROID || UNITY_IOS 
        JoyStickInputInstaller.Install(Container);
#endif

        //WebGL
#if UNITY_WEBGL 
        KeyInputInstaller.Install(Container);
#endif
    }
}

Installerをもう一つ用意して、
先ほど作成した2つのInstallerをプラットフォーム別判定の中で呼び出しています。

後ほどInspectorで登録するのでMonoInstallerを継承させておく必要があります。

Context

次に、Installerの影響範囲決めとInstallerの登録を行います。

下記リンクに全部載ってますが、一応メモします。

【参考リンク】:Zenject入門その1 疎結合とDI Container


Contextというものを作成します。
今回使うのはSceneContextと呼ばれるものです。

SceneContext.png

影響範囲や設定方法については下記リンクが参考になります。

【参考リンク】:【Unity】【Zenject】DIの影響範囲を指定するContextの使い方まとめ

あとはInstallerの登録を行えば完了です。
SceneContextSettings.png

おわりに

インターフェースってなんのためにあるんだ?
使ったことないけど困ったことないぞ?
って感じだったので触ってみました。

まだまだ小規模なテストも書いてない個人製作の範囲なので、
その強力さにいまいちピンときてませんが、
できるだけ疎結合を意識した設計ができたらいいなと思ってます。


追記

記事を書くにあたって、Unityゲーム開発者ギルドでいろいろと
疑問点にお答えいただいた方、ありがとうございました。

まだ入ってない人は早急に入った方がいいと思います。
損することは何もないです。(強いて言えばみんな強すぎてちと凹む。。。)

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

UnityC#によるスクリプト反転について

キャラクター移動時、スクリプトで反転させるコードを書いているのですが。いざ動かしてみるとキャラクターが棒状になってしまいます。
怪しい箇所を弄ってみると元のスケール数値に異常があるのは理解できたのですが対策がわかりません。元のスケールをx1 y1 z1 にすると正常に機能します。

どなたかご教授いただけないでしょうか?

(キャラクターのスケールはx5 y5 z1となっています)

以下cs

public class Player : MonoBehaviour
{

public float speed = 4f; 
private Rigidbody2D rigidbody2D;
private Animator anim;

void Start()
{

    anim = GetComponent<Animator>();
    rigidbody2D = GetComponent<Rigidbody2D>();
}

void FixedUpdate()
{

    float x = Input.GetAxisRaw("Horizontal");

    if (x != 0)
    {
        //入力方向へ移動
        rigidbody2D.velocity = new Vector2(x * speed, rigidbody2D.velocity.y);
        //localScale.xを-1にすると画像が反転する
        Vector2 temp = transform.localScale;
        temp.x = x;
        transform.localScale = temp;
        //Wait→Dash
        anim.SetBool("Dash", true);

    }
    else
    {
        //横移動の速度を0にしてピタッと止まるようにする
        rigidbody2D.velocity = new Vector2(0, rigidbody2D.velocity.y);
        //Dash→Wait
        anim.SetBool("Dash", false);
    }
}

}

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

[Chrome拡張機能]ローカルのフォルダ/ファイルを開く機能を作ってみた

はじめに

自分が勤めている会社に、社内Wikiみたいなものがあるのですが、各ページに書かれている参考資料へのパスが、社内のファイルサーバやローカルのパスになっていることが、多々あります。
パスを毎回コピーしてエクスプローラに貼り付けるのが、だんだん面倒になってきました。
そこで、Chrome拡張機能で自動化することにしました。

  • 文字列を選択し、右クリックした時のコンテキストメニューから実行する
  • 選択文字列がフォルダ/ファイルのパスなら開く
  • 選択文字列の中にfile:とか<とか>とかあったら、事前に取り除く

先に言っておきますが、苦労の割にあまり自動化されません。。。

しかも、Chromeウェブストアにないため、Chromeを起動する度に毎回「無効化する」かどうか聞かれます。
作った機能からして、Chromeウェブストアに置かせてもらえる気がしません。

そのため、毎回聞かれても無効化せずに利用いただくか、投稿が部分的にでも何かの参考になれば幸いです。

やったこと

以下を丸パク、いや、参考にさせていただき、やったことを挙げていきます。
- Native Message1(外部ソフト登録)
- Native Message2(拡張機能)
- Native Message3(通信設定 拡張側)
- Native Message5(2byte文字等の対応)

(1) 拡張機能のマニフェストファイル作成

namedescriptionversionはお好きな値で。
あと、以下の例なら、48x48の好きなアイコン画像も必要です。

manifest.json
{
    "manifest_version": 2,
    "name": "OpenSelectedText",
    "description": "Open Selected Text",
    "version": "1.0",
    "background": {
        "scripts": ["background.js"],
        "persistent": false
    },
    "permissions": [
        "contextMenus",
        "nativeMessaging"
    ],
    "icons": {
        "48": "icon48.png"
    }
}

(2) 拡張機能の本体となるスクリプト作成

今回は、パスとなる文字列を選択後の、コンテキストメニューから呼ぶことにしました。
manifest.jsonで"persistent": falseにしているため、chrome.contextMenus.create()の中でonclickは指定しないで、メニューのIDから判断することにします。

スクリプトの終わり際にある、以下2点が重要です。
- chrome.runtime.sendNativeMessage()を呼んでいること
- chrome.runtime.sendNativeMessage()の第1引数を、後述するレジストリキーと合わせること
特に1点目は、参考サイトの方法
chrome.runtime.connectNative()で取得したportに対してport.postMessage()を呼ぶ)
と異なります。
その理由は、ホスト側のプロセスが勝手に終わっても、Chrome側にエラーNative host has exited.が発生しないようにするためです。

background.js
//コンテキストメニューのクリック時イベントハンドラ
function onClickHandler(info, tab)
{
    if (info.menuItemId == "OpenSelectedText")
    {
        sendText(info, tab);
    }
};
chrome.contextMenus.onClicked.addListener(onClickHandler);

//拡張機能インストール時のみ、自メニュー追加
chrome.runtime.onInstalled.addListener(function()
{
    chrome.contextMenus.create(
    {
        id          : "OpenSelectedText",
        title       : "選択文字列をパスとして開く",
        type        : "normal",
        contexts    : ["selection"]
    });
});

//選択文字列を送信
function sendText(info, tab)
{
    var SelectedText = encodeURIComponent(info.selectionText.replace(/\\/g, '/'));

    chrome.runtime.sendNativeMessage(
        "host1",
        { SelectedText },
        function(response)
        {
            var message = decodeURIComponent(response);
            console.log(message);

            if (message != "OK")
            {
                alert(message);
            }
        }
    );
}

(3) 拡張機能の読み込み

作ったマニフェストファイルとスクリプト(とアイコン画像)を、ローカルの任意フォルダに集めます。
(以降、フォルダをC:\Work\OpenSelectedTextと仮定しますが、各自読み替えてください)
そして、Chromeのメニュー「その他のツール」-「拡張機能」から、
「パッケージ化されていない拡張機能を読み込む」ボタンを押し、上記フォルダを指定します。
読み込んだ拡張機能に表示されたIDの値が次に必要なので、控えておいてください。

(4) 拡張機能と通信するホストのマニフェストファイル作成

拡張機能と通信するホスト用に、任意名称のマニフェストファイルを作ります。
(以降、ファイル名をOST_Host.jsonと仮定しますが、各自読み替えてください)

nameは後述のレジストリキーと合わせます。
descriptionはお好きな値で。
pathには、この後作るホスト(*.exe)へのパスを書きます。
allowed_originsには、下記の例からID部分を、読み込んだ拡張機能のIDに修正します。

OST_Host.json
{
    "name": "host1",
    "description": "Open Selected Text Host",
    "path": "OST_Host.exe",
    "type": "stdio",
    "allowed_origins": ["chrome-extension://bgkppcgfghmbmlfljpldaaddklfeaafg/"]
}

で、作ったマニフェストファイルもC:\Work\OpenSelectedTextに置いちゃいます。(本当はどこでもいいと思いますが)

(5) ホストを登録するためのレジストリ編集

レジストリ上、HKEY_CURRENT_USER\SOFTWARE\Google\ChromeにキーNativeMessagingHostsがなければ、作成しておきます。
さらに、NativeMessagingHosts直下に、スクリプトで呼ぶchrome.runtime.sendNativeMessage()の第1引数と同じ名称のキー(今回ならhost1)を作成します。
作成したキー(今回ならhost1)の値に、ホストのマニフェストファイルへの絶対パスを設定します。

レジストリ登録用のファイル(*.reg)風に書くと、こんな感じです。

Windows Registry Editor Version 5.00

[HKEY_CURRENT_USER\SOFTWARE\Google\Chrome\NativeMessagingHosts\host1]
@="C:\\Work\\OpenSelectedText\\OST_Host.json"

(6) ホスト作成

C#で作ります。参考サイトには「コンソールアプリケーション」とあったのですが、DOS窓をチラ見せしたくないので、筆者は以下の手順で作り始めました。
(この手順が正しいかは不明ですが)

  1. Visual Studio起動(筆者は、PCにまだ入っていたVisual C# 2008 Express使用)
  2. 新規プロジェクトの作成で、「Windowsフォームアプリケーション」を選択
  3. 作成したプロジェクトから、「Form1.cs」を削除
  4. プロジェクトのプロパティにて、スタートアップを「Program」に変更

Chromeから来るデータはJSONなので、(ただ使ってみたかっただけですが)DataContractJsonSerializerを使ってみます。

作成したプロジェクトには以下3点、参照を追加します。
- Microsoft.JScript
- System.Runtime.Serialization
- System.ServiceModel.Web(これだけは.NET Framework 4以降なら不要)

新規クラス「NativeMessage.cs」を追加して、「Program.cs」とともに、以下のように実装します。

NativeMessage.cs
using Microsoft.JScript;
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Text;

namespace OST_Host
{
    [DataContract]
    public class Message
    {
        [DataMember]
        public string SelectedText { get; set; }
    }

    class NativeMessage
    {
        public static string StringRead()
        {
            // JSONデータの受信
            string inStr = OpenStandardStreamIn();
            inStr = GlobalObject.decodeURIComponent(inStr);

            // JSONデータのデシリアライズ
            var serializer = new DataContractJsonSerializer(typeof(Message));
            using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(inStr)))
            {
                var data = (Message)serializer.ReadObject(ms);
                return data.SelectedText;
            }
        }

        public static void StringWrite(string stringData)
        {
            int limit = 1024 * 1024 - 2;
            string stringText = GlobalObject.encodeURIComponent(stringData);
            while (stringText.Length >= limit)
            {
                OpenStandardStreamOut("\"" + stringText.Substring(0, limit) + "\"");
                stringText = stringText.Substring(limit);
            }
            OpenStandardStreamOut("\"" + stringText + "\"");
        }

        private static string OpenStandardStreamIn()
        {
            Stream stdin = Console.OpenStandardInput();
            byte[] bytes = new byte[4];
            stdin.Read(bytes, 0, 4);
            int length = BitConverter.ToInt32(bytes, 0);
            string input = "";
            for (int i = 0; i < length; i++) input += (char)stdin.ReadByte();
            stdin.Close();
            return input;
        }

        private static void OpenStandardStreamOut(string stringData)
        {
            byte[] bytes = BitConverter.GetBytes(stringData.Length);
            Stream stdout = Console.OpenStandardOutput();
            for (int i = 0; i < 4; i++) stdout.WriteByte(bytes[i]);
            Console.Write(stringData);
            stdout.Close();
        }
    }
}
Program.cs
using System;
using System.Diagnostics;
using System.IO;

namespace OST_Host
{
    static class Program
    {
        [STAThread]
        static void Main(string[] args)
        {
            // 受信文字列(¥が全て/になっている)
            string inStr = NativeMessage.StringRead();
            int index;
            string[] prefixes = new string[2] {"file://", "file:"};

            if (inStr == string.Empty)
            {
                NativeMessage.StringWrite("選択文字列が空です。");
            }
            else
            {
                // 不要な文字の削除
                index = inStr.LastIndexOf("<");
                if (index >= 0)
                {
                    inStr = inStr.Substring(index + 1);
                }
                index = inStr.IndexOf(">");
                if (index >= 0)
                {
                    inStr = inStr.Substring(0, index);
                }

                // "file:"の削除("FILE:"と書く方はまれだと思うが、一応は考慮)
                for (index = 0; index < prefixes.Length; index++)
                {
                    if (inStr.StartsWith(prefixes[index], StringComparison.OrdinalIgnoreCase))
                    {
                        inStr = inStr.Substring(prefixes[index].Length);
                    }
                }

                // UNCパス先頭の"//"と、"file://"の"//"が合体していたケースの対処
                if (!inStr.StartsWith("//") && !inStr.Contains(":"))
                {
                    inStr = "//" + inStr;
                }

                // 通信~デシリアライズ前とは逆の変換
                inStr = inStr.Replace("/", "\\");

                if (Directory.Exists(inStr))
                {
                    try
                    {
                        Process.Start("explorer.exe", "/e, \"" + inStr + "\"");
                        NativeMessage.StringWrite("OK");
                    }
                    catch (Exception)
                    {
                        NativeMessage.StringWrite("フォルダを開けません。");
                    }
                }
                else if (File.Exists(inStr))
                {
                    try
                    {
                        ProcessStartInfo psi = new ProcessStartInfo(inStr);
                        psi.WorkingDirectory = Directory.GetParent(inStr).FullName;
                        Process.Start(psi);
                        NativeMessage.StringWrite("OK");
                    }
                    catch (Exception)
                    {
                        NativeMessage.StringWrite("ファイルを開けません。");
                    }
                }
                else
                {
                    NativeMessage.StringWrite("不正なパスです。");
                }
            }
        }
    }
}

ビルドして生成したファイルも、C:\Work\OpenSelectedTextに置いちゃいます。
(ホストのマニフェストファイルに書いたpathと合わせます)

終わりに

選択範囲を狭くすれば、ファイルパスに対しても親フォルダを表示できるので、我ながら便利だと思います。
あとは、以下2点だけが気になります。
- Chromeウェブストアに置かせてもらえるか
- 置かせてもらえたとしても、他PCにインストールするとき、レジストリ編集はChromeがやってくれるのか、バッチか何かを用意しないといけないか、それとも手動しかないか

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