かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

WPFで画像表示時にファイルをロックしないようにしたい

ネタ元

ListBox にイメージをデータバインドした時に画像を削除する方法

確かにWPFでImageのSourceにstringをBindingしたときは、ファイルがしばらくロックされちゃって困ることがあります。これをロックされないようにするには、自前のコンバーターでファイルロックをしないようにする必要があります。

ロックされる版のアプリ

以下のようにピクチャーフォルダのjpgへのパスをDataContextにつっこみます。

using System;
using System.IO;
using System.Linq;
using System.Windows;

namespace ImageApp
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            var picturesFolderPath = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures);
            this.DataContext = Directory.GetFiles(picturesFolderPath, "*.jpg", SearchOption.AllDirectories)
                .ToArray();
        }
    }
}

そして、それをListBoxのItemsSourceにバインドしてItemTemplateでImageのSourceにバインドします。

<Window x:Class="ImageApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:ImageApp"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <ListBox ItemsSource="{Binding}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Image Source="{Binding}" Height="50" />
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</Window>

実行すると以下のように表示されます。

f:id:okazuki:20150620121402p:plain

このときファイルを消そうとすると以下のようにロックされてることが確認できます。

f:id:okazuki:20150620121537p:plain

ロックされないようにする

デフォルトのコンバーターがダメなので自前コンバーターを仕込みます。

using System;
using System.Globalization;
using System.IO;
using System.Windows.Data;
using System.Windows.Media.Imaging;

namespace ImageApp
{
    public class ImageConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var path = (string)value;
            using (var fs = new FileStream(path, FileMode.Open))
            {
                var decoder = BitmapDecoder.Create(
                    fs,
                    BitmapCreateOptions.None,
                    BitmapCacheOption.OnLoad);
                var bmp = new WriteableBitmap(decoder.Frames[0]);
                bmp.Freeze();
                return bmp;
            }
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

参考: http://pieceofnostalgy.blogspot.jp/2012/02/wpf-bitmapsource.html

あとは、このConverterをImageのSourceに設定します。

<Window x:Class="ImageApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:ImageApp"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <local:ImageConverter x:Key="ImageConverter"/>
    </Window.Resources>
    <Grid>
        <ListBox ItemsSource="{Binding}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Image Source="{Binding Converter={StaticResource ImageConverter}}" Height="50" />
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</Window>

実行するとちゃんと画像が表示されて、画像の削除もできるようになります。