投稿者「Yusuke Yamasaki」のアーカイブ

Yusuke Yamasaki について

http://www.linkedin.com/in/yyamasak

UI Automation Clients

UI Automationについて参考になりそうなページを50個ほどブックマークしたものの、多すぎるので入口として役立ちそうなページを抜粋しておく。
本来はUIテストやアクセシビリティを目的としているようだが、今回は他人が作ったソフトを自動制御するために使う。
プロバイダとクライアントの説明があるが、今回はクライアントのことが分かればよいはず。
Windows Forms、WPF、サードパーティのUIコンポーネントでもたいていはオートメーションプロバイダとして扱えるようだ。
確証はないがUISpy.exeを使ってAutomationIdなどが確認できれば対応していると思っていいのではないかと思っている。

UI Automation のススメ

UI Automation Fundamentals

UI Automation Client Programmer's Guide (Windows)

Use the AutomationID Property

Bugslayer - こちらGUIコントロール、トム少佐応答せよ

UIツリーを辿り、プロパティを確認するにはUISpyやInspect Objectというのがある。
UI Spy(UISpy.exe)が含まれているWindows SDK Version

現状はC#でやるのが無難そうだ。
TclでもGarudaを使うと、インプロセスで.NET Frameworkのクラスを利用できるのだが、まだ未完成、ドキュメントがないに等しいので難しい。クラスメソッドやイテレーションを使えるのかすら分からない。
COMは茨の道だと思われるのでできれば避けたい。

ちまちまUI Treeを歩き回るコードを書く代わりに、もっと楽にしてくれるライブラリというのもあるみたいだ。
White
White Tutorial
StackOverflow White - File Open Dialog Box
White - Working with window

UISpyみたいなインターフェースと、UI Automationプロバイダとして必要な機能を持っているかチェックするためのソフト。
Visual UI Automation Verify
UIVerifyが内部で使っているライブラリがこれ。
UI Automation COM-to-.NET Adapter
.NET版のAPIがあるのに何でラッパーを作る必要があるのかは気になるが、まだ調べてない。

Tclでベクトル計算するサンプル

SXGAのリモートデスクトップウィンドウをアスペクト比を保ちつつ65%に縮小し、FullHDのディスプレイの右下に配置したい。手計算でも簡単にできることだが、今まで使ってなかった行列計算ライブラリの練習台に使うことを思いついた。

+-----------------------------+
|                             |
|                             |
|                             |
|                             |
|                             |
|               nw------------+
|               |xxxx         |
|               |   x rec x   |
|               |        xxxx |
|               |           xxx
+---------------+------------se
package require math::linearalgebra
namespace eval LinearAlgebraSample {
  namespace import -force ::math::linearalgebra::*
 
  set scale 0.65
  set v(se) [mkVector 2]
  set v(rec) [mkVector 2]
 
  setelem v(se) 0 0 1920
  setelem v(se) 1 0 1280
 
  setelem v(rec) 0 0 1280
  setelem v(rec) 1 0 1024
 
  set v(nw) [sub_vect $v(se) [scale $scale $v(rec)]]
  show $v(nw)
}

例題がしょぼすぎて練習になってないな。

線形代数苦手でプログラミングにも活用してないし、Maxima、Scilab、Octave、Rとかも使ってない。
ロボットの研究室にいたのに行列計算が苦手というのは勉強してなかった証拠ですね。

ネットワークアドレス変換関連の用語

NAT (Network Address Translation)

グローバルIPとローカルIPを1:1対応させる。

NAPT (Network Address Port Translation)

グローバルIPアドレスとポート番号のペアをローカルIPとポート番号のペアに1:1対応させる。

NAPTには多数の同義語が存在する

  • IPマスカレードはNAPTのLinuxにおける実装。
  • NATP(NAT+)、PAT(Port Address Translation)
  • ポートマッピング、ポートフォワーディング
  • 仮想サーバー、バーチャルサーバー、ローカルサーバー
  • NATオーバーロード、オーバーロード変換

