(2010.10.18〜)
アクセラレーションキー(Altシーケンス)は、項目名の該当文字の前に「&」を付ける。よく忘れるので注意。処理系によって付ける記号が異なるので混乱する。なお、何も指定しないと、頭文字が自動的にアクセラレーションキーになるようだ。 |
ただし、この方法では「Ctrl+文字」または一部の特殊キーしか登録できない。もうちょっとキー入力の自由度を高めたい。併用キーなしの文字キーやカーソルキー等にも機能を割り振りたい。こういうときには、フォームのキー入力イベント(KeyDown)を使う。このイベントハンドラでキー入力をフックして、必要な処理をしたあと、キー入力をコントロールに渡さないようにすればよい。ほとんどのキーでこの方法が使える。
こういうときは、ボタンのPreviewKeyEventハンドラでIsInputKey=Trueとすれば、タブやカーソルも通常のキー入力として見なされる。しかし、タブまでキー入力と見なされるのはちと困る。確かに、カーソルはキー入力として処理したいのだが、タブはフォーカスの移動のままにしておきたい。カーソルキーのみ例外的な処理をしたいわけだ。
で、まあ、いろいろ考えたんだが(フォーカス移動ルーチンを自作するとか(^^;)、結局、KeyCode=37/39(左右のカーソルキー)のときだけ、IsInputKeyをTrueにすればよいことを発見した。具体的には、たとえば、Button1_PreviewKeyDownの中で、
If (e.KeyCode=37) or (e.KeyCode=39) Then e.IsInputKey = True Else e.IsInputKey=Falseとすればよい。
なお、PreviewKeyDownイベントハンドラの宣言部のHandlesのパラメータには注意すること。一つのボタンに対して上記の設定を行えば、Handlesで指定されている他のコントロールでもこの設定が有効になる。しかも、Handlesはイベントハンドラ作成時に自動的に設定されるので、ユーザーが明示的に指定する必要もない。ただし、このイベントハンドラを作成したあとに追加したコントロールに関しては、手作業での追加が必要になる。
ファイル操作には、FileSystemObjectというスクリプト用のオブジェクトもあるが、これはデフォルトのVB2010では使えない。ランタイムを追加すれば使えるのかも知れないが、私は成功しなかった。それに、基本的にテキストファイル専用のようだ。 |
ちなみに、「My.Computer.FileSystem.GetFiles」という形式で使用する。なお、M$のHPのサンプルソースには誤りがあるようだ。第二パラメータは「SearchOption.SearchTopLevelOnly」または「〜.AllDirectories」でないとダメらしい。
なお、.Net Framework2以降だと、「System.IO.Directory.GetFiles」がほぼ同等の機能のメソッドだが、パラメータの順番が違うようだ。なんか、こっちの方がスマートな気がするんだが、どちらを使うべきか?
本来なら、DelphiのFileListBoxみたいのがあると便利なんだが、どうもないようだ。ただし、GetFilesの内容をそのまま配列に読み込んで、ListBoxに配列ごとAddRangeすれば、ほぼ同じことができる。少なくとも、一つずつチマチマ扱う必要はない。以下は、M$のHPにあった例の抜粋(一つずつ取り出す場合は次項参照)。
Dim files As String() = System.IO.Directory.GetFiles("C:\test", "*", System.IO.SearchOption.AllDirectories) ListBox1.Items.AddRange(files)
Dim exps() As string = {"*.jpg","*.png","*.gif","*.jpeg"} ListBox1.Items.Clear 'ListBox1の中身を初期化 For Each ex As string in exps '拡張子を順次指定するループ For Each fname As String In My.Computer.FileSystem.GetFiles _ '該当ファイルを一つずつ取り出すループ ("D:\ImgDir", FileIO.SearchOption.SearchTopLevelOnly, ex) ListBox1.Items.Add(fname) 'ListBox1にファイル名を一つずつ追加 Next Next
なので、元データと、元データを90度回転させたデータを、それぞれ別データとして保持して、必要に応じて切り替えることにした。問題は、回転済みの形式で保持する方法が見つからないこと。元データを内部で編集しても、同じ物にしかならないようだ。回転してコピーすればデータ並びも変わると思ったんだが…
で、逃げ道として考えたのが、回転後のデータを外部ファイル(テンポラリファイル)に書き出し、再度読み込む方法。確信があったわけではないが、結果的にこれで上手くいった。
ところが、プログラム終了時に、このテンポラリファイルが削除できない。それどころか、起動中にテンポラリファイルを再利用することすらできず、プレビューする画像の数と同じだけのテンポラリファイルが必要になる。何と、image.FromFile(ファイル名)で読み込んだファイルは、プログラムが終了されるまでロックされる仕様。imageをdisposeしてもロックが外れない。仕様と言うよりバグだろう。同じ現象に泣いているプログラマが物凄く沢山いるようだ。
で、これに対処する方法としてM$が提示しているのが、image.FromStreamを使ってストリームで読み込む方法。コードは少し面倒になるが、確かにこの方法だとロックは掛からない。ところが、これで読み込むと、回転画像の表示が元の状態(表示の度に回転処理を行う)に戻ってしまう!う〜む、面妖な…
しょうがないので、FromFileで読む事は変えず、何とかロックを外せないか、いろいろと試してみた。一番可能性がありそうだったのは、最後にダミーファイルを読み込む方法。一つのimageに画像を複数回読み込んだ場合、最後のファイルにのみロックが掛かるようなので、最後にダミーファイルを読み込んだらどうだろうか? 方向性としては有力だったが、そう簡単ではなかった。読み込むだけでいいのか?表示は必要か?どのタイミングで読み込むのか?どのルーチンに置くのか?などなど、検討すべき条件が多く、本来の機能のジャマになるカンジなのだ。少なくとも、デストラクタでFromFileする程度では駄目。書き出したファイルをコピーするなどの方法も試してみたが効果なし。
で、最後に考えたのが、例外処理。要するにこれはバグなのだから、バグらしい対処の仕方の方がよい。フォームのCloseハンドラ内でKILL命令を使ってテンポラリファイルを削除するのだが、このKILL命令をtryブロックに入れて、IOExceptionを捕まえて、何もせずに終了することにした。これでうまくいくんだよね……まあ、確かに、最後の一つが消せないことはあるみたいだけど、全部消えることもある。何とも…
ということで、えらいこと苦労したんだが、そもそもの問題としては、回転表示が速ければ、あるいはまともな回転変換ができれば済む話で、そのあたりを再度検討はしてみたいとも思う…
キー | 機能 | 機能名 | 備考 |
Ctrl+H | BackSpace | 1語削除 | 変な表記だ… |
Ctrl+M | Enter | 行に改行を挿入 | これまた変だ |
BSをCtrl+Hに割り付けると、置換とバッディングするが、BSを優先すれば問題ない。 問題はCtrl+Mの方で、2ストロークキーに使われていて、しかも、かなり沢山ある。 でもって、特に2ストロークキーの第一キー指定があるわけではなく、 使われているキー登録を一つずつ潰していくしか方法がないようだ、オイオイ…
ま、とりあえず、ダイヤモンド・カーソルくらいはできるようになった、と…。 それだけでも、けっこうありがたい。
初期ディレクトリの設定例 | : | OpenFileDialog.InitialDirectory="d:\mydir"
フィルターの設定例 | : | OpenFileDialog.Filter="Images|*jpg;*.png;*gif"
| |
フィルターの指定子の書式は次の通り
項目名|拡張子;拡張子;…|項目名|拡張子;拡張子;…もちろん、拡張子は一つだけでもよい
"テキスト|*.txt|HTML|*.html"
WindowState = FormWindowState.Maximized FormBorderStyle = FormBorderStyle.Noneただし、これはフォーム全体を全画面表示するものであり、 フォーム上のコンポーネントは全て表示される。 もし、PictureBoxのみ全画面表示にして、 他のコンポーネントを隠したいなら、 隠すコンポーネントのサイズやVisibleを変更する必要がある。 今回私が作成したプログラムの場合、 キモはTableLayoutPanelのサイズ変更で、これは、
TableLayoutPanel.RowStyle(1)=0といったカンジで余分な行のサイズを0にする必要があった。
[タグID][データのタイプ][データの長さ][データの内容][タグID]というのは、データの種類のことで、カメラの機種名とか、絞り値とか、露出補正などを示す、予め決められた数値が入っている。例えば「272」は「カメラの機種名」、「33434」は「シャッター速度」、「36867」は「撮影日時」。
[データのタイプ]というのは、端的に言えば数値か文字列か、という区別。これも予め決められた数値で表わされる。例えば、「Type2」は文字列、「Type3」は2バイト整数。問題は数値を表わす方法で、Exifでは基本的に整数形式しか扱えない。写真で扱う数値は整数とは限らないのだが、プログラム言語の浮動小数点型のような形式は採用されてないらしい(メーカーが独自拡張していれば別だが)。「Type5」や「Type10」は実数を扱う形式だが、これらは二つの整数を組み合わせて実数を表現している。
[データの長さ]は文字通りデータのバイト長。
[データの内容]は、数値の場合はデータの内容そのもの、文字列の場合は内容が格納されている場所のアドレスを示すポインタになることが多い。
Dim img as Image = Image.FromFile("p:\test.jpg") 画像の読み込み Dim item as System.Drawing.Imaging.PropertyItem 各プロパティを入れる変数 For Each item in img.PropertyItems ListBox1.Items.Add(item.id) プロパティの識別番号(タグ) ListBox1.Items.Add(item.type) プロパティのタイプ(2は文字列) ListBox1.Items.Add(item.len) プロパティの値の長さ If item.type=2 then タイプ2のプロパティの値を文字列化して表示 ListBox1.Items.Add(System.Text.Encoding.ASCII.GetString(item.value)) End if Next前述のように、id(タグID)、type(データタイプ)、len(データ長)、val(データ値)の4つの項目が取得できる。type2以外は数値、type2は文字列だがそのままでは読めないので(単純にバイト配列として扱われる)、ASCII.GetStringで通常の文字列に変換している。また、文字列の場合、本来valの値は文字列の場所を示すポインタだが、このメソッドでは自動的に文字列の内容を返してくれる。
ただし、このPropertyItemsにはバグがあるようで、一部のデジカメの画像のExif情報が読み取れないことがある。少なくとも、東芝Allegretto 2300のメーカー名や機種名は読み落としている。バイナリエディタで画像データの中身を覗くと、メーカー名も機種名もきちんと入っているし、Windows 7のファイルのプロパティでも表示される。PropertyItemsのバグとしか考えられない。Exifの構造はそんなに複雑ではないので、自分で読み取りルーチンを作るのも悪くはないかも…