質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

WPF

Windows Presentation Foundation (WPF) は、魅力的な外観のユーザー エクスペリエンスを持つ Windows クライアント アプリケーションを作成するための次世代プレゼンテーション システムです

Q&A

解決済

3回答

11534閲覧

WPFで画像表示時に例外エラー

lain

総合スコア161

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

WPF

Windows Presentation Foundation (WPF) は、魅力的な外観のユーザー エクスペリエンスを持つ Windows クライアント アプリケーションを作成するための次世代プレゼンテーション システムです

0グッド

0クリップ

投稿2016/10/26 08:06

###前提・実現したいこと
WPFで画像閲覧ソフトを作成しています。
ListBox + UniformGridを使用し、指定された行・列数で分割し一覧そこに画像を順番に表示するものです。

###発生している問題・エラーメッセージ

「進む」ボタンを押すことにより、指定数分シフトして再表示しようとすると、
ImageConverterクラスの
var bmp = new WriteableBitmap(decoder.Frames[0]);で

System.Runtime.InteropServices.COMException が発生しました。 ErrorCode=-2003304445 HResult=-2003304445 Message=HRESULT からの例外:0x88980003 Source=PresentationCore StackTrace: 場所 System.Windows.Media.Imaging.WriteableBitmap.InitFromBitmapSource(BitmapSource source)

例外を無視してそのまま進めると、
var decoder = BitmapDecoder.Createメソッドのとこで、

System.OutOfMemoryException が発生しました。 HResult=-2147024882 Message=プログラムの実行を続行するための十分なメモリがありませんでした。 Source=PresentationCore StackTrace: 場所 System.Windows.Media.Imaging.BitmapSource.CreateCachedBitmap(BitmapFrame frame, BitmapSourceSafeMILHandle wicSource, BitmapCreateOptions createOptions, BitmapCacheOption cacheOption, BitmapPalette palette) InnerException:

が起こってしまいます。

メモリの解放がうまくいってないとは思うのですが、どこでどうしたら良いのか分からず煮詰まっています。
(根本的に考え方がおかしいのか?)
手掛かりになるようなヒントでも良いのでお知恵をお貸しください。
宜しくお願いいたします。

###該当のソースコード

XAML

1<Window x:Name="window" x:Class="WpfApplication19.MainWindow" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 5 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 6 xmlns:local="clr-namespace:WpfApplication19" 7 mc:Ignorable="d" 8 Title="MainWindow" Height="600" Width="800"> 9 10 <Window.Resources> 11 <local:ImageConverter x:Key="ImageConverter"/> 12 </Window.Resources> 13 14 <Window.DataContext> 15 <local:MainWindowViewModel/> 16 </Window.DataContext> 17 18 <DockPanel> 19 <Button x:Name="NextButton" DockPanel.Dock="Top" Content="進む" Click="NextButton_Click" /> 20 21 <ListBox x:Name="listBox" ItemsSource="{Binding DisplayFileNameList}" ScrollViewer.HorizontalScrollBarVisibility="Hidden" ScrollViewer.VerticalScrollBarVisibility="Hidden"> 22 <ListBox.ItemsPanel> 23 <ItemsPanelTemplate> 24 <UniformGrid Rows="{Binding SelectedRow}" Columns="{Binding SelectedCol}" 25 Width="{Binding ActualWidth, ElementName=listBox, Mode=OneWay}" 26 Height="{Binding ActualHeight, ElementName=listBox, Mode=OneWay}" /> 27 </ItemsPanelTemplate> 28 </ListBox.ItemsPanel> 29 <ListBox.ItemTemplate> 30 <DataTemplate> 31 <Border BorderThickness="1" BorderBrush="Black" HorizontalAlignment="Center" VerticalAlignment="Center"> 32 <DockPanel> 33 <TextBlock DockPanel.Dock="Bottom" Text="{Binding FileDateTIme}" /> 34 <Image Source="{Binding FullFileName, Converter={StaticResource ImageConverter}}" /> 35 </DockPanel> 36 </Border> 37 </DataTemplate> 38 </ListBox.ItemTemplate> 39 </ListBox> 40 41 </DockPanel> 42</Window> 43 44

C#