静的NAT、静的NAPTは外部から内部のネットワークへのアクセスに使う。一般的なブロードバンドルータの設定に存在するのはこれらの設定。

動的NAT、動的NAPTは内部から外部への通信に対する応答が、送信元に返るようにするための機能であり、通常ユーザーが意識することはない。

と思う。

参考

IPマスカレード(NAPT,PAT,NAT+)を設定する
NAT と IPマスカレード(ポートフォワーディング) について
4.インターネットへの接続形態

Excelオートシェイプでフリーフォームの頂点を直角にする

Excel2007登場以来、なんかいい方法はないものかと探していたけど、ついに見つけた。ありがたや。

図形描画で直線の頂点を編集するには?

Public Sub main() 
    On Error GoTo L_Exit 
    With Selection.ShapeRange.Nodes 
        On Error Resume Next 
        Dim i As Integer 
        For i = 1 To .Count 
            .SetSegmentType i, msoSegmentLine 
            .SetEditingType i, msoEditingCorner 
        Next 
    End With 
L_Exit: 
    On Error GoTo 0 
End Sub

かざぐるマウスからGestures for Chrome (ChromeGesture)に乗り換え。

林檎の木で紹介されてたCloverを入れてみたけど、マウスホイールでスクロールすると無限ループに陥ってしまった。かざぐるマウスを停止すると問題は発生しない。設定でエクスプローラを除外するとか、いろいろやってみたけど効果はなし。
今まで使ったマウスジェスチャソフトで一番気に入っているものだけど、Tcl/Tkとの相性問題というのもあった。
最近はブラウザを使い分けることがほぼなくなったことと、Gestures for Chromeがそろそろ完成度が高まってるのではないかと思い、
ちょっと入れてみました。かざぐるマウスの自分設定と同じにしたものを晒しておきます。
Chrome拡張はchrome:chrome_urlsに対しては無効なので、スタートページでもやはり無効です。
あとページによってはジェスチャの開始点が限定される場合がある。それ以外はそこそこ使えそうなので、しばらくこれで試してみます。

Public Sub main() 
    On Error GoTo L_Exit 
    With Selection.ShapeRange.Nodes 
        On Error Resume Next 
        Dim i As Integer 
        For i = 1 To .Count 
            .SetSegmentType i, msoSegmentLine 
            .SetEditingType i, msoEditingCorner 
        Next 
    End With 
L_Exit: 
    On Error GoTo 0 
End Sub

Tcl/Tkで任意のフォルダをデフォルトファイラーで開く

たまにやるけど忘れるので。スペースを含まないときは別のやり方でも開くのですが、
一般的には以下のようにします。cmdの引数にエスケープされた状態で渡すというのがキモです。
string mapの変換ルールではエスケープシーケンスが適用されることに注意します。

