【WPF】ContextMenuに表示される一部項目を、場合に応じて選択できなくする

意外と書いてあるサイトが少なかったのでメモしておきます。 WPFで、通常右クリック時に開かれるメニューに用いられるContextMenuコントロールの内容を、 開かれる前に編集したいことがあると思いますが、 そのときはContextMenuOpeningイベントを利用すると良いみたいです。 自分はListVIewを右クリックしたときに表示されるメニューのうち、アイテムが複数選択されているときのみ有効な項目を作りたかったので、以下のようにしました。

    public partial class Window1 : Window
    {
        ContextMenu contextMenu = new ContextMenu();

        MenuItem menuItem0 = new MenuItem();
        MenuItem menuItem1 = new MenuItem();

        public Window1 ()
        {
            InitializeComponent();
            CreateMenu();
        }

        private void CreateMenu()
        {
            menuItem0.Header = "選択項目をマージ";
            menuItem0.Click += menuItem_ClickMergeItems;
            menuItem1.Header = "選択項目を削除";
            menuItem1.Click += menuitem_ClickDelete;

            contextMenu.Items.Add(menuItem1);
            contextMenu.Items.Add(menuItem2);

            //コンテキストメニュー表示時に発生するイベントを追加
            listVIew.ContextMenuOpening += contextMenu_Click;

            //ListViewコントロール
            listVIew.ContextMenu = contextMenu;
        }

        private void contextMenu_Click(object sender, RoutedEventArgs e)
        {
            //listviewのアイテムが複数選択されていない場合、一部項目を無効にする
            menuItem0.IsEnabled =  listVIew.SelectedItems.Count > 1;
        }
    }

大事なのはContextMenuの親元のコントロールにイベントを追加することです。 最初

contextMenu.ContextMenuOpening += contextMenu_Click;

としていたのですが、こうするとContextMenuが開かれているときに右クリックした場合しか呼ばれなくなります。

stackoverflow.com

Unity 2Dでサイコロを作る

こういうの作ります。

f:id:Kanchi0914:20181206034925g:plain

3Dのサイコロを作る方法はいくつか他サイトで公開されていて、2Dのサイコロも同じようにしてすぐ作れるだろうと思っていたら、思いの外苦労したのでその過程を記録しておくことにしました。

制作過程

まずはサイコロの各面の画像を適当に用意します。 今回はいつもお世話になっているicooon-mono様の画像をお借りし、それを加工したものを用意しました。

icooon-mono.com

画像はフォルダにまとめ、Resources.LoadAll<Sprite>()で読み込むと便利です。 サイコロを振っている間はランダムに画像を変更するようにし、スクリプトのUpdate内に処理を書きます。 あとはサイコロのImageにrigidbodyとイベントトリガーを取りつけ、クリックで鉛直上方向にAddForceを加え、振っている感じを出します。 サイコロのImageの下に床となるコライダーをつけたオブジェクトを用意するのを忘れずに。 コードは以下の通りです。

using UnityEngine;
using UnityEngine.UI;

using DG.Tweening;

public class DiceManager : MonoBehaviour
{
    public GameObject dice;
    Rigidbody2D rb2D;
    private Image diceImage;
    private int preNum;
    float posY;

    Sprite[] images;

    private bool isRandom = false;

    void Start()
    {
        diceImage = dice.GetComponent<Image>();
        rb2D = dice.GetComponent<Rigidbody2D>();
        images = Resources.LoadAll<Sprite>("DiceImage");
    }

    void Update()
    {
        if (isRandom)
        {
            if (!rb2D.IsSleeping())
            {
                int num = Random.Range(0, 6);
                diceImage.sprite = images[num];
                preNum = num;
            }
            else
            {
                Debug.Log(preNum + 1);
                isRandom = false;
            }
        }
    }
    //クリックで呼ぶ
    public void OnClickDice()
    {
        if (rb2D.IsSleeping() && !isRandom)
        {
            rb2D.AddForce(new Vector2(0f, 1000f));
            isRandom = true;
        }
    }

}

このままでも一応動くのですが、デフォルトだと非常にゆっくりした感じになってテンポが悪いので、 RigidBody2DのGravity Scaleを大きくしてサイコロが早く動くようにします。 またX軸方向の動きや回転を制限するため、Freeze Position XとFreeze Rotation Zにチェックを入れます。

