かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

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>

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