複数の画像を読み込んで、フォームの背景画像を動的に変更する方法を教えて下さい。

タグの編集
投稿者 VB+VC# Beginner  (社会人) 投稿日時 2020/10/16 00:29:31
複数の画像を読み込んで、フォームの背景画像を動的に変更する方法を教えて下さい。

やりたいことは、
① 1つのフォルダーに、0.jpg ~ 9.jpg を用意しました。
② 画像をあらかじめ、img(0) ~ img(9) に読み込みたい。
③ タイマーでフォームの背景画を変更したい。

ネットで、以下のコードを見つけましたが、全く理解できず、見よう見まねで
変更しましたが、開始後数秒でエラーになります。
参考ページ : 
https://www.atmarkit.co.jp/fdotnet/dotnettips/597fastloadimg/fastloadimg.html

Imports System.IO
Public Class Form1
    Dim i As Integer
    Dim img(9) As Image
    Dim dir As String = "E:\My Documents\My Picture\..."
    Dim jpgFiles As String() = Directory.GetFiles(dir, "*.jpg")
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        i = 0
        For Each jpg As String In jpgFiles
            Using fs As FileStream = File.OpenRead(jpg)
                Using image As Image = Image.FromStream(fs, False, False)
                    img(i) = image
                    i += 1
                End Using
            End Using
        Next
        i = 0
        Timer1.Enabled = True
    End Sub
    Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
        If i < img.Length - 1 Then
            i += 1
        Else
            i = 0
        End If
        Me.BackgroundImage = img(i)
    End Sub
    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Application.Exit()
    End Sub
End Class

コードエディターで Dim img(9) ~ Dim jpgFiles の行に
IDE0044 Visual Basic Make field readonly
が出ています。
実行すると、「System.ArgumentException: '使用されたパラメーターが有効ではありません。'」
がでます。

他のページも検索しましたが、見つからなかったので質問します。
宜しくお願い致します。
投稿者 るきお  (社会人) 投稿日時 2020/10/16 08:12:01
>実行すると、「System.ArgumentException: '使用されたパラメーターが有効ではありません。'」
がでます。

どの行でそれが出ますか?

何が起こっているのかよくわからないので、

Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load

の行を

Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Shown

に変更して実行してみて、エラーが変わるかどうかご確認ください。

※Loadイベント内で発生したエラーをVisual Studioは報告してくれない非常に特殊なイベントなので、VB+VC# Beginner さんが気が付いていない別のエラーが実は発生しているかもしれません。Shownイベントはフォームが表示されたタイミングで発生し、この中のエラーであればVisual Studioは報告してくれます。

>コードエディターで Dim img(9) ~ Dim jpgFiles の行にIDE0044 Visual Basic Make field readonlyが出ています。
これはVisual Studioからのアドバイスですが、今回のエラーには関係ないです。
変数imgと変数dirは初期値を設定する以外、後で値を変更していないので、Reaonlyキーワードを使って読み取り専用にしてみてはどうでしょう? というVisual Studioからの控えめな提案です。無視しでも機能・性能に影響はありません。

ところで、VB+VC# Beginner さんが、今のプログラムに興味がなく、やりたいこと①と③が実現できる方法がわかれば何でも良いということでしたら、簡単なサンプルを紹介することもできますよ。
私は、②について、画像をあらかじめ読み込んでおくのはメモリーがもったいないので好きではないのですが、Timerの間隔を非常に短くしてアニメーションのような効果を期待しているのであれば、確かに重要ですね。



投稿者 VB+VC# Beginner  (社会人) 投稿日時 2020/10/16 19:06:07
るきお様

ご回答ありがとうございました。

ご指摘の通り、
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load



Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Shown

に変更しても、表示内容は一緒でした。

前回記述していませんでしたが、前述のエラー以外に、
「アプリケーションはブレークモードになっています」(大きな字)
「お使いのアプリはブレーク状態になりましたが、すべてのスレッドが外部コード (通常はシステム コードまたはフレームワーク コード) を
実行していたため、表示されるコードはありません。」(細かい字)
も出ていました。

画像の切り替えは、テスト段階では、1~5秒程度、最終的には1~10分に一度になる予定です。
画像は取り敢えず10個でテストしますが、画像が集まれば30~60個程度を予定しています。
メモリーに入れておけば切り替えがスムーズであるとどこかで読みましたが、ご指摘の通り、メモリーの無駄遣いになるのなら
今後検討するとして、まずは動作確認をしたいので、①+③のサンプルを紹介して下さい。
お手数をおかけしますが、宜しくお願い致します。
投稿者 るきお  (社会人) 投稿日時 2020/10/16 20:44:16
>前回記述していませんでしたが、前述のエラー以外に、
>「アプリケーションはブレークモードになっています」(大きな字)
>「お使いのアプリはブレーク状態になりましたが、すべてのスレッドが外部コード (通常はシステム コードまたはフレームワーク コード) を
>実行していたため、表示されるコードはありません。」(細かい字)
>も出ていました。
いろいろ妙なので、プログラムの問題以外に、実行環境の状態や設定がおかしいのかもしれません。
[ツール]メニューの[設定のインポートとエクスポート]を使うと、すべての設定を最初の状態に戻すことができます。
また、原因が追求できないのならば、現在のプログラムはもう開かないことにして、新しいプロジェクトを作成しなすと良いかもしれません。