f:id:Kanchi0914:20181206035820p:plain

注意してほしいのが、Linear DragAngular Dragの値が1になっていることです。 これがデフォルト値のままだと、Gravity Scaleを大きくしたときにサイコロが微振動するようになり、 見た目も気持ち悪い上にサイコロの静止判定がとれなくなって困ります。 あとCollision DetectionもContinuousに変更する必要があったりで、ここらへんの設定にかなり苦労しました。

最後に、RigidBody 2dの静止判定をIsSleeping()メソッドでとる部分についてですが、 これもデフォルトの設定だとサイコロが止まってからも少しの間サイコロの画像が変わり続けるので、 Project Settings>Physics 2DからTime To Sleepの値を変更します。

f:id:Kanchi0914:20181206043403p:plain

これでようやく満足の行く2Dサイコロが作れました。思っていた3倍は苦労しました。

アプリケーションの起動時間を測るソフトを作る(Java)

PCで何かしらの作業をしている時、その作業時間を測っておきたいと思うことがあります。 1万時間の法則という言葉もあるくらいですから、例えば趣味にどれくらいの時間を使っていて、 どれくらい頑張ったのかを確認して満足したり、 はたまたゲームなどに費やした時間を可視化して、多すぎるようなら反省したり(大体のゲームではプレイ時間が記録されますが)。 個人的には作業時間を記録して、そういうことに役立てたいと思っています。

作業時間を記録するWebサービスには、代表的なものにToggleなどがあり、一時期は自分も使っていました。 しかしこれは、作業開始時と終了時に自分でタイマーを操作しなければいけないため、作業時間を記録し忘れたり、タイマーを止め忘れたりしてしまうことが多々あり困っていました。

toggl.com

要するに何がしたいか

PCで起動しているソフトの起動時間を測定しておけば、タイマーの操作忘れに悩まされることもないだろうということです。 個人的には普遍的な悩みだと思っていて、探せば良さげなフリーソフトが見つかるかと思いきやそうでもなかったので、 練習を兼ねてJavaで組んでみることにしました。

コード

github.com

やることは単純で、指定したアプリが起動しているかを一定時間ごとに確認し、起動していれば累積起動時間を分単位で更新していきます。 起動しているかどうかの確認にはWindowstasklist | Findコマンドを使いました。サンプルとして自分が時間を記録したいと思っていたUnityとClipStudio Paintが確認対象になっています。

困った部分

JavaからWindowsのコマンドを呼び出すために、Runtime.exec()メソッドを使ったのですが、実はダブルクォーテーションで囲まれた部分があると正常に動作しないみたいで、結局バッチファイルを作って処理することにしました。そのせいで起動確認したいソフトごとにバッチファイル作る必要があったりしてややこしいことになってます。

課題

一分ごとに毎回データを保存しているのが冗長な気がするので、一時間ごとに保存、終了時にShutdownHookで保存とかのほうが良いのかもしれません。今はどうせ自分しか使わないので本当に最低限の要素しかありませんが、本格的に開発するならやっぱりC#とかのほうが良さげですね。Cにはアクティブウィンドウを取得するメソッドがあるみたいで、実際の作業時間を記録するなら明らかにそっちのほうがいいですし。地味にC#では使えたタプルがjavaでは使えなかったりして面倒でした。

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

d.hatena.ne.jp

環境変数を設定したのにコマンドが通らない、と思ったら

メインで使っているデスクトップでは問題なかったのですが、サブで使っているWindows8.1ノートでは、コマンドプロンプトからAnacondaのコマンドが実行できないことに気づきました。 Anacondaはインストール時に環境変数を設定するかどうか選べるので、おそらくそのときにNoを選択したんだろうなと思い、 必要なフォルダにパスを通すことに。

C:\Users\USERNAME\Anaconda3
C:\Users\USERNAME\Anaconda3\Library\bin
C:\Users\USERNAME\Anaconda3\Scripts

このへんですね。

しかしコマンドプロンプトから試してみると、まだコマンドが通らない。 pathコマンドで確認しても確かに認識はしてるはずなんだけど…。

