🏗️
WPFのViewModelをデザイン用と本番用に分離する
ListViewの表示速度確認用の画面として、下記のような c:\test にある画像をすべて抽出・表示するコードを作成しました。
対象フォルダには3〜5MB程度の画像が約500枚あり、表示にはそれなりに時間がかかります。
しかし、XAMLデザイナーを起動するたびに重い ViewModel を読み込んでしまうため、開発時に非常にストレスになります…!
MainWindow.xaml
<Window x:Class="WPFListViewTest.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:WPFListViewTest.ViewModels"
Title="Image Viewer" Height="450" Width="800">
<Window.DataContext>
<vm:ImageListViewModel />
</Window.DataContext>
<Grid>
<ListView ItemsSource="{Binding Images}"
SelectionMode="Single"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Margin="5">
<Image Source="{Binding Image}" Width="100" Height="100"/>
<TextBlock Text="{Binding FileName}" HorizontalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Window>
ImageListViewModel.cs
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Windows.Media.Imaging;
namespace WPFListViewTest.ViewModels;
public class ImageListViewModel
{
public ObservableCollection<ImageItem> Images { get; } = new();
public ImageListViewModel()
{
Initialize();
}
public void Initialize()
{
string folder = @"c:\test";
if (Directory.Exists(folder))
{
var files = Directory.GetFiles(folder, "*.*")
.Where(f => f.EndsWith(".jpg") || f.EndsWith(".png") || f.EndsWith(".bmp"));
foreach (var file in files)
{
Images.Add(
new ImageItem
{
FilePath = file,
FileName = Path.GetFileName(file),
Image = LoadImage(file)
});
}
}
}
private static BitmapSource LoadImage(string path)
{
var bytes = File.ReadAllBytes(path);
using var ms = new MemoryStream(bytes);
var image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.StreamSource = ms;
image.EndInit();
image.Freeze();
return image;
}
}
public class ImageItem
{
public string FilePath { get; set; } = string.Empty;
public string FileName { get; set; } = string.Empty;
public BitmapSource? Image { get; set; }
}
1. 軽量な ViewModel を用意する
デザイナーの表示が重くなる問題を解消するため、軽量なダミー画像を表示する MockImageListViewModel を作成します。
MockImageListViewModel.cs
using System;
using System.Collections.ObjectModel;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace WPFListViewTest.ViewModels;
public class MockImageListViewModel
{
public ObservableCollection<ImageItem> Images { get; } = new();
public MockImageListViewModel()
{
Initialize();
}
private unsafe static BitmapSource CreateBitmap(int width, int height, uint color)
{
var wb = new WriteableBitmap(width, height, 96, 96, PixelFormats.Bgra32, null);
wb.Lock();
var span = new Span<uint>((void*)wb.BackBuffer, width * height);
span.Fill(color);
wb.Unlock();
wb.Freeze();
return wb;
}
public void Initialize()
{
const int count = 10;
for (int i = 0; i < count; i++)
{
Images.Add(
new ImageItem
{
FileName = $"image{i}.png",
Image = CreateBitmap(100, 100, (uint)Random.Shared.Next() | 0xff000000)
});
}
}
}
2. XAML に軽量ViewModelを読み込む記述を追加
デザイナー上で MockImageListViewModel が表示されるようになります。 ただし、この時点では ImageListViewModel のインスタンスは依然として作成されてしまうため、まだ動作自体は軽量化されていません。
MainWindow.xaml
<Window x:Class="WPFListViewTest.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:WPFListViewTest.ViewModels"
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ mc:Ignorable="d"
Title="Image Viewer" Height="450" Width="800">
<Window.DataContext>
<vm:ImageListViewModel />
</Window.DataContext>
+ <d:DesignProperties.DataContext>
+ <vm:MockImageListViewModel />
+ </d:DesignProperties.DataContext>
(以下略)
3. デザイナー起動時の判定処理を追加
デザイナーから起動された場合に ImageListViewModel の初期化処理をスキップするため、デザイナーかどうかを判定するユーティリティを作成します。
WPFUtility.cs
using System.ComponentModel;
using System.Windows;
public class WPFUtility
{
public static bool IsDesignMode => (bool)DesignerProperties
.IsInDesignModeProperty
.GetMetadata(typeof(DependencyObject))
.DefaultValue;
}
4. 元のViewModelの初期化処理を変更
WPFUtility.IsDesignMode == true の時は、初期化処理をスキップするようにします。
ImageListViewModel.cs
public ImageListViewModel()
{
+ if (WPFUtility.IsDesignMode)
+ return;
Initialize();
}
これで、デザイナー上では重い初期化処理が実行されず、快適に編集できるようになります! 🎉
もっと楽なやり方があれば、是非教えてください。
実行結果
-
デザイナーでのウィンドウ表示

-
実行時のウィンドウ表示

Discussion