ファイル検索プログラムより

タグの編集
投稿者 over50  (社会人) 投稿日時 2009/11/23 11:26:38
初めまして、こちらのサイトを利用して3ヶ月のROM専門の超初心者です。
よろしくお願いします。
50才を超えて、失業中なのをいいことにデーターベースを勉強しようとMS ACCESSを1段落して、VBを基礎からやってみようとこちらのサイトに来ました。
MS VS2008の評価版を使用しています。

題名の通り、初級講座の第31回ファイルシステムにあるファイル検索プログラムのことで教えていただきたいと書き込みました。
準備講座から始めてなんとか初級講座の第31回ファイルシステムまで来たので、今自分で出来る範囲でこのプログラムを自分で使いやすくカスタマイズしてみました。
と言っても、ドライブとデレクトリーの選択とタスクバーに表示。
プログレスバーと、検索ファイル数の表示だけですが。

如何せん、初めてなものでエラーも出ずに動作はしていますが皆さんに見ていただいて、おかしな点や間違い等、改良すべきところをご指摘いただければと思い投稿しました。
どんな点でも、これからの学習の指標になればと思います。
以下に、コードの全文を入れます。よろしくお願いします。

Public Class Form1

    Dim DD As String

    'プログレスバーの初期化
    Private Sub InitProgress()

        ToolStripProgressBar1.Minimum = 0

        ToolStripProgressBar1.Maximum = 100

        ToolStripProgressBar1.Value = 0
    End Sub

    Private Sub TextBox1_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles TextBox1.TextChanged

        Dim ofold As New IO.DirectoryInfo(DD)

        ListBox1.Items.Clear()

        If TextBox1.Text.Length = 0 Then
            Return
        End If

        Dim ofile As IO.FileInfo

        ListBox1.BeginUpdate()
        InitProgress()

        For Each ofile In ofold.GetFiles(TextBox1.Text & "*", IO.SearchOption.AllDirectories)

            Dim sfile As Integer = ListBox1.Items.Count

            ListBox1.Items.Add(ofile.FullName)
            ToolStripProgressBar1.Value = (sfile + 1) / (sfile + 1) * 100
            ToolStripStatusLabel1.Text = sfile + 1 & "項目"
            
        Next

        ListBox1.EndUpdate()
        ToolStripProgressBar1.Value = 0
    End Sub

    Private Sub ListBox1_DoubleClick(ByVal sender As Object, ByVal e As System.EventArgs) Handles ListBox1.DoubleClick
        On Error Resume Next
        Process.Start(ListBox1.SelectedItem)
    End Sub

    Private Sub DriveListBox1_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles DriveListBox1.SelectedIndexChanged
        DirListBox1.Path = DriveListBox1.Drive
        Call DirListBox1_SelectedIndexChanged(sender, Nothing)


    End Sub

    Private Sub DirListBox1_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles DirListBox1.SelectedIndexChanged

        DD = DirListBox1.DirList(DirListBox1.DirListIndex)
         StatusBar1.Text = DD
    End Sub

End Class
投稿者 あにす  (社会人) 投稿日時 2009/11/23 17:52:34
読んだだけで実行はしていないのですが、気付いた点を挙げていこうと思います。


3行目
Dim DD As String

変数名の意味がわかりません。SelectedDirectory等のわかりやすい名前を付けた方がいいと思います。


21行目
If TextBox1.Text.Length = 0 Then
    Return
End If

こういう無駄なネストを避ける書き方は僕好みです。


30行目でIO.SearchOption.AllDirectoriesを指定していますが、検索するフォルダによってはファイル数が多過ぎて検索に時間がかかり、アプリケーションが応答無しの状態になるかも知れません。マルチスレッド化を考えた方がいいと思います。


35~36行目
ToolStripProgressBar1.Value = (sfile + 1) / (sfile + 1) * 100
ToolStripStatusLabel1.Text = sfile + 1 & "項目"

