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

  • 質問

  • お世話になります。

    下記のコードで Model を使用し画像ファイルを読んでいます。

    listBox(A)からlistBoxChoice(B)にDrag&Drop でファイルを移動(各A,Bは別フォルダー内の画像を表示)して同じファイル名なら上書きコピーしようと思います。

    Modelでファイル名をもらって下記のように表示していますので、どっかのデバイスコンテキストのようなものが、握っていてファイルを上書きコピーできないようです。

    エラーメッセージは、「別のプロセスで使用されているため、プロセスはファイル 'C:\Users\SL_7\Documents\....temp002.jpg' にアクセスできません。」です。

    <DockPanel>
                        <ListBox DockPanel.Dock="Top" Name="listBoxChoice"
                             Style="{StaticResource listBoxThumbnailStyle}"
                             IsSynchronizedWithCurrentItem="True" 
                             ItemsSource="{Binding saveImages}" 
                             HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
                                 PreviewMouseLeftButtonDown="ListBox_PreviewMouseLeftButtonDown" AllowDrop="True"
                                 DragOver="ListBox_DragOver" Drop="ListBox_Drop">
                        </ListBox>
                        <ScrollViewer Name="scrollViewer1" VerticalScrollBarVisibility = "Auto">  <!-- StackPanelだとListBoxに縦スクロールバーが出なかった DockPanelでOK! -->
                            <ListBox DockPanel.Dock="Top" SizeChanged="OnListBoxSizeChanged" Name="listBox"
                                 Style="{StaticResource listBoxThumbnailStyle}"
                                 IsSynchronizedWithCurrentItem="True"
                                 ContextMenu="{DynamicResource menuListBox}" 
                                 ItemsSource="{Binding Images}"  VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
                                     PreviewMouseLeftButtonDown="ListBox_PreviewMouseLeftButtonDown" AllowDrop="True" 
                                     PreviewMouseMove="listBox_PreviewMouseMove" QueryContinueDrag="listBoxItem_QueryContinueDrag"
                                     DragOver="ListBox_DragOver">
                                <ListBox.ItemsPanel>
                                    <ItemsPanelTemplate>
                                        <WrapPanel/>
                                    </ItemsPanelTemplate>
                                </ListBox.ItemsPanel>
                            </ListBox>
                        </ScrollViewer>
                    </DockPanel>

    で、調べると「https://social.msdn.microsoft.com/Forums/ja-JP/56e8a6ad-4085-499c-9e26-262650fb5cec/wpf」が出てきてどうやら解決策のようですが、やり方がわかりません。

    •  WPF では BitmapSource が IDisposable ではないので、「削除するために画像を破棄する」ではなく、「削除できるよう画像作成時にファイルから切り離す」という発想で行くべきでしょう。

    実際、どのようにするのですか?

    よろしくお願いします。

    Windows 7/8.1 VS2010 WPF C#

    2015年6月19日 4:11

回答

  • BitmapDecoderから、WritableBitmapを使って画像に変換する手順が抜けてたみたいです。

    以下のようにやったらできました。

    http://okazuki.hatenablog.com/entry/2015/06/20/122427


    かずき Blog:http://d.hatena.ne.jp/okazuki/

    • 回答としてマーク ferret001 2015年6月21日 5:53
    2015年6月20日 3:27
  • Resourcesに設定しているだけで、DataContextに設定している箇所が見当たりませんね。

    また、ListBoxのItemsSourceは{Binding}だそうですが、これはコレクションがバインドされていますか? Image.SourceのBindingもPathが設定されていないようですが。

    というか、そもそもこのスレッドを立てた時点で、削除できない以外は機能を実現できていたんですよね? それのImage.SourceのBindingにConverterを追加するだけの話だったはずなのが、なぜこんな後退しているのでしょうか。

    • 回答としてマーク ferret001 2015年6月21日 5:53
    2015年6月20日 22:30