1 2public partial class MainWindow : Window 3{ 4 private MainWindowViewModel _viewModel = null; 5 private int startIndex = 0; 6 7 public MainWindow() 8 { 9 InitializeComponent(); 10 11 _viewModel = this.DataContext as MainWindowViewModel; 12 string pathName = @"E:\ImageFolder"; 13 foreach (string f in Directory.EnumerateFiles(pathName, "*.jpg", SearchOption.TopDirectoryOnly)) 14 { 15 _viewModel.FileNameList.Add(new ImageFileInfo() 16 { 17 FullFileName = f, 18 FileDateTIme = File.GetLastAccessTime(f) 19 }); 20 } 21 _viewModel.SetDisplayList(startIndex); 22 } 23 24 private void NextButton_Click(object sender, RoutedEventArgs e) 25 { 26 _viewModel.SetDisplayList(++startIndex); 27 } 28} 29 30public class MainWindowViewModel : INotifyPropertyChanged 31{ 32 public event PropertyChangedEventHandler PropertyChanged; 33 protected virtual void RaisePropertyChanged([CallerMemberName]string propertyName = null) 34 { 35 if (PropertyChanged != null) 36 PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); 37 } 38 39 public int SelectedRow { get { return 5; } } 40 public int SelectedCol { get { return 5; } } 41 42 private List<ImageFileInfo> _fileNameList = new List<ImageFileInfo>(); 43 public List<ImageFileInfo> FileNameList 44 { 45 get { return _fileNameList; } 46 set 47 { 48 _fileNameList = value; 49 RaisePropertyChanged(); 50 } 51 } 52 53 private IList<ImageFileInfo> _displayFileNameList = new ObservableCollection<ImageFileInfo>(); 54 public IList<ImageFileInfo> DisplayFileNameList 55 { 56 get { return _displayFileNameList; } 57 set 58 { 59 _displayFileNameList = value; 60 RaisePropertyChanged(); 61 } 62 } 63 64 public void SetDisplayList(int startIndex) 65 { 66 _displayFileNameList.Clear(); 67 int max = startIndex + SelectedCol * SelectedRow; 68 69 for(int i=startIndex; i<max; i++) 70 { 71 DisplayFileNameList.Add(FileNameList.ElementAtOrDefault(i)); 72 } 73 } 74} 75 76public class ImageFileInfo 77{ 78 public string FullFileName { get; set; } 79 public DateTime FileDateTIme { get; set; } 80} 81 82public class ImageConverter : IValueConverter 83{ 84 public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 85 { 86 var path = (string)value; 87 88 try 89 { 90 using (var fs = new FileStream(path, FileMode.Open)) 91 { 92 var decoder = BitmapDecoder.Create( 93 fs, 94 BitmapCreateOptions.None, 95 BitmapCacheOption.OnLoad); 96 var bmp = new WriteableBitmap(decoder.Frames[0]); 97 bmp.Freeze(); 98 return bmp; 99 } 100 } 101 catch (Exception ex) 102 { 103 Console.WriteLine(ex.Message); 104 return null; 105 } 106 } 107 108 public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 109 { 110 throw new NotImplementedException(); 111 } 112} 113

###補足情報(言語/FW/ツール等のバージョンなど)
Visual Studio 2015
C# WPF
.NET Framework 4.5.2

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

himakuma

2016/10/26 08:23

型を指定せずにvarを使用しているのはなぜですか?
himakuma

2016/10/26 08:26

ファイルサイズ、ファイル数を教えてください
lain

2016/10/26 08:41

varを使っているのは、冗長なコードになるので簡略化のためです。いま私がテストで使用しているデータのファイル数は2000件ファイルサイズは1M前後です。
himakuma

2016/10/26 09:51

ishi9さんが回答していますが、一括でファイルをロードしている為、単純に2000*1MB=2G以上のメモリを使用していると思われます。表示ページ以外の画像はロードしない方がよいです。進むでロードする感じです。
lain

2016/10/26 09:58

ishi9様の返答でも書きましたが、ListBoxにバインドしているのは、DisplayFileNameListプロパティになります。このプロパティには表示件数のみを格納するようにしています。
himakuma

2016/10/26 10:08

今すぐに試せる状態にないので、確認なのですが、バインドしたListBoxの値はクリアしている処理はどこですか?startIndexを最大件数までカウントアップして、表示を終了したオブジェクトを破棄していないように見えます。
lain

2016/10/26 23:31

himakuma様 ListBoxにバインドしているコレクションはMainWindowViewModel.SetDisplayList内でしています。このコレクションはファイル名と日時を格納しているだけなので、解放できてなくてもメモリーを圧迫していないと思っています。ImageConverter内でWriteableBitmapに展開しているところが怪しそうなのですが、どうやってアクセスすれば良いのか分からない状態です。(出来れば回答欄に回答お願いしてもよろしいですか?ここは質問に対する修正依頼欄ですので)
guest

回答3

0

自己解決

本番の方にフィードバックさせて動作確認とかをしていたので、遅くなりました。

根本解決には至ってはいないのですが、下記のようにコンバーターを変更する事により
メモリーのオーバーフローは起こりにくくなりました。
(本番の方では3×3より小さい区分けを使用し、5×5はレアケースという事と、
画像もフルHD(1920×1080)がメインということなので)
なので、ここで解決済みとさせていただきます。
(将来デジカメの高解像度化や4K、8Kカメラ等の普及が怖いですが...)