でコントロールを操作していますが、この操作はTextBox1_TextChangedメソッドを抜けるまで反映されません。Application.DoEvents()を実行することで項目の表示が更新されるでしょう。
また、"(sfile + 1) / (sfile + 1) * 100"の計算結果は常に100になります。正しくは"1 / (sfile + 1) * 100"ではないですか?


45行目
On Error Resume Next

としていますが、Try-Catch文で例外をキャッチして、実行が失敗したことをユーザーに知らせた方がいいと思います。


51行目
Call DirListBox1_SelectedIndexChanged(sender, Nothing)

第一引数にsenderを渡していますが、イベントプロシージャの第一引数は通常イベントを発生したオブジェクトが指定されます。混乱を避けるためにも、NothingかDirListBox1を指定した方が安全だと思います。
投稿者 over50  (社会人) 投稿日時 2009/11/24 02:47:54
早速の返答ありがとうございます。
あにすさんの言われることを参考に、書き直してみました。

 ●35~36行目
ToolStripProgressBar1.Value = (sfile + 1) / (sfile + 1) * 100
ToolStripStatusLabel1.Text = sfile + 1 & "項目"

 これは、プログレスバーの表示に検索されるファイル数が不明なので、1度ファイル数を ToolStripProgressBar1.Maximum = 100に置き換えているです。

 ●51行目
Call DirListBox1_SelectedIndexChanged(sender, Nothing)
 第一引数にsenderを渡していますが、イベントプロシージャの第一引数は通常イベントを発生したオブジェクトが指定されます。混乱を避けるためにも、NothingかDirListBox1を指定した方が安全だと思います。

 このプロシージャの引数について,よく理解できていないのでもう少し詳しく教えていただければ嬉しいです。


あと、昼からデイレクトリ内にファイルが見つからない時に、メッセージを表示しようとしているにですが、上手くいきません。アドバイスをお願いできないでしょうか。
 
 
 Public Class Form1

    Dim DD As String '対象のディレクトリを宣言

    'プログレスバーの初期化
    Private Sub InitProgress()

        ToolStripProgressBar1.Minimum = 0

        ToolStripProgressBar1.Maximum = 100

        ToolStripProgressBar1.Value = 0
    End Sub

    Private Sub TextBox1_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles TextBox1.TextChanged

        Dim ofold As New IO.DirectoryInfo(DD) '対象フォルダのインスタンス作成

        ListBox1.Items.Clear()

        If TextBox1.Text.Length = 0 Then '何も入力されていない場合は何もしない。
            Return
        End If

        Dim ofile As IO.FileInfo

        ListBox1.BeginUpdate()
        InitProgress() 'プログレスバーを呼び出し

        Try

            For Each ofile In ofold.GetFiles(TextBox1.Text & "*", IO.SearchOption.AllDirectories)
         
                 Dim sfile As Integer = ListBox1.Items.Count + 1 '対象ファイル数
         
          'ここにファイルがないときのメッセージを入れたい。
                'If  ??????     Then
                'MsgBox("ファイルは見つかりません。")
                'End If
 
                ListBox1.Items.Add(ofile.FullName)
                ToolStripProgressBar1.Value = sfile / sfile * 100 '対象ファイル数を100に変換
                ToolStripStatusLabel1.Text = sfile & "項目"
            Next

        Catch ex As System.UnauthorizedAccessException  'ここにアクセス権限がない場合の処理を書く
            MsgBox(ex.Message, MsgBoxStyle.Exclamation)
        End Try

        ListBox1.EndUpdate()
        ToolStripProgressBar1.Value = 0 'プログレスバーを再度初期化
    End Sub

    Private Sub ListBox1_DoubleClick(ByVal sender As Object, ByVal e As System.EventArgs) Handles ListBox1.DoubleClick
        Dim path As String

        Try
            path = ListBox1.SelectedItem
            Process.Start(path) '選択ファイルを起動
        Catch ex As Exception
            MsgBox(ex.Message, MsgBoxStyle.Exclamation)
        End Try

    End Sub

    Private Sub DriveListBox1_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles DriveListBox1.SelectedIndexChanged
        DirListBox1.Path = DriveListBox1.Drive
        Call DirListBox1_SelectedIndexChanged(sender, Nothing)


    End Sub

    Private Sub DirListBox1_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles DirListBox1.SelectedIndexChanged

        '対象ディレクトリを指定
        DD = DirListBox1.DirList(DirListBox1.DirListIndex)
         StatusBar1.Text = DD
    End Sub