最近導入したRapid Environment Editorというフリーソフトは、 特にWindows8だと面倒な環境変数GUIで手軽に編集できる優れものなのですが、 これで確認してみるとやはり何かエラーが出ている模様。

f:id:Kanchi0914:20180731193203p:plain

別にフォルダが存在しないわけでもあるまいし…。 しばらく考えた結果、「あっ」となりました。

フォルダ名の最初に半角スペースが入っている。 そういえばPathに追加する際、;(セミコロン)の後に無意識に半角スペースを入れていた気が…。

Windows10では環境変数の設定が比較的やりやすくなっているのですが、 8の時点では一行を編集する形でしか設定できないので、こういうミスが起こりうるんですね(自分が無知なだけ)。 早い段階で気づけて良かったんですが、そろそろこのPCも10にアップデートしようか悩みます。 ただ10は不具合が多くてなぁ…。

UbuntuでNo space left on deviceエラーが出たときの対処メモ

現象

Linux版Unreal Engine4で、CARLAというカーシミュレータをパッケージ化する際、以下のエラーによりクックが失敗する現象に遭遇しました。

 LogDirectoryWatcher: Error: inotify_add_watch cannot watch folder /home/USERNAME/carla/Unreal/CarlaUE4/Plugins/Carla/Content/ (errno = 28, No space left on device)

環境

Ubuntu 16.04
Unreal Engine4 4.18

解決策

こちらを参考にします。 unix.stackexchange.com

早速、cat /proc/sys/fs/inotify/max_user_watchesを打つと、

8192

となっているので、これを適当に大きな値に変えます。

sudo sysctl fs.inotify.max_user_watches=524288 

その後、無事にパッケージ化に成功しました。

巡回セールスマン問題を2-opt法で解く(Python)

概要

巡回セールスマン問題(TSP:Traveling Salesman Problem) は、都市の集合と各2都市間の移動コストが与えられたとき、 全ての都市をちょうど一度ずつ巡り出発地に戻る巡回路の総移動コストを最小化する、組み合わせ最適化問題です。 以前、大学の講義で自由に課題を設定してレポートを書く際に実装したのですが、割と苦労したので公開しておきます。

2-opt法

2-opt 法は逐次改善法の一種であり、逐次改善法とは、ある巡回路を基として、 それより更にコストの小さい巡回路を探す方法です。 2-opt 法のアルゴリズムは、次のように表されます。

  1. 入力した巡回路に対し、適当な二つの辺を選択し、それらを入れ替えた結果のコストを計算する。 そのコストが入れ替える前より小さくなれば、入れ替えを採用し、巡回路を改善する。
  2. 1.を、巡回路の改善ができなくなるまで繰り返す。

逐次改善法は既存の巡回路を改善していくことで最適解に近づけていくものなので、 事前に初期解として何らかの巡回路を与えておかなければなりません。 適当に巡回路を定めても良いのですが、ここでは構築法(与えられた都市間のコストから、巡回路を構築していく方法) であるNearest Neighbor法、Convex Hull Insertion法の2つを実装しました。

Nearest Neighbor法

Nearest Neighbor法のアルゴリズムは、次のように表されます。

  1. 適当な都市を選び、出発点とする。
  2. まだ訪れていない都市のうち、現在いる都市から最も近い都市を選び、その都市との経路を巡回路に加え、その都市に移動する。
  3. 2.を、まだ訪れていない都市がなくなるまで繰り返す。なくなったら、最後に訪れた都市と出発点とをつなぐ経路を巡回路に加えて終了する。

Convex Hull Insertion法

凸包を初期部分巡回路とし、それ以外の都市を最近挿入法により加えながら巡回路を構築する方法です。アルゴリズムは以下で表されます。

  1. 都市を頂点とする凸包の境界上の都市を初期巡回路とする。
  2. 巡回路上の連続した都市を{i,j}とし、巡回路以外の各都市{\theta_{12}}に対して、追加コスト {c_{ik}+c_{kj}-c_{ij}} を計算し、この値を最小とする都市{i,j}を求める。求めた組み合わせ について、追加コスト比 {c_{ik}+c_{kj}/c_{ij}} を計算し、この値を最小とする都市{k}を巡回路 に追加する。
  3. 全ての都市を通過する巡回路ができるまで2.を繰り返す。

グラハムスキャン法

