何も知らない人への”MVVMって何”?

WinForms開発経験が存分にある人だとそこそこ理解してもらえるんだろうけど、そうじゃないと辛いかなぁということで。

流石にオブジェクト指向の基本的な知識はあってほしい。

GUIについて。

GUIは、一般的にコンストラクタで各種部品をインスタンス化していくことで作ります。
部品に対する操作(ボタンをクリックしたり、チェックボックスのチェックを付けたり)などは、イベントとしてコールされます。このイベントに反応して処理することによって、いろいろな処理を行います。

01

こんな模式図。

古典的なUIとドメインロジックの分離。

さすがにこれでは何がなんだか分からないので、せめてUIを構成するソースコードとUIからの反応に対するソースコードを分けようと、先の偉い人は考えた訳です。

02

分けたんですが、同じクラス内でソースコードを分離したりなどのゆるーい区切りしかありませんでした。

MVCパターン。

あんまり変わってないじゃんか!と怒った先の偉いひとは、もっと明確に分離しようと考えました。MVC(Model-View-Controller)パターンの誕生です。

03

Modelでデータの保持、ViewでUIへの反映、Controllerでその調停、そんな感じで役割を受け持つことですっきりしました。

じゃあ、MVVMパターンは?

04

見てわかるとおり、MVCパターンとあんまり変わりません。

MVVMパターンは”WPF”というアプリケーション開発のベースとなる機構に沿って作られた、MVCパターンの変化球のようなものです。どう違うのでしょう。

05

これは一例ですが、ちょっとだけ変化したのが分かると思います。

青い矢印が入りました。これは、WPFが受け持ってくれる部分です。この部分はある作法に則って作ると、WPFが自動で制御してくれます。また、操作が直接ViewModelに行くようになりました。これもいろいろな仕掛けのおかげです。

と、小難しいことを書いていますが、要するに「より見通し良くソフトを作りたい!」という欲求の結果で生まれたものです。より綺麗に、やりたいことだけを見据えることができるようになります。

よりMVVMについて専門的な知識をお求めの場合は、