End Class
投稿者 tokumei  (社会人) 投稿日時 2009/11/24 03:49:31
>あと、昼からデイレクトリ内にファイルが見つからない時に、メッセージを表示しようとしているにです
>が、上手くいきません
こちらをご覧下さい
http://msdn.microsoft.com/ja-jp/library/system.io.file.exists(VS.80).aspx
投稿者 葉月  (社会人) 投稿日時 2009/11/24 04:33:09
初めましてよろしくお願いします。

Dim DD As String

私もあにすさんと同じ意見で、名前をわかりやすく変えた方がいいと思います。
C言語などは、略して使うのが一般的ですが――
オブジェクト指向言語では、略さないで使うのが一般的です。

私も一般的な略しは使います。
たとえば――
Control→Ctrl
Command→Cmd

しかし、本来なら略さない方が親切ですし、MSDNにある「名前に関するガイドライン」にも従っていない形になります。
http://msdn.microsoft.com/ja-jp/library/ms229002.aspx
投稿者 over50  (社会人) 投稿日時 2009/11/24 05:11:57
葉月さん、tokumeiさん、初めましてよろしくお願いします。
変数についてのご指摘ありがとうございます。

tokumeiさんの指摘されたmsdnも参考にして、ブール値や、ファイル数などを利用して条件式を試行しているのですが上手くいきません。
もう少し頑張ってみます。
投稿者 葉月  (社会人) 投稿日時 2009/11/24 07:53:36
いくつか気になる点があったので、修正用にサンプルを作りました。
ですが、サンプルの説明をする時間がなくなりました。
(これから、親戚の課題をアドバイスするため別のサンプル作りをします)
こちらに時間を割かないといけないので、即席になりました。
いつも以上にヤバいできですがご了承ください。


>>>状態推移
 BackgroundWorkerクラスを使うのが機能的にもいいですが、
 ループ中にMe.Textを変更するだけでポンプの役割を果たします。
 今回それでごまかしています。

■サンプル
以下のコントロールを貼り付けてください。
テキストボックス×2
リストボックス×1
ボタン×1