{
  "name": "Chrome Gestures",
  "version": "1.12.1",
  "normal_actions": {
    "L": {
      "name": "back",
      "args": []
    },
    "R": {
      "name": "forward",
      "args": []
    },
    "UL": {
      "name": "select left tab",
      "args": []
    },
    "D": {
      "name": "go to #1",
      "args": [
        "http://www.google.com/",
        "Google"
      ]
    },
    "DR": {
      "name": "close this tab",
      "args": []
    },
    "#FlipBack": {
      "name": "back",
      "args": []
    },
    "#FlipForward": {
      "name": "forward",
      "args": []
    },
    "U": {
      "name": "run script #1",
      "args": [
        "(function(){if((document.fullScreenElement&&document.fullScreenElement!==null)||(!document.mozFullScreen&&!document.webkitIsFullScreen)){if(document.documentElement.requestFullScreen){document.documentElement.requestFullScreen();}else if(document.documentElement.mozRequestFullScreen){document.documentElement.mozRequestFullScreen();}else if(document.documentElement.webkitRequestFullScreen){document.documentElement.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);}}else{if(document.cancelFullScreen){document.cancelFullScreen();}else if(document.mozCancelFullScreen){document.mozCancelFullScreen();}else if(document.webkitCancelFullScreen){document.webkitCancelFullScreen();}}})()",
        "Fullscreen"
      ]
    },
    "UR": {
      "name": "select right tab",
      "args": []
    },
    "DL": {
      "name": "re-open closed tab",
      "args": []
    }
  },
  "linkdrag_actions": {
    "D": {
      "name": "open in new tab",
      "args": []
    },
    "DU": {
      "name": "open in background tab",
      "args": []
    },
    "U": {
      "name": "copy url",
      "args": []
    },
    "UD": {
      "name": "copy url and text",
      "args": []
    }
  },
  "textdrag_actions": {
    "DR": {
      "name": "search with #1 in new tab",
      "args": [
        "http://www.google.com/search?q=%s",
        "Google"
      ]
    },
    "UL": {
      "name": "copy text",
      "args": []
    },
    "UR": {
      "name": "copy html",
      "args": []
    }
  },
  "mouse_track": true,
  "visualized_arrow": true,
  "superdrag": false,
  "useMousewheel": true,
  "useTabList": false,
  "useSmoothScroll": false,
  "ScrollSpeedValue": 0.2,
  "useScrollAcceleration": true,
  "AccelerationValue": 2,
  "suppress_contextmenu": true,
  "minimumUnit": 10
}

-- 追記
上記のスクリプトではエクスプローラで開くのが前提だったので、私のように秀丸ファイラーなどの代替ファイラーを使ってる人にとっては100%満足できるものではありませんでした。なんかこれを書いてるちょっと前にStackOverflowに同じ内容の質問が挙がってて、上記の提案をドヤ顔で投稿したところ、100%満足な回答が返ってきました。
http://bit.ly/Uu6q6N

set folderpath "C:/Program Files/Microsoft Office"
exec {*}[auto_execok start] "" [file nativename $folderpath]

ちなみに、趣旨が異なりますが、エクスプローラで指定のファイルを選択した状態でフォルダを開くには、

set out "C:/Program Files (x86)/Microsoft Office/Office14/EXCEL.EXE"
exec explorer.exe /select, [file nativename $out] &

LinkedList in XOTcl

いわゆる双方向連結リストというデータ構造ですが、かつてはリチャードストールマンに、Tclには構造体がないからlinked listを作れないと指摘されていました。今ではいくつもあるオブジェクト指向拡張を使い、クラスを導入すればTclでも他の言語と同じような形で実装できます。

arrayでもできますが、あまり使いやすくならないと思うので、僕はXOTclでLinkedList::ContainerとLinkedList::Elementというクラスを作っていて、これらを継承するか、mixinしたクラスは、連結リストの管理オブジェクト、リスト要素として扱えるようにしています。

通常のリストに比べると、要素が少ないうちは遅いですが、数千数万の要素数になってくるとかなり効率が違ってきます。

テストスクリプト

set office_dir {C:\Program Files\Microsoft Office}
set escaped_office_dir [string map {\\ \\\\} [file nativename $office_dir]]
puts [concat exec [auto_execok start] explorer $escaped_office_dir]
eval exec [auto_execok start] explorer $escaped_office_dir

結果

set folderpath "C:/Program Files/Microsoft Office"
exec {*}[auto_execok start] "" [file nativename $folderpath]

tktableなんかもarrayをデータソースにすると高速なんですが、arrayを直接操作するよりも、データの順序などを安全に扱うことができる連結リストでデータ管理するのがいいと思います。

set out "C:/Program Files (x86)/Microsoft Office/Office14/EXCEL.EXE"
exec explorer.exe /select, [file nativename $out] &

パッケージにしておきました。
linkedlist.3.0.120823

過去にやった「リレーショナルデータベースの間違った使い方」

いきあたりばったりのアーキテクチャと教訓:リレーショナルデータベースの間違った使い方10項目