フォルダー内の画像(jpg)を次々と切り替えるサンプルを紹介します。
私の環境では動作します。

Public Class Form1

    Dim filePointer As IEnumerator(Of String)

    Private Sub Form1_Shown(sender As Object, e As EventArgs) Handles Me.Shown

        '▼マイピクチャーフォルダーの直下にある 拡張子が jpg のファイルのパスの一覧を取得します。 
        Dim imageFolder As String = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures)
        Dim fileNames As New List(Of String)(IO.Directory.GetFiles(imageFolder, "*.jpg"))

        '▼一覧の画像を指し示すポインター(列挙子)を取得して、最初の画像を表示します。 
        filePointer = fileNames.GetEnumerator()
        ShowNextImage()

        'タイマー起動。以降は 3秒ごとに次の画像を表示します。 
        Timer1.Interval = 3000
        Timer1.Enabled = True

    End Sub

    ''' <summary> 
    ''' filePointerの次の画像を表示します。次の画像がない場合、最初の画像を表示します。 
    ''' ファイルが1つもない場合はエラーになります。 
    ''' </summary> 
    Private Sub ShowNextImage()
        If filePointer.MoveNext = False Then
            filePointer.Reset()
            filePointer.MoveNext()
        End If
        Me.BackgroundImage = Image.FromFile(filePointer.Current)
    End Sub

    Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
        ShowNextImage()
    End Sub

End Class


このサンプルではマイピクチャーフォルダーを対象にしていますが、下記のようにimageFolderの値を変更することで任意のフォルダーを対象にすることができます。
Dim imageFolder As String = "C:\xxxx\ddddd"

すべての画像を最初の段階でメモリー上に読み込んでおく例も紹介します。
Public Class Form1

    Dim filePointer As IEnumerator(Of Image)

    Private Sub Form1_Shown(sender As Object, e As EventArgs) Handles Me.Shown

        '▼マイピクチャーフォルダーの直下にある 拡張子が jpg のファイルの画像の一覧を取得します。 
        Dim imageFolder As String = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures)
        Dim images As New List(Of Image)
        For Each fileName As String In IO.Directory.GetFiles(imageFolder, "*.jpg")
            images.Add(Image.FromFile(fileName))
        Next

        '▼一覧の画像を指し示すポインター(列挙子)を取得して、最初の画像を表示します。 
        filePointer = images.GetEnumerator()
        ShowNextImage()

        'タイマー起動。以降は 3秒ごとに次の画像を表示します。 
        Timer1.Interval = 3000
        Timer1.Enabled = True

    End Sub

    ''' <summary> 
    ''' filePointerの次の画像を表示します。次の画像がない場合、最初の画像を表示します。 
    ''' ファイルが1つもない場合はエラーになります。 
    ''' </summary> 
    Private Sub ShowNextImage()
        If filePointer.MoveNext = False Then
            filePointer.Reset()
            filePointer.MoveNext()
        End If
        Me.BackgroundImage = filePointer.Current
    End Sub

    Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
        ShowNextImage()
    End Sub

End Class


いろいろな書き方があり、上記はほんの一例です。

投稿者 VB+VC# Beginner  (社会人) 投稿日時 2020/10/17 19:25:03
るきお様

昨日は投稿後すぐに外出し、
先ほど仕事から帰宅し、今読み終わったところです。

ありがとうございました。
試してみます。
投稿者 VB+VC# Beginner  (社会人) 投稿日時 2020/10/17 23:50:42
るきお様

①  Dim filePointer As IEnumerator(Of String)

②  Dim filePointer As IEnumerator(Of Image)

の両方とも、私のパソコン環境でも正しく背景画像を変更できました。

①と②では、プロセスメモリーのグラフに歴然とした差が見られました。
3秒ごとの変更では、①でもスムーズに切り替わりましたので、
あえてイメージを読み込んでおく必要はなさそうですね。

私が画像を変更したいプロジェクトには、すでに Timer は Interval = 1000 で
使用していますので、xx秒ごとに ShowNextImage() を呼び出すことは可能です。
フォルダー内のファイル数のカウントの仕方はネットで調べられましたので、
エラーは防げそうです。
(画像を変更したいので、あえてファイルを消すことはありませんが。)

どうもありがとうございました。