Public Class Form1

    Private Sub Button1_Click(ByVal sender As System.ObjectByVal e As System.EventArgs) Handles Button1.Click
        Me.FileSearch()
    End Sub

    ''' <summary> 
    ''' ファイルを探す。 
    ''' </summary> 
    Private Sub FileSearch()
        ' 選択ディレクトリ 
        Dim strSelectDir As String = Me.TextBox1.Text

        If Not System.IO.Directory.Exists(strSelectDir) Then
            Return
        End If

        ' 作業ディレクトリ 
        Dim selectDirectory As New System.IO.DirectoryInfo(strSelectDir)
        ' 作業ファイル 
        Dim file As System.IO.FileInfo
        ' 一時的にファイルを格納 
        Dim arFiles As New ArrayList()
        ' 探すファイル 
        Dim strSearch As String = Me.TextBox2.Text
        Dim strFile As String = String.Empty

        ' カウント変数 
        ' 宣言はFor文の前で行うのがいいです。 
        Dim sCount As Short = 0

        For Each file In selectDirectory.GetFiles(strSearch & "*", IO.SearchOption.AllDirectories)
            If Not file.Exists() Then
                MessageBox.Show("ファイルが存在しません")
            Else
                strFile = file.FullName
                arFiles.Add(strFile)
                Me.Text = String.Concat("処理ファイル数:" + sCount.ToString())
                ' プログレスバーの処理 
            End If
        Next

        Me.ListBox1.BeginUpdate()
        Me.ListBox1.Items.AddRange(arFiles.ToArray())
        Me.ListBox1.EndUpdate()
    End Sub
End Class

投稿者 over50  (社会人) 投稿日時 2009/11/24 09:02:22
今晩は、葉月さん。
お忙しい中、有難うございます。
早速サンプルを試して、明日にでも書き込みます。
投稿者 あにす  (社会人) 投稿日時 2009/11/24 18:13:10
>over50さん
イベントプロシージャの引数については、そのまま初級講座を進めていけば"第49回 イベントの作成"で詳しく書かれています。
投稿者 over50  (社会人) 投稿日時 2009/11/24 20:33:03
あにすさん、レス有難うございます。
"第49回 イベントの作成"じっくり読みこんでみます。

葉月さん、昨日のコードでいろいろ試しているのですが、配列の部分 
”For Each file In selectDirectory.GetFiles(strSearch & "*", IO.SearchOption.AllDirectories)”でエラーが出て前に進みません。

でも違う人が同じ目的のプログラムを書くと私のレベルでは全く違うものに見えて、勉強になります。
じっくり時間をかけて調べてみます。
 
あと、ファイルが見つからない時のメッセージには手こずっています。
ExistsメソッドやDir関数も試したのですが上手くいきません。
今は、ループ内から出して下にスクリプトを書いて試しています。
この場合は、ファイルがないので配列その物が出来ないのだからそこから判断させる方法を考えています。
投稿者 葉月  (社会人) 投稿日時 2009/11/25 05:08:06
サンプルの説明をしていなかったので簡単に説明します。
For文の中でlistboxのAddメソッドを使用していたので、
ArrayListに格納してからAddRangeでまとめて処理しました。
こうすることでコードがシンプルになり処理も向上します。

■サンプル
Me.ListBox1.BeginUpdate()
Me.ListBox1.Items.AddRange(arFiles.ToArray())
Me.ListBox1.EndUpdate()


処理の話は、Addメソッドで一つずつ格納しても、ListBoxだったらよほど大量のデータでな
ければ問題ないと思います。
しかし、ListViewで同じような使い方をすると画面がちらついたり処理がもたつく可能性が
あります。


>サンプルの使い方
TextBox1に作業するフォルダを入力します。
TextBox2に探すファイル名(一部OK)を入力します。
TextBox2に該当するファイルが見つかった場合に、ListBox1にフルパスが表示されます。

>~でエラーが出て前に進みません。
サンプルはVS2005以上なら動くと思うのですが、コンパイルエラー、
もしくは実行時にエラーが起きましたでしょうか?
対応中でしたら、エラー内容と状況をお知らせ頂ければ、他の参加者や私が助言できると思います。
カウント変数が0のままだったり酷い出来ですが、ファイル検索はできていました。
over50さんの方で処理を追加していましたら、その絡みかも知れません。
掲示板のまま打ったとしたら、どこかで打ち間違えているのも考えられます。
その場合は、FileSearch()メソッドを丸ごとコピペしてみてください。
投稿者 over50  (社会人) 投稿日時 2009/11/25 08:42:12
葉月さん、丁寧な説明ありがとうございます。

言われるように再度確認すると、TextBox1にドライブの指定だと以下のメッセージが表示。

”unauthorized access axceptionはハンドルされませんでした。とダイアログが出て
パス 'c:\System Volume Information' へのアクセスが拒否されました。”

ディレクトリーまで指定すれば問題なく動作確認。

ただし、ファイルがないときのメッセージや処理ファイル数の表示はカウントされないですね。

”ArrayListに格納してからAddRangeでまとめて処理しました。
こうすることでコードがシンプルになり処理も向上します。

■サンプル
Me.ListBox1.BeginUpdate()
Me.ListBox1.Items.AddRange(arFiles.ToArray())
Me.ListBox1.EndUpdate ”

を参考に再度コードをやり直して、アップします。
投稿者 葉月  (社会人) 投稿日時 2009/11/25 10:29:18
>”unauthorized access axceptionはハンドルされませんでした。
権限のないディレクトリにアクセスしてエラーが起こっています。
ドライブ全体を対象にすると、ユーザーにアクセス権限がないフォルダを除外するよう考慮
する必要があり難度が上がると思われます。
以下に修正したサンプルを載せますが、こちらもドライブ全体を考慮していません。

>>>ただし、ファイルがないときのメッセージ
簡易的に修正したサンプルを上げるので、こちらで試してください。
このサンプルを少し修正すれば、エラーファイルのカウントなども取得できるようになりま
す。

■サンプル
Public Class Form1

    Private Sub Button1_Click(ByVal sender As System.ObjectByVal e As System.EventArgs) Handles Button1.Click
        Me.ListBox1.Items.Clear()
        Me.FileSearch()
    End Sub

    ''' <summary> 
    ''' ファイルを探す。 
    ''' </summary> 
    Private Sub FileSearch()
        Const SHORT_COUNT_UP As Short = 1

        ' 選択ディレクトリ 
        Dim strSelectDir As String = Me.TextBox1.Text

        If Not System.IO.Directory.Exists(strSelectDir) Then
            Return
        End If

        ' 作業ディレクトリ 
        Dim selectDirectory As New System.IO.DirectoryInfo(strSelectDir)
        ' 作業ファイル 
        Dim file As System.IO.FileInfo
        ' 一時的にファイルを格納 
        Dim arFiles As New ArrayList()
        ' 探すファイル 
        Dim strSearch As String = Me.TextBox2.Text
        Dim strFile As String = String.Empty

        ' カウント変数 
        ' 宣言はFor文の前で行うのがいいです。 
        Dim sCount As Short = 0

        For Each file In selectDirectory.GetFiles(strSearch & "*", IO.SearchOption.AllDirectories)
            strFile = file.FullName

            If System.IO.File.Exists(strFile) Then
                sCount += SHORT_COUNT_UP
                strFile = file.FullName
                arFiles.Add(strFile)
                Me.Text = String.Concat("処理ファイル数:" + sCount.ToString())
                ' プログレスバーの処理 
            Else
                MessageBox.Show("ファイルが存在しません")
            End If
        Next

        Me.ListBox1.BeginUpdate()
        Me.ListBox1.Items.AddRange(arFiles.ToArray())
        Me.ListBox1.EndUpdate()
    End Sub
   