WriteableBitmapがバッファを画像2枚分とるということが分かったので使わないようにしました。

C#

1 2public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 3{ 4 var path = (string)value; 5 bool isFailed = false; 6 BitmapImage bmpImage = null; 7 using (var fs = File.OpenRead(path)) 8 { 9 bmpImage = new BitmapImage(); 10 bmpImage.DownloadFailed += (s, e) => isFailed = true; 11 bmpImage.DecodeFailed += (s, e) => isFailed = true; 12 bmpImage.BeginInit(); 13 bmpImage.CacheOption = BitmapCacheOption.OnLoad; 14 bmpImage.StreamSource = fs; 15 bmpImage.EndInit(); 16 bmpImage.Freeze(); 17 } 18 19 if(isFailed) 20 { 21 GC.Collect(); 22 return new BitmapImage(new Uri(@"D:\error.png", UriKind.RelativeOrAbsolute)); 23 } 24 else 25 { 26 return bmpImage; 27 } 28} 29

回答を頂いた、ishi9様、himakuma様ありがとうございました。

投稿2016/11/08 01:34

lain

総合スコア161

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

回答遅くなりました。自分も以前同様の画面を作成したことがあります。
質問のコードだとメモリが2GB超えたりしますね。。。

マウスホイール等で拡大縮小を行うということなので、
画像表示部分はUserControlを作成するのが良いと思います。
DependencyPropertyを使用すればバインドも可能です。
BitmapDecoderを使用して、縮小状態で表示させることで、メモリ容量を節約できると思います。
気になるほどでもないかも知れませんが、表示用のファイルの読込処理は重くなります。

投稿2016/10/31 01:27

himakuma

総合スコア952

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

lain

2016/11/01 05:26

回答ありがとうございます。 質問で書いてあるコードは省略のため Imageを直接テンプレートの中に置いてますが、 本番の方は拡大・縮小以外にも機能が付加されてますのでUserControl化してあります。 どちらにしてもオリジナルサイズでの画像展開には無理がありそうなのですね。 なので、例外発生時に代替文字(画像)を表示するようにしようと思います。
guest

0

表示範囲外に出たら開放する処理を入れないと溜まっていく一方だと思いますよ。
WPFの処理にはあまり明るくないので的確なアドバイスはできませんが、
私はこの手のアプリを作る場合は、基本的に同時に表示される最大の数分以上のオブジェクトは持ちません。
そのオブジェクトの中身を入れ替えれたり、コントロールへの割り当てを切り替えたりすることで表示の更新を実現します。
(まぁ実際は表示切り替えが追いつかないのでバックバッファとして倍の数のオブジェクトを持つこともありますが、それでもあくまで表示数依存です。レコードの数には影響しません)

訂正:
失礼しました。WriteableBitmapは開放不要のクラスのようですね。

投稿2016/10/26 09:29

編集2016/10/26 09:52
ishi9

総合スコア1294

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

lain

2016/10/26 09:52

私も、表示されている分だけを保持するようにしているつもりなのですが、うまくいかない状態でして。。。 実際に表示する分はSetDisplayListメソッドを使用しDisplayFileNameListプロパティに格納するようにしています。(サンプルでは5×5で25件分) ImageConverterクラス内のWriteableBitmap使っているところ辺りが怪しいかなと思っているのですが、 WriteableBitmapクラスはIDisposableを使ってなかったりするのでどうしたものかと悩んでいます。
ishi9

2016/10/27 03:07 編集

WriteableBitmapは開放不要とはいえ、ずっと参照され続けていれば、やはり開放はされないと思います。ListBoxの中にある表示範囲外のWriteableBitmapを参照から外す処理が必要なのでは、とちょっと当てずっぽうに言ってみます。
ishi9

2016/10/27 03:26

SetDisplayListあたりでListBoxItemにアクセスして明示的に中の要素にNULLを入れるとか・・・できるんですかね?すいません、また当てずっぽうです。
lain

2016/10/27 05:34

データを入れ替えるタイミングでListBoxのItemCollectionの中を見たのですが、ImageFileInfoクラスのコレクションが入っているだけでした。 ImageクラスとImageSourceがどこかで紐づいていると思うのですが、それがいったい何処を辿っていけば良いのか分からない状態です。 あと、WriteableBitmapの参照の方ですが、上のコードにGC.Collectを実行するボタンを付け加え試してみたのですが、 初回起動後(25件正常に表示) GC実行 : 690M 進むボタンクリック → 例外発生(11件正常表示、あとは歯抜け) : 1.4G GC実行 : 460M (右端の数字は使用メモリ) 進むボタンボタンを押す前と後では表示出来ている枚数が違うので差が出てると思うのですが、 GCが効いている事を見ると大きくメモリを食いそうなWriteableBitmapは再表示終了時には参照外れているのかな?と推測しています。
ishi9