[MVVMパターンとは? – わんくま同盟東京勉強会 #60 セッション資料] – http://ugaya40.net/mvvm/what_is_mvvm.html

など参考になると思います。

こんな感じ?

“DependencyObject-Context”例外についての仮説。

注:個人的なメモです。8割憶測です。

System.ArgumentException: 指定された DependencyObject は、この Freezable のコンテキストではありません。

この例外は、Krile Mystique のレンダリング時に不定期に発生するもので、スタックトレースを見ても

場所 System.Windows.Freezable.RemoveContextInformation(DependencyObjectcontext, DependencyProperty property)
  場所 System.Windows.Freezable.RemoveInheritanceContext(DependencyObjectcontext, DependencyProperty property)
  場所 System.Windows.DependencyObject.RemoveSelfAsInheritanceContext(DependencyObject doValue, DependencyProperty dp)
  場所 System.Windows.DependencyObject.UpdateEffectiveValue(EntryIndex entryIndex, DependencyProperty dp, PropertyMetadata metadata, EffectiveValueEntry oldEntry, EffectiveValueEntry& newEntry, Boolean coerceWithDeferredReference, Boolean coerceWithCurrentValue, OperationType operationType)
  場所 System.Windows.DependencyObject.SetValueCommon(DependencyProperty dp, Object value, PropertyMetadata metadata, Boolean coerceWithDeferredReference, Boolean coerceWithCurrentValue, OperationType operationType, Boolean isInternal)
  場所 System.Windows.DependencyObject.SetCurrentValue(DependencyProperty dp, Object value)
  場所 System.Windows.Controls.Image.MeasureArrangeHelper(Size inputSize)
  場所 System.Windows.Controls.Image.MeasureOverride(Size constraint)
  場所 System.Windows.FrameworkElement.MeasureCore(Size availableSize)
  場所 System.Windows.UIElement.Measure(Size availableSize)
  場所 Mystique.Views.CustomPanels.FillPanel.MeasureOverride(Size availableSize)
  場所 System.Windows.FrameworkElement.MeasureCore(Size availableSize)
  場所 System.Windows.UIElement.Measure(Size availableSize)
  場所 System.Windows.Controls.Grid.MeasureCell(Int32 cell, Boolean forceInfinityV)
  場所 System.Windows.Controls.Grid.MeasureCellsGroup(Int32 cellsHead, Size referenceSize, Boolean ignoreDesiredSizeU, Boolean forceInfinityV)
  場所 System.Windows.Controls.Grid.MeasureOverride(Size constraint)
  場所 System.Windows.FrameworkElement.MeasureCore(Size availableSize)
  場所 System.Windows.UIElement.Measure(Size availableSize)
  場所 MS.Internal.Helper.MeasureElementWithSingleChild(UIElement element, Size constraint)
  場所 System.Windows.Controls.ContentPresenter.MeasureOverride(Size constraint)
  場所 System.Windows.FrameworkElement.MeasureCore(Size availableSize)
  場所 System.Windows.UIElement.Measure(Size availableSize)
  場所 System.Windows.Controls.Border.MeasureOverride(Size constraint)
  場所 System.Windows.FrameworkElement.MeasureCore(Size availableSize)
  場所 System.Windows.UIElement.Measure(Size availableSize)
  場所 System.Windows.Controls.Control.MeasureOverride(Size constraint)
  場所 System.Windows.FrameworkElement.MeasureCore(Size availableSize)
  場所 System.Windows.UIElement.Measure(Size availableSize)
  場所 MS.Internal.Helper.MeasureElementWithSingleChild(UIElement element, Size constraint)
  場所 System.Windows.Controls.ContentPresenter.MeasureOverride(Size constraint)
  場所 System.Windows.FrameworkElement.MeasureCore(Size availableSize)
  場所 System.Windows.UIElement.Measure(Size availableSize)
  場所 System.Windows.Controls.Border.MeasureOverride(Size constraint)
  場所 System.Windows.FrameworkElement.MeasureCore(Size availableSize)
  場所 System.Windows.UIElement.Measure(Size availableSize)
  場所 System.Windows.Controls.Control.MeasureOverride(Size constraint)
  場所 System.Windows.FrameworkElement.MeasureCore(Size availableSize)
  場所 System.Windows.UIElement.Measure(Size availableSize)
  場所 System.Windows.Controls.VirtualizingStackPanel.MeasureOverride(Size constraint)
  場所 System.Windows.FrameworkElement.MeasureCore(Size availableSize)
  場所 System.Windows.UIElement.Measure(Size availableSize)
  場所 System.Windows.ContextLayoutManager.UpdateLayout()
  場所 System.Windows.ContextLayoutManager.UpdateLayoutCallback(Object arg)
  場所 System.Windows.Media.MediaContext.FireInvokeOnRenderCallbacks()
  場所 System.Windows.Media.MediaContext.RenderMessageHandlerCore(Object resizedCompositionTarget)
  場所 System.Windows.Media.MediaContext.RenderMessageHandler(Object resizedCompositionTarget)
  場所 System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
  場所 MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)
  場所 System.Windows.Threading.Dispatcher.WrappedInvoke(Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
  場所 System.Windows.Threading.DispatcherOperation.InvokeImpl()
  場所 System.Threading.ExecutionContext.runTryCode(Object userData)
  場所 System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode code, CleanupCode backoutCode, Object userData)
  場所 System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
  場所 System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
  場所 System.Windows.Threading.DispatcherOperation.Invoke()
  場所 System.Windows.Threading.Dispatcher.ProcessQueue()
  場所 System.Windows.Threading.Dispatcher.WndProcHook(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
  場所 MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
  場所 MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
  場所 System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
  場所 MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)
  場所 System.Windows.Threading.Dispatcher.WrappedInvoke(Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
  場所 System.Windows.Threading.Dispatcher.InvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
  場所 MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
  場所 MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg)
  場所 System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)
  場所 System.Windows.Application.RunInternal(Window window)
  場所 System.Windows.Application.Run()
  場所 Mystique.App.Main()