End Class




投稿者 over50  (社会人) 投稿日時 2009/11/26 08:55:07
葉月さん、度々丁寧なRES.有難うございます。
もう少し早くアップしたかったのですが、動作確認に時間がかかって遅くなりました。

今回は、自分のPCの環境に合わせてTextChangedイベントに拘りました。
マルチブートとドライブが多く、自分では使いやすい物になったように思います。

デザインは、ドライブリストボックス、ディレクトリリストボックス、テキストボックス、ステータスバーとステータスストリップに、ラベルとプログレスバーを各1つづつ張り付けたものです。

特にコードの中のTextChangedイベントで、キー入力のイベント発生を遅らせる処理での 
”System.Threading.Thread.Sleep(1000)
        Application.DoEvents()”と、

ファイルが無い時のメッセージの処理は何とか出来たもののこれでいいのかよく解っていません。
その他を含め、皆さんから何かアドバイスをいただければ幸いです。


Public Class Form1

    Dim SlctDirect As String '対象のディレクトリを宣言

  Private Sub TextBox1_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles TextBox1.TextChanged
        'キー入力でのイベントを遅らせて、スムーズにする。
        System.Threading.Thread.Sleep(1000)
        Application.DoEvents()
        '表示を初期化
        ToolStripProgressBar1.Value = 0
        ToolStripStatusLabel1.Text = Nothing
        Me.Text = ("簡易ファイル検索")
        Me.ListBox1.Items.Clear()
        Me.filesearch()

    End Sub
    Private Sub filesearch()
        Const short_count_up As Short = 1

        ' 選択ディレクトリ 
        Dim strSelectDir As String = Me.SlctDirect
        '何も入力されていない場合は何もしない。
        If TextBox1.Text.Length = 0 Then
            Return
        End If

        ' 作業ディレクトリ
        Dim selectDirectory As New System.IO.DirectoryInfo(strSelectDir)
        ' 作業ファイル 
        Dim file As System.IO.FileInfo
        ' 一時的にファイルを格納
        Dim arFiles As New ArrayList()
        ' 探すファイル 
        Dim strSearch As String = Me.TextBox1.Text
        Dim strFile As String = String.Empty

        ' カウント変数 
        ' 宣言はFor文の前で行うのがいいです。
        Dim sCount As Short = 0
        Try
            For Each file In selectDirectory.GetFiles(strSearch & "*", IO.SearchOption.AllDirectories)
                strFile = file.FullName

                If System.IO.File.Exists(strFile) Then
                    sCount += short_count_up
                    strFile = file.FullName
                    arFiles.Add(strFile)
                    ' プログレスバーの処理
                    ToolStripProgressBar1.Value = sCount / sCount * 100
                    ToolStripStatusLabel1.Text = String.Concat(+sCount.ToString() & "項目")
                End If
            Next
            If strFile = String.Empty Then
                Me.Text = "ファイルが見つかりません!"
            End If
            'エラー処理
        Catch ex As System.UnauthorizedAccessException
            MsgBox(ex.Message, MsgBoxStyle.Exclamation)
        End Try
        Me.ListBox1.BeginUpdate()
        Me.ListBox1.Items.AddRange(arFiles.ToArray())
        Me.ListBox1.EndUpdate()

    End Sub

    Private Sub ListBox1_DoubleClick(ByVal sender As Object, ByVal e As System.EventArgs) Handles ListBox1.DoubleClick
        Dim path As String

        Try
            '選択ファイルの起動
            path = ListBox1.SelectedItem
            Process.Start(path)
        Catch ex As Exception
            MsgBox(ex.Message, MsgBoxStyle.Exclamation)
        End Try

    End Sub

    Private Sub DriveListBox1_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles DriveListBox1.SelectedIndexChanged
        DirListBox1.Path = DriveListBox1.Drive
        Call DirListBox1_SelectedIndexChanged(sender, Nothing)


    End Sub

    Private Sub DirListBox1_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles DirListBox1.SelectedIndexChanged
        '対象のディレクトリの選択
        SlctDirect = DirListBox1.DirList(DirListBox1.DirListIndex)
        StatusBar1.Text = SlctDirect
    End Sub
End Class
投稿者 葉月  (社会人) 投稿日時 2009/11/26 10:26:15
over50さん、目的のものが出来たようで何よりです。
おめでとうございます。

私的には作ることが大事だと思うので、コードに関してとくにありません。
命名規則だけ簡単に説明いたします。
メソッド(関数)は、最初大文字、違う単語がきたらまた大文字にします。

Private Sub filesearch()だったら――
Private Sub FileSearch()が、.NET系の推奨されている書き方になります。

それから、私はハンガリアン記法をString型とFormコントロールでよく使っていますが、
.NET系では使わないのが推奨になっています。
その点をご理解頂いて使用されているなら、全てに目を通していませんが変数名はとくに問
題ないと思います。
投稿者 over50  (社会人) 投稿日時 2009/11/26 17:26:49
葉月さん、有難うございます。
いただいたコード、時間がある時に再度じっくり読み込んでみます。
また何か有りましたらよろしくお願いします。