No more Death March

あるSEのチラシの裏 C# WPF

WPF Bindingの解除は必要なのか

StackOverFlowから、ざっくりいうと

質問が「Bindingって解除しないとメモリリークする?」ということで、
それに対する回答が
「DependencyPropertyかINotifyPropertyChangedを実装しているなら大丈夫、CLRオブジェクトだとメモリリークするかも。」
とのこと。

c# - Can bindings create memory leaks in WPF? - Stack Overflow

変更チェックのためにPropertyDescriptorが対象のオブジェクトに対する強参照を保持してしまうようだけど、OneTimeなら大丈夫みたい。一回こっきりで値がわかれば良いからね。

WPF ScrollViewerとPreviewKeyDown

画面上にButtonやToggleButtonを並べて配置すると矢印キーの操作でフォーカスを移動させることが出来るが、この連続したボタンをScrollViewerでラップするとフォーカス移動が効かなくなり、代わりにScrollViewerのスクロールバーが押した矢印の方向に動くようになる。

スクロールバーの動きを無効化してボタン間のフォーカス移動を復活させようとしたとき、ボタン側のKeyDownイベント等でフォーカスを操作しようとしても上手くいかない、というのもボタンでKeyDownイベントが着火する前にスクロールバーのPreviewKeyDownイベントでスクロール操作が処理され、下位のUI要素には処理済みとしてイベントが通知されなくなるからだ。

WPF PreviewKeyDownとPreviewKeyUpの動き

またWPFについてちょとしたメモ、プログラムで嵌ったことはいつかの自分のためなるべくメモしておきたい。

今回はPreviewKeyDownイベントとPreviewKeyUpイベントについて書いておく。当初、キーを押したらKeyDownイベントがキーを離したらKeyUpイベントが着火する程度の認識だったんだけど、コントロールをカスタマイズしててなかなか上手く行かないことがあり、よくよく調べて見れば思ってたのと大分違う動き方をしていた。

端的に結論だけ、まずキーを押下するとKeyDownイベントが着火するのだが、キーを押しっぱなしにしている限りイベントは連続で着火し続ける。でキーを離すとKeyUpイベントが着火する。加えて、例えばShiftキーとAltキーを同時押ししていた場合、キーを押しっぱなしにしている間はKeyDownイベントが連続で着火するのだが、どちらかのキーを離し、KeyUpイベントが着火すると、新たにキーを押下するまでKeyDownイベントは着火しなくなる。

つまり、KeyDownはKeyUpが着火するまで連続で着火し続け、KeyUpが発生すると押しているキーが残っているか否かを問わず、次のKeyDown操作が行われるまでイベントは発生しなくなるということ。

WPF TextBoxのIsReadOnlyとIsEnabledの組み合わせについて

WPFに関するちょっとしたメモ、TextBoxを修正させたくない場合基本的にはIsReadOnlyプロパティにTrueを設定するかIsEnabledにFalseを設定すると思われる。

が、IsReadOnly=False、IsEnabled=Trueの場合に限って、少し困ったことになる場合がある。それは、確かにキーボードによる文字入力自体は出来なくなるのだけど、既にテキストが入っている状態だと右クリックによるテキストの変換が動いてしまうということ。

TextBoxをカスタマイズして数字入力コントロールを作ったりした時、この仕様を理解していないとコードビハインドの制約をすり抜けて数字が漢数字に変換されてしまったりする。なので、TextBoxにIsEnabled=Falseと指定するのであれば、合わせてIsReadOnly=Trueを設定するようにしたい。

他にもコンテキストメニュー自体を無効化したり、入れ替えたりとやり方は色々あるのだけど、とにかくこういう動きが「仕様」ってことを抑えて置かないと意図しない文字が設定されることがあるので注意が必要だ。

ソースの記事自体はどこで見たか忘れたしまったのだけど確かMSDNでもこれに関して説明があってIsEnabled=Falseにするなら一緒にIsReadOnly=Trueにしましょう、という旨の解説がされていたと思う。

直感的にはIsEnabled=Falseであればコードで直接Textプロパティを操作する以外、テキストが変更されるようなことはないと思うが、これは「仕様」らしい・・・

WPF StackPanelとScrollVIewerの不思議な動き

StackPanelとScrollViewerを組み合わせた時、
思った通りにScrollViewerが機能せずに引っ掛かることがある。

普通に動く場合

f:id:nomoredeathmarch:20190120174203p:plain

StackPanelをScrollViewerでラップすると、
画像のようにスクロール操作が出来るようになる。

機能しない場合

f:id:nomoredeathmarch:20190120174207p:plain

先の例とほとんど変わらないのだが、
ScrollViewerの親要素の大きさがAutoで指定されていると、
スクロールバーが見切れた状態になりスクロール操作が出来ない。

じゃあどうする?

f:id:nomoredeathmarch:20190120174211p:plain

ScrollViewerのサイズが特定出来るよう、Autoではなく*で高さを指定するとスクロール操作が出来るようになる。

なにが原因なのか?

はっきりとした理由は不明、なので予想だけ。

StackPanelは列挙する方向に対して無限長の領域をとると思われる。
ので、ScrollViewerでラップしてやるとStackPanelから返ってくる要求サイズは無限長となりScrollViewerの要求サイズも無限長となる。

本来であれば更に親の要素に要求サイズを渡したとき、
親の要素が具体的に決まっていればその大きさに合わせてScrollViewerのサイズも調整されるが
GridのAuto指定の場合だと子要素のサイズが採用されるのでScrollViewerとStackPanelのサイズは不定となる。

結果、画面には収まらないが要求された通りの描画が行われScrollViewerから見ても子要素が全部収まってるという判断がされるので、
スクロール操作は出来ないよね、という具合に判断されている。

MSDNによるとRowDefinition.Heightプロパティの既定値はstarなので、
最初のサンプルと最後のサンプルではWindowの高さに合わせた調整が行われ、
ScrollViewerのサイズもレイアウト時に調整されたのだろう。

docs.microsoft.com

つまり、ScrollViewerが自分の高さがわからないという状況にならなければ良いわけで、
ScrollViewerのMaxHeightプロパティに値を指定してもスクロールバーは動くようになる。
が、動的に配置を調整してくれるXAMLの強みが無くなってしまうのでこの方法は微妙だと思う。


StackOverFlowでも「ScrollViewerが上手く動かない」というトピックが散見され、
みんな同じとこで嵌ってる様子。

この辺りは仕様上の挙動と直感的な挙動が剥離してるよなぁ。。。

WPF 不明な型 ~ を作成できません。

XAML関係で引っ掛かりやすいところ。

XAMLの中で名前空間を指定する際に、同じアセンブリ内であればアセンブリ名を省略して記述することが出来る。

次の画像がアセンブリ名を省略したもの。
f:id:nomoredeathmarch:20190114164547p:plain

省略せずに記述したものは次の画像のようになる。
f:id:nomoredeathmarch:20190114164551p:plain

省略した方が記述が短いので良いのだが、
前者のディクショナリを外部のアセンブリから利用し、その名前空間内で宣言された型を生成しようとすると。

不明な型 ~ を作成できません。

と実行時に例外が出てしまう。

後者のようにアセンブリ名を省略しなければ例外は発生せず、ちゃんと動いてくれる。
XAMLはパースされた側が解決出来るか否かではなく、パースした側が解決出来るかどうかという問題ということだろうか???
(その割にはリソースディクショナリをマージする記述は相対パスだけでも大丈夫だったりするのだけど・・・)