と、要するにどうしたらいいんだろうかと頭を抱えてしまう代物だった。

最終的に内部で利用している画像キャッシュに起因するものだと分かったのだが、最近またこの例外が再燃した。これらの顛末と、そこからの僕の勝手な推測をメモっておく。

初期の問題

Krileの画像キャッシュは、平たく言えば Dictionary<Uri, BitmapImage> となっている。BitmapImageはFreezeしてしまえばスレッドセーフとして扱えるので、バックグラウンドスレッドで画像の取得とフリーズを行いこれをキャッシュに格納し、必要に応じてBitmapImageを取り出すという動作をしていた。(参照: http://msdn.microsoft.com/ja-jp/library/system.windows.freezable(v=VS.80).aspx )

画像の取得では当初は単に

return cache[uri];

としていたのだが、これではこの例外が不定期に送出されていた。そこで、

var img = cache[uri].Clone();
img.Freeze();
return img;

という実装に変更したところ、同様の例外はぱったり送出されなくなった。

というわけで、クローンしないとこの例外が飛ぶということを理解した。

最近の問題

画像の表示処理が処理のボトルネックとなっていることが分かったので、画像の表示処理を高速化したいと考えた。

種々の対策を講じたのだが、先述したClone部分にもメスを入れた。

http://msdn.microsoft.com/ja-jp/library/system.windows.freezable.getasfrozen.aspx

によると、

Freezable を固定できることを検証するには、このメソッドを呼び出す前に CanFreeze プロパティを確認する必要があります。 このメソッドを使用するのは、Clone を使用してコピーを作成してから、Freeze メソッドを使用して、作成したコピーを固定するのと同じです。

GetAsFrozen メソッドおよび GetCurrentValueAsFrozen メソッドでは、既に固定されている Freezable サブオブジェクトを複製せずに、それらを参照によってコピーするだけなので、コピー パフォーマンスが向上します。

とのことで、まさにうってつけだと感じ、先述した実装を

return (BitmapImage)cache[url].GetAsFrozen();

に変更した。

すると、また例外が送出されるようになった。

つまり

まず、初期の問題から考える。

初期の問題は、「Cloneしていない」ことによって例外が送出されていたと思われる。

また、この例外は常に

System.Windows.Freezable.RemoveContextInformation(DependencyObject context, DependencyProperty property)

から送出されている。

メソッドの具体的な動作が分からないが、このFreezableに付加していたContextInformationを取り除こうとしていることが分かる。そして、そんなコンテキストは無いということで例外が飛んだのだろう。完全に異常系で処理されているので、「本来コンテキストはあって然るべきだが、それが失われている」状態になっているということが分かる。

この手の問題で一番ありがちなのがスレッドセーフ関連だが、スタックトレースを見れば分かるとおり、このメソッドはディスパッチャスレッドから呼ばれているため、マルチスレッド絡みの問題は生じない。

残る可能性としては、Krileの構造に由来する例外だということ。Krileの構造上、BitmapImageのImageへの束縛と解放が短時間に大量に行われるという動作があり、これが悪影響を及ぼしているのではないか。

BitmapImageはCloneされず、同じURIに対しては同じインスタンスを返すようにしていたので、大量のImageから一つのインスタンスへの参照が張られている状態になる。この状態で動作している時に何らかの理由でContextInformationが破損した状態になり、これにより例外が送出、と考えると辻褄が合う。

子は複数の親を持つ場合に想定外の動作をする、ということなのだろうか?

とりあえずこれ以上は内部の実装の問題になってしまうので、「BitmapImageに限っては」複数の親に所持される場合、個々の親に対してCloneしてやる必要があるようだ。
※他のFreezableやDependencyObjectなどについては不明

最近の問題は明らかにCloneからGetAsFrozenメソッドへ切り替えたことが問題であるが、これは

GetAsFrozen メソッドおよび GetCurrentValueAsFrozen メソッドでは、既に固定されている Freezable サブオブジェクトを複製せずに、それらを参照によってコピーするだけなので、コピー パフォーマンスが向上します。

という点によるものだろう。複製によって例外送出を回避したのだから、「複製せず」なら例外が飛ぶ。

そんな感じのメモ。ImageCacheStorageを触るときは気を付ける。

Dictionary`2.Insertからぬるり

(゚д゚)ってなった事象。

System.NullReferenceException: オブジェクト参照がオブジェクト インスタンスに設定されていません。
  場所 System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
  場所 Inscribe.Communication.UserStreams.ConnectionManager.RefreshConnection(AccountInfo info)
  場所 Inscribe.Communication.UserStreams.ConnectionManager.b__2(AccountInfo i)

っていう例外が飛ぶんですね。なんでか知らないけど。

ConnectionManager.RefreshConnectionは単純にDictionaryの内容を弄り回してるだけなので、何が起きてるんだ 「Yフゥォォォォァァァァ—–!!!!!! ってなったところ。

ググってみたら答えがありました。

いつもいつもお世話になっているstackoverflowさん。

http://stackoverflow.com/questions/1320264/how-did-i-get-this-nullreferenceexception-error-here-right-after-the-constructor

Dictionary<>.Insert() will throw a NullReferenceException internally if the dictionary instance is modified from another thread during the insert operation.

要するに、きっちりDictionaryでSynchronizationが行われてないと死ぬよ、ってことですね。

lockなり、ReaderWriterLockSlimなりで同期取ってやるといいです。

.NET Framework 4 なら、System.Collections.Concurrent名前空間にConcurrentDictionaryっていうのもあります。こいつはスレッドセーフなので、あんまり変な事考えないで使うことができそうです。癖があるので、クリティカルなところだと自分でシンクロを実装した方が速いこともあるみたいですが。。

MVVMおぼえがき

自分の知ってるMVVMについての知識をまとめるつもりが、なんだか変な方向に行きました。

あと、以下に書いてある内容は正しいとは限りません。あくまで僕の考え方を自分の為にまとめたものです。プログラムの組み方なんて星の数ほどあるじゃないですかという。

WPFを使ってると、どうしても「Model-View-ViewModel パターン」、通称「MVVMパターン」を採用せざるを得なくなります。このMVVMパターン、定義は人によっていろいろなんですが、今回はただ単にViewとViewModelが存在する状態ってことにしといてください。そんなんMVVMじゃねーよって人はぜひ定義を教えてください。

MVVMパターンでプログラムを作成されたことがある人なら分かると思いますが、何も無しでMVVMパターンを実践しようとすると死にます。普通に死にます。大切な事なので二回言いました。それほどまでに正しく実装するのが割と難しいんですが、幸いなことに色々なライブラリやインフラストラクチャがリリースされています。たとえば、MVVM LightやPrismは有名どころですね。あとは最近僕が使ってるやつですが、Livetっていうのもあります。探せばたぶんいっぱい出てくると思います。

まぁインフラは使いましょう。問題はそこから、WinFormsやWPF(Without MVVM)でやってたことをどうやればいいのっていう。

Why MVVM?

なぜ今までのUIモデルを捨て、MVVMパターンを採用するのでしょう。答えは簡単、そのほうが作りやすいからです。少々回りくどくはなりますが、バグが少なくパフォーマンスが高くメンテナンスしやすいプログラムが作成できます。さらに、MVVMはWPFのためのパターンです。WPFの持つ表現力を最大限まで引き出すことができます。

MVVMは、MVPやMVCなどの三層アーキテクチャモデルの親戚です。すでに成熟したMVPやMVCではなく、何故MVVMパターンという新しいモデルを用いるのかと考える方がいるかと思いますが、それは誤解です。MVVMでも基本は変わりません。単純に、実現の方法が違うだけです。BindingというWPFの機能を活用すれば、より自然な三層アーキテクチャモデルを構築可能だということです。三層アーキテクチャ + Binding = MVVMだと考えると良いと思います。僕もそう考えています。

MVVMインフラストラクチャにはWPFという明確なターゲットがあるため、しばしばWPF固有の知識が盛り込まれますが、これはMVVMパターンをより簡単に実装可能にするためのもので、これらを活用しなければMVVMではないということはありません。ただ、車輪の再発明だと思いますので、できれば頼った方が良いとは思います。

要するに、肩肘張ってMVVMパターンを実装する必要はありません、ということです。あなたがMainWindowViewModel.csを作った瞬間、それはMVVMパターンです。(だと思います)

MVVM – Model View ViewModel

Viewは、画面です。勝手にできたり消えたりします。
ユーザーになにかを見せたり、ユーザーから入力を受け取ったりするのが仕事なので、このあたりはとても得意です。ですが、状態に応じて表示を変えたり、ユーザーの入力に応じて何かしたりっていうのは限定的な機能しかありません。

ViewModelは、画面とデータをくっつけるものです。
データをViewに送るために整形したり、ユーザーの操作を解釈したりするのがお仕事です。ちょうどViewに欠けている部分がまとまって固まってるようなものです。

Modelは、データです。なんでもいいです。

WinFormsでプログラムを作成されたことがある方なら、Viewが *.Designer.cs、ViewModelが*.csみたいなものかーって考えられる方もいらっしゃるかと思うんですが、ちょっと違います。Viewはもっと高度なことができます。見た目を司るのがView、全体的な動作を司るのがViewModelって考えるといいかもしれません。

生存期間

ViewとViewModelとModelはそれぞれ生存期間が違います。Modelはそれぞれでしょう。ViewModelは、参照してる間は生きてます。いずれにしても、ModelとViewModelの寿命についてはプログラマがコントロール可能です。ところが、Viewについては勝手にできたり勝手に消えたり、消えたと思ったらまた作られたりを繰り返しています。プログラマはコントロールできません。

たとえば、リストボックスを考えてみてください。10000件のデータがあるけど、そのうちの100件しか描画範囲内に無いなら、その100件だけあればいいですね。このとき、Viewは本当に100件分しか存在しません。(※実際のところはいろいろ変わってくるのですが、今回はそういうことにしといてください。)
リストをスクロールしたら、スクロールして新しく表れてきた分のViewを作って、隠れてしまった分のViewを削除すれば、メモリ領域を無駄に使うことなくリストを表示できますね。(※)
WPFは、この動作をいろんなところで自動的に行います。この一連の動作はプログラマからは隠されていて、いつできたか、いつ消されるかを知ることは基本的にできないと思った方が良いです。

さて、Viewを作るときには当然それの種となるものが必要です。見た目はXAMLのDataTemplateでなんとかなると思いますが、表示するデータはどうするかというと、ここでViewModelを使います。ViewModelが、前のViewの状態を覚えていて、新しく作られるViewにその情報を提供することで、ユーザーにViewが出来たり消えたりしていることを意識させないようにすることができます。←重要

データバインディング

Viewは当然どこかでViewModelへの参照を得ないといけません。また、各コントロールはViewModelのどのデータを参照すればいいかを指定されないとどれを表示すればいいんだか困ります。この時に使われるのが、データバインディングシステムです。

WPFの各コントロールには「DataContext」というプロパティが存在します。このDataContextはobject型のプロパティですから、いろんなデータを渡すことができます。WPFのコントロールの中にはコントロールを内包できるものがあります(WindowやGridなんかは代表例ですね)。そんな親コントロールのDataContextを設定した時、子コントロールのDataContextも自動的にそれを継承します。Windowの中にTextBoxを配置している時に、WindowのDataContextに何かセットすると、TextBoxのDataContextもそれになります。実のところ、これはViewModelへの参照そのものです。DataContextにViewModelのインスタンスをセットしてやればすべて上手く行きます。

コントロールでどのデータを表示するのかを決めるのがいわゆる「データバインディング」です。たとえば、<TextBlock Text=”{Binding SomeValue}“ />としたとき、このテキストブロックはViewModelのSomeValueプロパティの内容を取得して表示します。ViewModelの参照は先述の通り、DataContextを使います。

ところで、DataContextへViewModelのインスタンスを渡す方法も必要になります。考え付くのは、

  • XAMLでインスタンスを生成する
    <Window.DataContext>
        <vm:WindowViewModel />
    </Window.DataContext>
    のような感じですね。
  • コードビハインドでインスタンスを生成する
  • コードビハインドをViewModelにする(!!)
    Window.DataContext={Binding}
    で設定することができますが、これには実は問題があります。後でまた。
  • インフラの機能に頼る
    特に画面遷移の場面で多いのですが、インフラによっては、ViewModelを指定してViewを表示することができるものがあります。

とまぁいろいろあります。

このデータバインディングの機能を利用してViewとViewModelを結びつけますが、参照可能なのはView→ViewModel だけであることに注意してください。ViewModelはViewを直接操作することはできません。

遠回りな、理不尽な実装に見えますが、これは非常に理にかなっています。次で説明しますね。

バインディングとパフォーマンス

Viewが出来たり消えたりするということと、ViewModelとViewはBindingでくっつくということは分かったかと思いますが、Bindingで参照可能なのはView→ViewModelの一方通行だけです。どうしてこんなことになっているのでしょうか?

例えば、ViewModelにBindingControlsプロパティか何かを用意して、Viewがバインドされたらここに参照を追加、バインド解除されたらここから参照を削除という方法を考えてみましょう。(View->ViewModelは多数張ることができますので、必然的にコレクションとなります。)これならViewModelからもバインドされているViewにアクセス可能です。なぜWPFはこのような実装をしなかったのでしょう。

先述した通り、Viewはめまぐるしい勢いで生成され、削除されます。生成されるたびに通知、削除されるたびに通知、しかもバインディングの数だけ。パフォーマンスは期待できません。すべてのWPFアプリケーションの足枷となってしまいます。そもそも、Viewの削除はGCによって行われていると推測できます。ファイナライザスレッドを使ってこの処理を行わなければいけないのは、パフォーマンスに重大な悪影響を与えます。そのため、ただ単に参照を保持するだけで済む View->ViewModelの一方通行となっています。これならパフォーマンスを高く保つことができます。

メモリリーク

View←ViewModelの参照は持つことが基本的にできません。しかし、どうしてもViewに情報を渡さなければいけない局面がいくつか存在します。たとえば、特定のコントロールにフォーカスを遷移する時。たとえば、メッセージボックスを出す時。たとえば、ダイアログを出して画面を遷移する時。これはどのようにすれば解決できるでしょうか。

コードビハインドでDataContextの変更をリッスンし、変更されたらDataContextにViewを渡す。場合によってはうまく行くでしょう。しかし、Viewの寿命は一般的にViewModelより短いです。このとき、どのようなことが起こるでしょう?

Viewが破棄されると、ガベージコレクションの対象となります。しかし、ViewのインスタンスはまだViewModelから参照されている状態になります。これでは、ガベージコレクションは行えません。

この現象は、ViewがViewModelのイベントをリッスンしている時も同じです。イベントはデリゲートのコレクションを保持し、発行された時に通知して回ります。デリゲートはViewのインスタンスを保持していますから、結局Viewのガベージコレクションが行えません。先述した「コードビハインドをViewModelとして使う」という解決策の問題点もまた同じです。コードビハインドは実行時にViewと結合されます。ViewModelへの参照がそのままViewの参照と同一となり、結局メモリリークを引き起こします。

コレクションされるべきViewがいつまでもコレクションされずメモリを圧迫し続ける、これはまさにメモリリークです。この現象の根本的な原因は、寿命の短いViewの参照をViewより寿命が長いViewModelが持っているという点にあります。

知識がある方なら、WeakReferenceによって解決することを思いつくでしょう。しかし、全てのView←ViewModel参照においてWeakReferenceを間違いなく実装するのは骨が折れます。そこで、各MVVMインフラストラクチャの出番となります。多くのMVVMインフラストラクチャは、この機能を実装しています。それも、簡単に実装が行える形で。たとえば、ViewModelの中にメッセンジャーとよばれる機構を組み込んだり。この機能についての詳細は各インフラストラクチャのヘルプを参照してください。

なぜ「メッセンジャー」を用いるのか。それは、View←ViewModelの参照が持てないから、です。

統一された手法を用いる意味

MVVMパターンでインフラストラクチャを用いる利点について説明しましたが、これらは場所が限定的であれば、例示したような解決策でもうまく行きます。たとえば、アプリケーションの開始から終了まで一貫して破棄されないViewであれば、メモリリークの問題は発生しません。

ですが、「このViewは破棄されるからMessenger」「このViewは破棄されないからイベント」というように一々考慮していてはバグの温床となりますし、メンテナンス性も大きく下がります。それならば、どこでも統一された、安全な手法を用いてアプリケーションを構築した方が良いと思いませんか?メンテナンスもしやすく、バグも生み出しにくくなります。

さぁ、WPF With MVVM!

INotifyPropertyChangedの働きだとかまだいろいろありますが、後は使ってみれば分かると思います。というか、別の人が詳しくまとめてると思います!

throw new 筆();

vsi ファイルがインストールできないとき

なんだかよくわからないけど、

System.ArgumentNullException: 値を Null にすることはできません。
パラメータ名: value
   場所 System.Windows.Forms.ImageList.ImageCollection.Add(Image value)
   場所 VSContentInstaller.SnippetLanguagePage.SnippetLanguagePage_Load_1(Object sender, EventArgs e)
   場所 System.Windows.Forms.UserControl.OnLoad(EventArgs e)
   場所 System.Windows.Forms.UserControl.OnCreateControl()
   場所 System.Windows.Forms.Control.CreateControl(Boolean fIgnoreVisible)
   場所 System.Windows.Forms.Control.CreateControl(Boolean fIgnoreVisible)
   場所 System.Windows.Forms.Control.CreateControl()
   場所 System.Windows.Forms.Control.ControlCollection.Add(Control value)
   場所 Microsoft.WizardFramework.WizardForm.ActivatePage(WizardPage page)
   場所 Microsoft.WizardFramework.WizardPage.Activate()
   場所 Microsoft.WizardFramework.WizardForm.OnNext()
   場所 Microsoft.WizardFramework.WizardForm.OnNextClicked(Object sender, EventArgs e)
   場所 Microsoft.WizardFramework.NavigationButtonBar.OnNext(Object sender, EventArgs e)
   場所 System.Windows.Forms.Control.OnClick(EventArgs e)
   場所 System.Windows.Forms.Button.OnClick(EventArgs e)
   場所 System.Windows.Forms.Button.OnMouseUp(MouseEventArgs mevent)
   場所 System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks)
   場所 System.Windows.Forms.Control.WndProc(Message& m)
   場所 System.Windows.Forms.ButtonBase.WndProc(Message& m)
   場所 System.Windows.Forms.Button.WndProc(Message& m)
   場所 System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
   場所 System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
   場所 System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)

みたいなこと言われてvsiファイルがインストールできない時の対処のおはなし。

 

あなたもしかして、Visual Studio Express for Windows Phone インストールしましたね!!!!

 

はい、というわけで次のステップに従ってください。あ、当然自己責任でお願いしますね☆

ソースはこちら: https://connect.microsoft.com/VisualStudioJapan/feedback/details/652543/vs-express-for-windowsphone-vsi

  1. レジストリエディタを開きます。
  2. 使っているWindowsが32bitか64bitかをおもむろに調べます。
    1. 32bitのとき
      HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSEnvCommunityContent\ContentTypes\Code Snippet\ContentHosts\1.0\Visual Studio 2010 Express for Windows Phone
      を開きます。
    2. 64bitのとき
      HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\MSEnvCommunityContent\ContentTypes\Code Snippet\ContentHosts\1.0\Visual Studio 2010 Express for Windows Phone
      を開きます。
  3. ApplicationIconに何か値が入っていますが、そこを空にします。

はい、お疲れ様でした。

C#からUser Streamsに多重接続する時の注意点

User Streams 超便利ですね。これに対応していないTwitterクライアントとか考えられません。

ところで、クライアントの中には複数のアカウントが利用できるものがあります。折角なので、User Streamsは何本も張って使いたいですよね。(事実、TwitterのImplementation Suggestionsにも「複数のアカウントを扱う場合は複数のストリームを扱えるようにせいって書かれてます-> http://dev.twitter.com/pages/user_streams_suggestions )

そこで、何本も張ろうとすると、あることに気づきます。

3本目以降のストリームが必ずタイムアウトする。

これ、バグでもなんでもなく、ただ単にHTTP 1.1の仕様なんです。

Clients that use persistent connections SHOULD limit the number of
simultaneous connections that they maintain to a given server. A
single-user client SHOULD NOT maintain more than 2 connections with
any server or proxy. A proxy SHOULD use up to 2*N connections to
another server or proxy, where N is the number of simultaneously
active users. These guidelines are intended to improve HTTP response
times and avoid congestion.
http://www.ietf.org/rfc/rfc2616.txt

でも、そんなこと言ってる場合じゃないですね。Twitterは別なんです。廃人さんのサティスファクションを得るには、こんな制限そげぶしなければなりません。

で、ちょっと調べると、INTERNET_OPTION_MAX_CONNS_PER_SERVERとかSetInternetOptionsとかレジストリとか出てきて萎えますが、もちろんそんな手段に頼らなくても済みます。

こちらをご覧ください。

http://msdn.microsoft.com/ja-JP/library/system.net.servicepointmanager.defaultconnectionlimit.aspx

ServicePoint オブジェクトで許可される同時接続の最大数を取得または設定します。

まんまですね。

要するに、

ServicePointManger.DefaultConnectionLimit = (同時接続数上限);

をぶち込んでやれば解決です。たぶん、

ServicePointManager.Expect100Continue = false

って書いてるはずなので、その上か下あたりにでも書いとけばいいでしょう。

って感じのトラブルシューティング。たった1行だけど、僕とKrileには大きな進歩だったりします。

VisualTreeHelperのHitTestは、割と適当。

VisualTreeHelperのHitTestは、Collapsedとかあんまり考慮しないみたいです。

流石に作成時からCollapsedな要素はヒットしませんが、そうじゃないときは最大値に対してヒットしてるような。

位置に関しては正確なので、うまいこと処理してやる必要があります。StackPanelとかWrapPanelだったら、最後にヒットした要素を採用する、とか。

 

小一時間はまったのでメモ。

DependencyObjectの親ウィンドウを取得したい

UserControlとかが配置されてるWindowを取得したいって時、たまーにあったりします。

そんなときは、

Window Window.GetWindow(DependencyObject dependencyObject);

で、いけるっぽいです。( http://msdn.microsoft.com/ja-jp/library/system.windows.window.getwindow(VS.80).aspx )

わざわざVisualTreeHelperとかLogicalTreeHelperとか使ってたよママン…

UACで昇格 by manifest in C#

アプリケーションの実行時にsuが欲しいときは、app.exe.manifestを使うといいらしい。

from http://blogs.msdn.com/tsmatsuz/archive/2006/11/01/windows-vista-uac.aspx

以下コピペ。WindowsApplication1は変更すること。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
   <assemblyIdentity version="1.0.0.0" processorArchitecture="X86" name="WindowsApplication1" type="win32"/>
      <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
      <security>
         <requestedPrivileges>
            <requestedExecutionLevel level="requireAdministrator"/>
         </requestedPrivileges>
      </security>
   </trustInfo>
</assembly>

 

で、VS2005とかだとあれこれしなきゃいけない。けど、VS2008からはもっと楽な方法がある。

①とりあえず作ったapp.exe.manifestをプロジェクトに参加させる

②プロジェクトのプロパティ画面を開く

③アイコンとマニフェスト→マニフェスト で app.exe.manifestを選択

以上。楽ですね。