いつもお世話になっております。
以下のコードのようにピクチャーボックスを並べて画像を表示しているのですが、
時間がたつとアプリが「メモリが大きすぎる」というエラーを出してしまいます。
おそらくdisposeで開放していないからなのかなと思っています。
Dim fs As FileStream Dim img As Image For i As Integer = 1 To 4 '生成 Dim bihin As New PictureBox '~~~DBからresourceを読み出します~~~~' fs = File.OpenRead(resource) img = Image.FromStream(fs, False, False) bihin.Image = img Me.Controls.Add(bihin) Next
しかし、ネットで調べてコードを作り、デバックしてみてもコンパイルエラーが起きてしまいます。
(使用されたパラメータが有効ではありませんとのことです。)
以下にコードを載せます。このコードでは何がだめなのでしょうか。
どうかご教授宜しくお願い致します。
Imports System.IO Try fs = File.OpenRead(resource) img = Image.FromStream(fs, False, False) bihin.Image = img Me.Controls.Add(bihin) Finally fs.dispose() img.Dispose() End Try
Windows Form Application
Framework:.Net4.6.1
「時間がたつと」とありますが、提示されたコードは定期的に動くコードなのでしょうか?
それとも1回だけなのでしょうか?
もし定期的に動くのであれば
PictureBoxを毎回新たに生成しなくてはならないのでしょうか?
生成した後のPictureBoxはどうしているのでしょうか?
ご質問ありがとうございます。YAmaGNZ様、いつもありがとうございます。
説明が抜けておりました。書いていませんでしたが、ご指摘の通り、このコードは1つのプロシージャ(スタートアップフォームのロードイベント)として使っており、
ピクチャーボックスに表示する情報を更新していくので、毎回DBから読み出すこのイベントを使っています。そして、これもまた書いていませんが、毎回このイベントでは前回生成したピクチャーボックスを消去しています。
時間が経つと、というのはこのイベントを何度も読み出すと、次第にメモリが大きくなってエラーを出すようになるということです。
やり方が分からず使っていませんが
生成したピクチャーボックスをdisposeできていないのが原因と考えています。
コンパイルエラーなのか、デバッグ時の例外なのかどちらですか?
エラー内容はそのままを正確に記載してください
エラーというか、フリーズです。
Releaseの実行ファイルを起動してもある程度動かしてたらメモリが大きくなり、動かなくなります。そのあとに「メモリが大きいです」というようなエラーメッセージが出ます。全文は申し訳ないですが分かりません。
PictureBoxを破棄しているとのことですが、その部分はどうなっていますか?
また、PictureBoxを作って、破棄するのであれば1回作って使いまわせばいいのではないですか?
ご回答ありがとうございます。
無知で申し訳ないのですが、それはどういうことなのでしょうか?
説明が下手で申し訳ないですが
pictureboxは現在のコードでは破棄するコードを入れてません。
>そして、これもまた書いていませんが、毎回このイベントでは前回生成
>したピクチャーボックスを消去しています。
と書いているのに破棄のコードが無いということは消去していないのではないですか?
使いまわしとは1回どこかでPictureBoxを生成して配列にでも入れておいて
提示されたループでNewするのではなく、その配列に格納されているPictureBoxにアクセスすればいいだけです。
語弊をまねく書き方で申し訳ありません。
前回生成しているピクチャーボックスを消去していると書いたのは以下のように配列の前にForm上のコントロールを全部削除していますという意味です。あえて書く必要もありませんでした。
↓↓↓↓
For Each c As Control In Me
'コントロールを削除します
Next
'この後にピクチャーボックスを新しく生成しています。
For i = 1 to 4
'生成
Dim bihin As New PictureBox
'~~~DBからresourceを読み出します~~~~'
fs = File.OpenRead(resource)
img = Image.FromStream(fs, False, False)
bihin.Image = img
Me.Controls.Add(bihin)
Next
そして今したいのは新しいピクチャーボックスを生成している配列の中にアンマネージドリソースの解放句らしいdisposeを加えたいということです。
現状のこのコードでは、このピクチャーボックスを新しく生成しているイベントを何回か繰り返すとPCが重くなり、しばらくすると「メモリが大きすぎる」というメッセージのエラーが出てしまいます。
これでは使い物にならないので、アンマネージドリソースを開放するコードを加えたいのです。
使いまわしというのは私のコードで書くと、以下のようにするということですか?
↓↓↓
'配列の外に出す
Dim bihin As New PictureBox
For i = 1 to 4
'~~~DBからresourceを読み出します~~~~'
fs = File.OpenRead(resource)
img = Image.FromStream(fs, False, False)
bihin.Image = img
Me.Controls.Add(bihin)
bihin.dispose()
Next
使いまわしとは
どこかで1回PictureBoxを生成しておいて
この処理では
For i = 1 to 4
'~~~DBからresourceを読み出します~~~~'
fs = File.OpenRead(resource)
img = Image.FromStream(fs, False, False)
Dim oldImage = bihin(i).Image
bihin(i).Image = img
oldImage.Dispose()
Next
といった感じで画像をセットするだけでいいのではないですかということです。
返信が遅くなり、申し訳ありません。
YAmaGNZさんの提示してくださったコードをいまから試してみようと思います。
bihin(i)のような配列の指定変数みたいなものを使うのは初めてです。
一つ一つ理解できずにすみませんが、
どこかで一回Pictureboxを生成しておくというのは 以下のようにするのでしょうか?
New 句はもういらなくなるのですか?
↓↓↓
Dim bihin(5) as PictureBox
for i = 1 to 4
’それぞれのbihin(i)にimageを入れる
Next
Dim bihin(5) as New PictureBoxとはできませんでした。
配列をNewで宣言することはできませんとなります。
>使いまわしとは1回どこかでPictureBoxを生成して配列にでも入れておいて
このやり方がわかりません。。。
また、oldimageはなんのために宣言するのでしょうか?
Dim oldImage = bihin(i).Image
とありますが、この時点ではbihin(i).ImageはNothingのはずです。
bihin(i).Image = img
img.Dispose()
ではだめなのでしょうか。
コントロールの配列に関して調べてください。
また、Disposeするのは何なのか考えてください。
私が書いたのはあくまで例であり動作を保証するものではありません。
ようは前にセットしてあったイメージをDisposeして新しいものをセットしましょうということです。
PictureBoxのImageにセットする前に保存しておくのは、PictureBox.Imageにセットされている状態でDisposeするとDispose後にPictureBox側からアクセスされる可能性があるので、Imageプロパティに新しいImageをセットし古いImageをPictureBoxから完全に切り離すという目的です。
ありがとうございます。oldimageを宣言するのはもともとbihin(i).imageに格納されているリソースを移してこれをdisposeするためだけのものってことだと理解しました。(1回目はNothingなはずなので2回目以降有効)これであっていますでしょうか?
また、そうすると一つ疑問が浮かんだのは
Dim oldimage =bihin(i).image
oldimage.dispose()
にしても、bihin(i).imageの中にdisposeは残っているのではないか?ということです。
でもそれは大丈夫なのですね。
いま作ったコードを書きます。以下のページも参考にして作りました
https://dobon.net/vb/dotnet/control/buttonarray.html
↓↓↓
'ボタンコントロール配列のフィールドを作成
Private bihin() As System.Windows.Forms.PictureBox
'ボタンコントロール配列の作成
Me.bihin = New System.Windows.Forms.PictureBox(3) {}
For i = 1 to 4
Me.bihin(i) = New System.Windows.Forms.PictureBox
'~~~DBからresourceを読み出します~~~~'
fs = File.OpenRead(resource)
img = Image.FromStream(fs, False, False)
If Not bihin(i).Image Is Nothing Then
'古いイメージを削除(イベント発生2回目以降有効)
Dim oldImage = bihin(i, j).Image
oldImage.Dispose()
End If
bihin(i).Image = img
Next
↑↑↑
一応これで動作はしますが、だめなのは分かります。
配列の中でピクチャーボックスを生成してしまっているから、
If Not bihin(i).Image Is Nothing Then
の分岐には行かないということですよね。
YAmaGNZさんの言われる使い回しにできるようにまず考えてみます。
あと、もしかしてYAmaGNZさんの意図では
>PictureBox.Imageにセットされている状態でDisposeするとDispose後にPictureBox側からアクセスさ>れる可能性があるので
ということですから、
oldimage.disposeはbihin.image=imgの後にやったほうがいいのでしょうかね。。。
思うのですが、使いまわしをすることで動作はいくらか軽くなるのでしょうか?
配列で生成している今のコードではdisposeは必要がないのではと今思いました。
なぜメモリがいっぱいになってしまうのでしょうか。
YAmaGNZさんの言われる使い回しをすれば、、、軽くなるのでしょうか。
現状のコードではフォームのデザインに何も配置していません。全て生成と削除をコードで記入しています。
同じフォームで何度も生成と削除を繰り返す。。
それが多分だめなのでしょうね。。。使い回しの方が軽くなりそうですね。
あと削除といっても以下のようにVisibleプロパティをFalseにしているだけです。
メモリが大きくなっていくのはこれだからかもしれません。
Dim objControl As Control
On Error Resume Next
For Each objControl In Me.Controls
objControl.Visible = False
Next
PictureBoxを使いまわすということは、どれだけの時間実行してもPictureBoxは最初のものだけということになります。
細かい話になりますが、「PictureBoxを作る」「PictureBoxを削除する」という処理を行う必要がなくなるのでその分軽くなります。
また、
Dim oldImage = bihin(i).Image
oldImage.Dispose()
bihin(i).Image = img
ではなく
Dim oldImage = bihin(i).Image
bihin(i).Image = img
oldImage.Dispose()
この順番です。
何故前者ではダメなのかというと
Dim oldImage = bihin(i).Image
oldImage.Dispose()
この時点でImageは破棄されるがPictureBoxはまだImageのインスタンスを保持していて、PictureBoxがその破棄されたImageを使う可能性がある
なので
Dim oldImage = bihin(i).Image
bihin(i).Image = img
このようにPictureBoxのImageを新しいものに変えてしまえばPictureBoxは古いImageのインスタンスは保持しなくなるのでアクセスできなくなります。
このように確実にアクセスできないようにしてから破棄してくださいということです。
VisibleプロパティをFalseにすることを削除とは言いません。
非表示と言います。
Control.Visible プロパティ
https://docs.microsoft.com/ja-jp/dotnet/api/system.windows.forms.control.visible?view=netframework-4.8
なので、見えなくなっているだけで存在します。
そしてPictureBoxは新たに生成され続けるのでPictureBoxの数はどんどん増えていきます。
PictureBoxを使いまわすということはこういったことも防げることになります。
なるほどです。納得しました。
親切丁寧な説明ありがとうございます。
ちょっと時間はかかりますが使いまわしのコードに今から直そうと思います。
「使いまわす」といった言葉がややこしいのかもしれませんね。
最初からPictureBoxを必要個数分配置しておいて、それに対してImageをセットするという言い方なら分かりますか?
ありがとうございます。
「使い回す」という言葉ですが、理解できていると思っています。
今ピクチャーボックスを必要個数分formのloadイベントで生成して使い回すようにコードを編集している最中です。
このloadイベントを何回も繰り返すようにプログラムを作ってしまっていたので、loadイベントははじめの一度きりしか使わず、後はその生成したピクチャーボックスを更新するイベントを繰り返すようにする方針です。
「使い回す」はとても分かりやすい表現だと感じてます!
生成するイベントは表題のコードをそのまま使えばいいと思っていますが、
更新するイベントは表題のコードで配列として生成したピクチャーボックスにアクセスしないといけないので、ちょっと難しいですね。
フォームのデザインに配置しているわけではないので、Loadイベントの中以外ではbihin(i)は
宣言されていない扱いになってしまいます。
これについてももうちょっと考えてみようと思います。
回答1件
あなたの回答
tips
プレビュー