動的なテーブルの作成 ⇒ INSERTのトリガで月ごとのテーブル作るようにしたらINSERTが遅すぎて失敗しました。
テーブルをキャッシュとして使う ⇒ Railsではセッションキャッシュに使ってたけど、のちにcookieセッションに変わったと思う。
テーブルをキューとして使う ⇒ やりました。Dequeを使うのがいいのですが、自作のデータグリッドウィジェットと自作のTcl版ActiveRecordの依存性が高すぎて変更が大変。いまだに変えられない。
テーブルをログとして使う ⇒ 他にどうすんの?KVSってやつ?検索とか似たような機能あるの?
分散したグローバルなロック ⇒ なにそれ?
ストアドプロシージャ ⇒ 使ったことない。
使われない項目 ⇒ いくつかあるけど、そんなに多くない。検索キーにならないカラムが増えていくのを、YAML形式とかでテキストカラムに押し込めるべきか悩んでる。
ORMによって繰り返されるクエリ ⇒ よくやりました。暗黙的遅延ロード機能なんかつけるからだ。ActiveRecordなら:include=>"groups"、EFなら.Include("groups")を使う。
負荷のコントロール ⇒ DBじゃないけど、ウェブサーバのリバースプロキシとかはよくやりました。メモリリークだらけのRailsには必要だった。

break可能なコードブロック

Tclにはgotoみたいに直列に書いたコードをスキップする制御構文がない。

例えばなんかデータを受信して、チェックしたり加工したりしたあとでどこかに記録するけど、
結果によらず受信回数はカウントしときたい場合、こんなコードを書いたとする。

proc on_receive {data} {
  global cnt
  set res [validate $data]
  if {$res == 1} {
    incr cnt; return $res
  }
  set res [convert $data data2]
  if {$res == 2} {
    incr cnt; return $res
  }
  set res [save $data2]
  incr cnt; return $res
}

incr cnt; return $res; というコードを何度も書かないといけないのが嫌だ。

そこで今まではこんな書き方をしてたことがあった。

proc on_receive {data} {
  global cnt
  while {1} {
    set res [validate $data]
    if {$res == 1} break
    set res [convert $data data2]
    if {$res == 2} break
    set res [save $data2]
    break; # Don't forget!
  }
  incr cnt; return $res
}

ときどきだけど、while {1} の最後のbreakを忘れて、
無限ループに陥ることがあったので、ケアレスミスを減らすため、こう変えた。

proc on_receive {data} {
  global cnt
  foreach once {1} {
    set res [validate $data]
    if {$res == 1} break
    set res [convert $data data2]
    if {$res == 2} break
    set res [save $data2]
  }
  incr cnt; return $res
}

ちなみにforeachのところは、こうしても同じ。

proc on_receive {data} {
  global cnt
  for {} {1} break {
    # ...
  }

だけど何とも見た目が悪い。
初めて見た人は foreach {var1 var2} {1 2} break 並みに分かりづらいだろう。
多重代入のためにlassignが導入されたように、やはり目的が分かる名前の制御構文があったほうがかっこいい。

8.6のtry(8.5ではtcllibに入ってる)を使って、blockという途中でbreak可能な制御構文を作ってみる。

if {[info tclversion] <= 8.5} {
package require try
}
proc block {script} {
  try {
    return [uplevel 1 $script]
  } on break {result options} {}
}

さっきのコードを書き換えてみる。

proc on_receive {data} {
  global cnt
  block {
    set res [validate $data]
    if {$res == 1} break
    set res [convert $data data2]
    if {$res == 2} break
    set res [save $data2]
  }
  incr cnt
  return $res
}

どうかな。

これだけでblockなんて名乗るのはおこがましいので、もうちょい慎ましい名前を考えたら使ってみようと思う。

候補

  • breakable
  • through

Tclだとそんなところに脳のリソースを割かないといけないの?とか言わないでね。