TL;DR;
<UserControl...>
...
<UserControl.DataContext><local:MockData/></UserControl.DataContext>
...
</UserControl>
これで利用側は表示され続けます。
しかし、UserControl側はリビルドすると画像だけ消えます。
やりたかったこと
WPFやUWPの利点の一つにXAMLでUIが描けること、そしてVisualStudioではXAMLのデザイナー(グラフィカルエディタ)が搭載されていることがあります。
折角そんな素晴らしい機能があるので、UIの見た目をリアルタイムで確認しながらXAMLを書きたいものです。
しかし、StackPanel
などの自動レイアウトを使っていると、画像が読み込まれているときとそうでないときでImage
コントロールのサイズが変わってしまい、デザイナー上での見た目が潰れて大変悲しいことになってしまいます。
これを何とかするために、実行時にBindする予定の部分にも何かしらダミーの画像を当てておいて、ある程度のサイズを確保してくれるようにしたかったのです。
駄目だったパターン
FallbackValue
、TargetNullValue
について
「xaml Default Designer」みたいなノリで検索すると、Binding
にBindingBase.FallbackValue
やBindingBase.TargetNullValue
を設定すればいいよ!といった感じの情報が見つかりました。
しかし、TextBlock.Text
などの場合はうまくいくのですが、Image.Source
については描いた瞬間は表示されるものの暫く経つor利用側のデザイナーで表示がされなくなってしまいます。
はじめは、Resourcesから供給される画像がBitmap
なのとImage.Source
がImageSource
の差の問題かとも思ったのですが、適切なConverterをかませても、暫く経つと表示されなくなってしまっていました。
<ImageSource="{
Binding img,
FallbackValue={StaticResource prop.Resources.fallback_image_icon},
Converter={StaticResource local:DefaultImageWhenNull}}"/><!-- DefaultImageWhenNullは [ValueConversion(typeof(Bitmap), typeof(ImageSource))] なValueConverter -->
Converterで入力がnullの時にデフォルトの画像を返す方法
OSSの実装例をあさっていたところ、以下のようにConverterでデフォルト画像を指定しているところがありました。
ということでこれも試してみたのですが、結果はDataContext
がBindされるまでConverterも動かない(FallbackValue
がついていても動かない)ということで駄目でした。
実際、元PJを開いてみてもそもそもデザイナ自体表示できなくなっていたので、私のやりたかったことの答えではない意図っぽいです。
たぶん実行時のnullヨケですね。
[ValueConversion(typeof(ImageSource),typeof(ImageSource))]publicclassImageSourceToThumbnailConverter:IValueConverter{staticreadonlyImageSource_defaultThumbnail=MainWindow.Current.Resources["thumbnail_default"]asImageSource;publicobjectConvert(objectvalue,TypetargetType,objectparameter,CultureInfoculture)=>value??_defaultThumbnail;...}
d:DataContext
を設定する。
UserControl側
<UserControl...d:DataContext="{
d:DesignInstance local:MockData,
IsDesignTimeCreatable=True}">
...
<ImageSource="{Binding img}"/>
...
</UserControl>
...publicclassMockData{// 返す値はお好みで// 今回はプロジェクトプロパティのResource画像からpublicImageSourceimg{get;}=Imaging.CreateBitmapSourceFromHBitmap(Properties.Resources.fallback_image_icon.GetHbitmap(),IntPtr.Zero,Int32Rect.Empty,BitmapSizeOptions.FromEmptyOptions());}
利用側
何も考えずに取り込めばOK。
<Window...>
...
<local:PageItem/><local:PageItem/><local:PageItem/>
...
</Window>
……と思っていたのですが、リビルドをかけた瞬間画像が吹っ飛びました。
どうも、時間が経つと消えるのではなく、リビルドが走ると消えるようです。
というのもリビルドしても、なぜかDataContextに入っているインスタンスがリロードされていない、つまりリビルド以前のインスタンスが入っています。
本当かよって感じですが、GetHashCode
をTextBlockにダンプしてみたところ、値は変わりませんでした。
ここからは推測ですが、リビルドをしてアセンブリは切り替わりそれまでのメモリも解放されているのだと思います。
しかし、Image側はそれを握り続けているため解放済みの無効な画像を表示しようとしてもできないくなっていたのではないかと思います。
現に、ImageSourceもダンプしてみましたが、リビルドしてもnullにはなっていませんでした。
意味不明ですね。
参考
Default value at design time XAML(Stackoverflow)
Ito Mitsuhiro/NeeView ImageSourceToThumbnailConverter.cs(BitBucket)
XAMLデザイナ専用ViewModelコンストラクタの作り方(Qiita)