すべての返信

  • リンク先で具体案を挙げて説明してると思うのですが、どこが分からなかったでしょうか。

    2015年6月19日 4:17
  • コード例がほしかったとか。
    こんな感じですかねぇ?

    using System;
    using System.IO;
    using System.Windows.Data;
    using System.Windows.Media.Imaging;
    
    namespace WpfApplication10
    {
        public class BmpConverter : IValueConverter
        {
            public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                var path = (string)value;
                using (var s = new FileStream(path, FileMode.Open))
                {
                    return BitmapDecoder.Create(
                        s,
                        BitmapCreateOptions.None,
                        BitmapCacheOption.OnLoad);
                }
            }
    
            public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                throw new NotImplementedException();
            }
        }
    }
    
    未検証なので、動作結果を教えてくれるとうれしいです。

    かずき Blog:http://d.hatena.ne.jp/okazuki/

    2015年6月19日 4:49
  • 勉強になります。

    Decoder自体だとどうやらロックされているようなので
    もしかしてフレームを返す必要がありますかね?

    using (var s = new FileStream(path, FileMode.Open))
    {
        var dec = BitmapDecoder.Create(
            s,
            BitmapCreateOptions.None,
            BitmapCacheOption.OnLoad);
        return dec.Frames[0];
    }
    ※私のファイルロックの確認タイミングが間違ってるかもしれませんが


    2015年6月19日 5:02
    モデレータ
  • BitmapDecoderから、WritableBitmapを使って画像に変換する手順が抜けてたみたいです。

    以下のようにやったらできました。

    http://okazuki.hatenablog.com/entry/2015/06/20/122427


    かずき Blog:http://d.hatena.ne.jp/okazuki/

    • 回答としてマーク ferret001 2015年6月21日 5:53
    2015年6月20日 3:27
  • かずき_okazuki 様、Tak1wa様、Hongliang様、いつものことながら、お世話になります。

    コードの提示ありがとうございます。

    Model からのデータの受け取りなどの部分をわかっていないようで上記の単純な場合はうまくいってましたが、提示の件では、Model変数がヌルになり受け取れません。現在のコードは下記のようになっています。

    MainWindow.xaml
    <Window.Resources>
            <local:ImageConverter x:Key="ImageConverter" />
            <local:TempImagesModel x:Key="imgData"/>
    </Window.Resources>
      ↑↑↑ここが正しいのかわかりません
    
    ↓↓↓この部分は、最上部のコードを変更しました
    <ListBox ItemsSource="{Binding}" DockPanel.Dock="Top" x:Name="listBoxChoice"
             Style="{StaticResource listBoxThumbnailStyle}"
             IsSynchronizedWithCurrentItem="True" 
             HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
             PreviewMouseLeftButtonDown="ListBox_PreviewMouseLeftButtonDown" AllowDrop="True"
             DragOver="ListBox_DragOver" Drop="ListBox_Drop">
                 <ListBox.ItemTemplate>
                     <DataTemplate>
                           <Image Source="{Binding Converter={StaticResource ImageConverter}}" Height="50" />
                     </DataTemplate>
                 </ListBox.ItemTemplate>
    </ListBox>
    
    Modelのコード
    namespace WpfCameraTest3
    {
        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();
            }
        }
    
        public class TempImagesModel : INotifyPropertyChanged
        {
            #region コンストラクタ
            public TempImagesModel()
            {
                // OnPropertyChangedでnullチェックするのがめんどいので
                // 空の処理をあらかじめ1つ追加しておく。
                PropertyChanged += (sender, e) => { };
            }
            #endregion
    
            #region プロパティ
            private IList<ImageInfo> _saveImages;
    
            private string[] _supportExts = { ".jpg", ".bmp", ".png", ".tiff", ".gif" };
    
            public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                throw new NotImplementedException();
            }
            /// <summary>
            /// ビューワーで表示する画像の情報を取得または設定します。
            /// </summary>
    
            public IList<ImageInfo> saveImages
            {
                get
                {
                    return _saveImages;
                }
                set
                {
                    _saveImages = value;
                    OnPropertyChanged("saveImages");
                }
            }
    
            #endregion
    
            #region 公開メソッド
    
            public void SaveFiles(string path)
            {
                if (path.Length == 0)
                    return;
    
    
                // 撮影ホルダー内表示  
                this.saveImages = ImageUtils.GetImages(path, _supportExts);
            }
    
            #endregion
    
            #region INotifyPropertyChanged メンバ
    
            public event PropertyChangedEventHandler PropertyChanged;
            protected virtual void OnPropertyChanged(string name)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(name));
            }
    
        }
    }
    
    
    MainWindow.xaml.cs
    
    public TempImagesModel Model
    {
         get { return DataContext as TempImagesModel; }
    }
    
    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
    	:
    	this.Model.SaveFiles(読み込むパス);
    

    2015年6月20日 12:52
  • Resourcesに設定しているだけで、DataContextに設定している箇所が見当たりませんね。

    また、ListBoxのItemsSourceは{Binding}だそうですが、これはコレクションがバインドされていますか? Image.SourceのBindingもPathが設定されていないようですが。

    というか、そもそもこのスレッドを立てた時点で、削除できない以外は機能を実現できていたんですよね? それのImage.SourceのBindingにConverterを追加するだけの話だったはずなのが、なぜこんな後退しているのでしょうか。

    • 回答としてマーク ferret001 2015年6月21日 5:53
    2015年6月20日 22:30
  • お世話になります。

     <ListBox ItemsSource="{Binding saveImages}" DockPanel.Dock="Top" x:Name="listBoxChoice"
    ここにバインドする変数(saveImages)がなかったのを追加し、


    <Window.Resources>
     ①<local:ImageConverter x:Key="ImageConverter" />
     ②<local:ImageConverter x:Key="imgData" /> // <- 根拠なくWindow.DataContextでなく、こうする?と思った
    </Window.Resources>

    上記の下の行②を削除し下記に戻しました。上記の①と下記がそれぞれ必要だったんですね。
        <Window.DataContext>
            <local:TempImagesModel />
        </Window.DataContext>

    > そもそもこのスレッドを立てた時点で、削除できない以外は機能を実現できていたんですよね?

    > それのImage.SourceのBindingにConverterを追加するだけの話だったはずなのが

    「Converterを追加するだけ」だと思わず、よからぬ方向に向かっていたようです。

    無事、上書きコピーできるようになりました。ありがとうございます。

    2015年6月21日 5:53