ややこしい話になりますが、CHI法で巡回路を構築する前に、さらに初期段階として 都市の点集合の凸包を求める必要があります。 凸包を求めるアルゴリズムには、分割統治法、逐次添加法などが存在しますが、 ここではグラハムスキャン法というアルゴリズムを採用します。

  1. 都市の集合のうち、y座標が最小のもの(複数ある場合は、そのうちx座標最小の もの)を基準点とし、基準点に対する角度の順に、残りの点に番号をつける。
  2. 番号順に点を見ていき、一つ前の点H、現在の点I、次点の角度Jの角度∠HIJ を 計算する。角度がマイナスになる場合、凸包にはならないため、点I を削除する。 そして、その次の点Kとの角度∠HJK を計算し、同様の操作を繰り返す。
    1. の操作を、全ての点との角度を計算するまで行う。

コード

長くなったので、GitHubに上げてあります。 「Nearest Neighbour法のみ」、「CHI 法のみ」、「Nearest Neighbour 法+2-opt 法」、「CHI 法+2-opt 法」の いずれかについて計算し、matplotlabで結果を表示できるようにしてあります。 なおTSPの問題となるデータは、TSPLIBというサイトから頂いてきます(同サイトで最適解の確認もできます)。 f:id:Kanchi0914:20180613180627p:plain

参考文献

坂上知英, 吉澤慎, 太田義勝,大山口通夫:巡回セールスマン問題の近似アルゴリズムに ついて. Research reports of the Faculty of Engineering, Mie University, 25, pp.81-96,(2000)
星野貴弘, 浜松芳夫:巡回セールスマン問題に対するヒューリスティック解法. 研究報告 高度交通システム(ITS), 2011(3), pp.1-4, 2011
関根渓:INTRODUCTION TO ALGORITHMS,http://www-ikn.ist.hokudai.ac.jp/ksekine/slides/convexhull.pdf

クリップボードのテキスト内の改行を削除するツール(Python + PyInstaller)

最近のGoogle翻訳の精度は本当に凄くて、英語の文献をしょっちゅうコピペしながら読んでいます。 ただ、Google Chrome拡張機能から翻訳する場合は大丈夫なのですが、Adobeのリーダーなどからコピーした文を貼り付けると改行コードが残り、 そのままペーストすると翻訳の邪魔になってしまいます。

というわけで、ワンアクションでクリップボード内のテキストに含まれる改行を削除するツールを作りました。

環境、使用したもの

  • Windows 10
  • Python 3.5.3
  • pyperclip 1.6.2
  • PyInstaller 3.3.1
  • ランチャー、またはショートカット変更ツール

実装

やることは非常に簡単です。

① クリップボード内のテキストを取得後、改行を削除したのちにクリップボードに返すコードを用意
② ①を.exeファイルに変換、ランチャーに登録

まず必要なパッケージであるpyperclip, PyInstallerをインストールします。両方ともpipで入れられます。

pip install pyperclip pyinstaller

①のコードはpyperclipを使って数行で書けます。

import pyperclip

a = pyperclip.paste()
b = a.split()
c = ''

for text in b:
    c = c + text + ' '

pyperclip.copy(c)

pyperclip.paste()クリップボードからテキストを取得、pyperclip.copy()クリップボードにコピーします。 なおsplit関数で引数を指定しないと空白も削除されてしまい、それだと英文の場合困るので単語ごとに空白を入れています。

書いたコードをsample.pyとして保存した場合、コマンドラインから下記を実行することで .exeファイルが作れます。

pyinstaller -F -w sample.py

引数はそれぞれ、生成するファイルを一つにまとめること、実行したときにウィンドウを生成しないことを意味しています。

生成したらあとは実行するだけなのですが、ランチャーに登録しておくと便利です。 私はOrchisというランチャーを使っており、Shiftキー2回連打でマウスの横にアプリが表示されるので、 実際に使うときは「コピー→ランチャー起動→クリックして改行を削除」という流れになります。

f:id:Kanchi0914:20180610050529p:plain

また、キーボードショートカットから特定のアプリを起動できるソフトを使えば、 好きなショートカットキーを押すだけで同じことができます。 そのようなソフトにはX Button Makerなどがあります。

余談

似たようなことができるツールがないか探したところ、普通にありました。 www.vector.co.jp