2016/10/27 05:48 編集

いくらなんでも初回の25件表示のところで690MB、 二件目(50枚やろうとしてできてない)のところで1.4Gって増えすぎな気がします。 リーク云々の前にメモリを食いつぶしている箇所があるという可能性もありそうです。 試しにConvert関数が何回実行されているか計測してみるとか・・・
ishi9

2016/10/27 05:57

後、それでOutOfMemoryExceptionが出るということは2Gの制限に引っかかっているくさいですが、32bit版でビルドしているんですかね?x64ターゲットでビルドすると、ひとまず動くかもしれません。
ishi9

2016/10/27 06:22

って修正欄見たら、画像データ1枚1MBもあるんですね。 それなら確かにメモリに展開したらそれくらいいきそうですね。 さすがに大きすぎるかと(多分3000x2000くらいありません?) 一度に25枚出すなら100×100もあれば十分ですよね(その半分くらいでも) 事前にサムネイル用の小さい画像データを作ってそれを使うべきです。
lain

2016/10/27 07:00

こちらこそ、お付き合いありがとうございます。 たしかにx64ビルドだと動作しました。 が、対応OSに7の32ビット版も入っていまして。。。 画像サイズもおっしゃる通りテストで使用しているものは3072×2048です。 Bitmapに展開しても18Mなので25枚で単純計算で450Mなのでいけるかな?と思っていたのですが。 そんなに甘くなかったですね。 サンプルコードでは省略してしまっているのですが、原寸大表示や表示倍率を変更できるようにしなくてはいけないのでサムネイル方式も使えないのです。 (25分割で原寸大表示って...というツッコミは無しでお願いします) Convertメソッドは進むボタンをクリックごとに25回呼ばれています。 まだちゃんと見れていないのですが、Convertメソッド内のWriteableBitmapを1回通るたびに40Mくらいメモリを食っているような感じがします。 これに関しては引き続き調べていきます。 ただ、ここに原因があったとして回避策をどうしたものかと。。。
ishi9

2016/10/27 07:03

枠のサイズさえわかっていれば、上記サイトのやり方でサイズ調整したWriteableBitmapを作れると思いますよ。
lain

2016/10/27 07:56

後だし情報ばかりで申し訳ないのです。 文書で説明が難しいのですが、 Windowsフォトビュアーのようにマウスホイールで拡大・縮小、ドラッグ&ムーブで表示エリアの移動を実現したいのです。 フォトビュアーが1Window内に25分割して表示されている感じになります。 となると、オリジナルサイズで読み込むのが最善かな?と思っているのですが。。。
ishi9

2016/10/27 08:17 編集

なるほど。 そういう場合はサイズ変更の完了イベントで読み込み直すのが手だと思います。 その時、スムーズに移行できるようにバックバッファに読み込んでおいて完了したら交換ってするとなお良いですね。 (画像ビューワとかでよく見かけませんか?表示された時、最初は荒いけど、だんだん綺麗に表示されていくやつ。あれはこの手の事をやっています) やってみて十分わかったと思いますが、画像データはすごい技術で恐ろしいほどに圧縮されているので、メモリに展開すると恐ろしく膨れ上がります。 せいぜい表示の倍くらいの大きさに留めておくのが無難です。
lain

2016/10/31 00:41

返事遅くなってすいません。 msdnでWriteableBitmapをよく読めば、内部でバックバッファとフロントバッファを使用していると書いてありますね。 これでWriteableBitmapのインスタンス時に18MのBitmap(解像度3072×2048)を展開しようとするわけですから、メモリ消費量が40M増えるのは納得です。 また内部でGDI+使ってるみたいで、アンマネージ部のメモリ使って、.net側にはポインタを返してるので、GCも動いてくれないようです。 次の方法を使い回避するようにしました。 とりあえずテストコードとテストデータでは動いています。 ``` public void SetDisplayList(int startIndex) { _displayFileNameList.Clear(); int max = startIndex + SelectedCol * SelectedRow; GC.Collect(); for(int i=startIndex; i<max; i++) { DisplayFileNameList.Add(FileNameList.ElementAtOrDefault(i)); } } ``` ``` public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { var path = (string)value; BitmapImage bmpImage = null; using (var fs = File.OpenRead(path)) { bmpImage = new BitmapImage(); bmpImage.BeginInit(); bmpImage.CacheOption = BitmapCacheOption.OnLoad; bmpImage.StreamSource = fs; bmpImage.EndInit(); } return bmpImage; } ``` さらに大きな解像度の画像がきた時は同じくメモリリークは起きると思うので根本解決ではないのですが。。。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問