FolderBrowserDialogの機能拡張

タグの編集
投稿者 neptune  (社会人) 投稿日時 2009/3/28 08:33:16
こんにちは


FolderBrowserDialog の機能拡張の仕方をご教示お願いします。
FolderBrowserDialog は初めて使います。

FolderBrowserDialogコントロールにTextBoxを実装してやろうと思うのですが、
どうも、プロパティとして用意されてなくDialogをHookしてやらなければならない
ということがわかりました。
で、キーワードとしてRunDialog HookProc等はMSDNから調べられて、

MSDNの解説には
///////////////////////以下引用/////////////////////////
継承クラスでは、特定のコモン ダイアログ ボックスを作成するために、ShowDialog を呼び出す
ことによって RunDialog を実装する必要があります。継承クラスでは、HookProc をオーバーライド
して、ダイアログ ボックスに対して特定のフック機能を実装することもできます。
///////////////////引用終わり////////////////////////////
と書いてあるんですが、使い方が良くわかりません。

「FolderBrowserDialog RunDialog HookProc」でググッても見たんですが、
どうもVBのサンプルも(日本語では)見当たらないようです。外国語も多分???
しかし、この程度のことなら誰もやってないはずないんですけどねぇ・・・


やはりSHBrowseForFolderでゴリゴリ書くしかないんでしょうか??
それなら
BIF_EDITBOX = &H10 
をflgに追加してやるということは理解してます。

しかしながら、FolderBrowserDialogのHWNDもAPIで取得しなければならないのか、
それとも、なにかMSDNに書いてない方法があるのかもわかりません。
プロパティにない事は確認してます。

恐らく、FolderBrowserDialogでも同様な処理が必要とは推測してますので、RunDialog HookProc
の使い方がわかれば何とかなるような気がします。


長くなったので纏めます。
目的としてはFolderBrowserDialogをHookしてflgにBIF_EDITBOXを追加したい。
質問事項は以下のようになります。

1.FolderBrowserDialogの RunDialog HookProcの使い方
2.FolderBrowserDialogのframeworkを使ったHWNDの取得の仕方
3.普通APIでゴリゴリやるもんなんでしょうか??

よろしくお願いします。
投稿者 葉月  (社会人) 投稿日時 2009/3/28 12:00:35
neputuneさん、こんにちは。

RunDialogとHockProcの組み合わせも、コードをガリガリ書くイメージがあります。
興味があったので、少し調べました。
参考になったサイトを掲載します。

http://bbs.wankuma.com/index.cgi?mode=al2&namber=161&KLOG=1
OpenFileダイアログの話ですが、下の方で発言されている名無しさんの意見が参考になります。
試していませんが、サンプル(C#)もありました。

http://www.kanazawa-net.ne.jp/~pmansato/net/net_technics.htm
ここにHookProcのサンプルがあります。
ColorDialogクラスを継承して使っております。
FolderBrowserDialogクラスに置き換えていけるかと思いましたが……
残念ながら、FolderBrowserDialogクラスは継承できないので無理でした。

私にとっては、かなり難しく感じました。初心者なので、そう感じるだけかも知れません。
RunDialogとHockProcの組み合わせは興味があります。
サンプルがあると重宝しますので、出来上がったら掲載してもらえると嬉しいです(他力本願)
投稿者 neptune  (社会人) 投稿日時 2009/3/28 22:05:06
葉月さん、こんにちは。
今日はまだ、始動してないneptuneです。

調べていただいてすみません。

http://www.kanazawa-net.ne.jp/~pmansato/net/net_technics.htm
の方は私も発見していたんですが、そうなんです。継承がFolderBrowserDialogでは
出来ないんですよね。

>私にとっては、かなり難しく感じました。初心者なので、そう感じるだけかも知れません
私も万年初心者で、VB.net以降についてはあまり勉強してないので滅茶苦茶ややこしいです。

取り合えず、APIゴリゴリでやるつもりになってきたんですが、宿題で、
frameworkでのやり方も引き続き調べていこうと思います。
って、やってみるしかないみたいですが。。。

質問の方ですが
>2.FolderBrowserDialogのframeworkを使ったHWNDの取得の仕方
はどうも関係ないような気もしてきました。
投稿者 neptune  (社会人) 投稿日時 2009/3/30 07:55:13
SHBrowseForFolder APIでササッと書こうと思っていましたが、案外苦戦中。

こんなC#なサンプルがありましたけど、それにしても少ない。
http://support.microsoft.com/kb/306285/ja

VB6やC++なら沢山あるのに、.net以降では一気に情報が少なくなりますね。
投稿者 neptune  (社会人) 投稿日時 2009/4/1 07:56:50
一応動きます程度のサンプルとしてUPしておきます。
不要な宣言とか、無駄もありますが、その辺はご勘弁。

(こんなものでも、すぐ動くサンプルは殆どなさそうなので)
初期フォルダの指定、選択したフォルダ名の表示をするようにしてます。
本気で使う時は、きっちり作りこんで下さい。

本当はC#のサンプル
http://support.microsoft.com/kb/306285/ja
をVBで書き直すのがよさそうです。

なお、これはやばいぞってな所があれば、お教え頂ければ幸いです。

Imports System.Runtime.InteropServices

Public Class  BrowseForFolder
#Region "API宣言"
    Private Const BFFM_INITIALIZED As Integer = 1
    Private Const BFFM_SELCHANGED As Integer = 2
    Private Const BFFM_VALIDATEFAILED As Integer = 3

    Private Const WM_USER As Integer = &H400
    Private Const BFFM_SETSTATUSTEXTA As Integer = (WM_USER + 100)
    Private Const BFFM_ENABLEOK As Integer = (WM_USER + 101)
    Private Const BFFM_SETSELECTION As Integer = (WM_USER + 102)

    Private Const MAX_PATH As Integer = 260

    Private BIF_EDITBOX = &H10                  'ダイアログボックス内にアイテム名入力用のテキストボックスを追加する 
    Private BIF_NEWDIALOGSTYLE = &H40

    <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)> _
    Private Structure BROWSEINFO
        Public hWndOwner As IntPtr
        Public pidlRoot As Integer
        Public pszDisplayName As IntPtr
        <MarshalAs(UnmanagedType.LPTStr)> _
        Public lpszTitle As String
        Public ulFlags As Integer
        <MarshalAs(UnmanagedType.FunctionPtr)> _
        Public lpfn As BrowseCallbackProc
        Public lParam As IntPtr
        Public iImage As Integer
    End Structure


    Private Declare Auto Function SHBrowseForFolder Lib "shell32.dll" _
                                (ByRef bi As BROWSEINFO) As IntPtr

    Private Declare Auto Function SHGetPathFromIDList Lib "shell32.dll" _
                    (ByVal pidl As IntPtr, ByVal pszPath As StringAs Integer

    Private Declare Function SendMessage Lib "user32.dll" Alias "SendMessageA" _
                    (ByVal hWnd As IntPtr, _
                     ByVal wMsg As Integer, _
                     ByVal wParam As Integer, _
                     ByVal lParam As IntegerAs Integer

    'Auto修飾子を付けたらSendMessageが失敗する(Alias "SendMessageA"がなくても失敗する。理由はわからない) 
    Private Declare Function SendMessage Lib "user32.dll" Alias "SendMessageA" _
                    (ByVal hWnd As IntPtr, _
                     ByVal wMsg As Integer, _
                     ByVal wParam As Integer, _
                     ByVal lParam As StringAs Integer

    'SHGetPathFromIDListで取得したItemIDListを開放する 
    Private Declare Function CoTaskMemFree Lib "ole32.dll" (ByVal pv As IntegerAs Integer

#End Region


#Region "Class宣言"
    Private Delegate Function BrowseCallbackProc(ByVal hwnd As IntPtr, ByVal uMsg As Integer, _
                            ByVal lParam As IntPtr, ByVal lpData As IntPtr) As Integer
    'プロパティ保管用 
    Private m_BInfo As BROWSEINFO

    Private m_Owner As IntPtr           '親ウィンドウのHWND 
    Private m_InitDirectory As String   '初期表示フォルダを指定する際に使用 
    Private m_Description As String      'STATUSTEXT 
    Private m_TitleBarText As String    'タイトルバーテキスト 
    Private m_ShowEditBox As Boolean    'EditBoxの表示指定 

#End Region

投稿者 neptune  (社会人) 投稿日時 2009/4/1 07:58:04
続き

    Public Sub New(ByVal pOwner As IntPtr)
        m_Owner = pOwner
    End Sub

    Protected Overrides Sub Finalize()
        'm_BInfo = Nothing 
        MyBase.Finalize()
    End Sub

    Public Function ShowDirDialog() As String
        Dim sRet As String = ""
        Dim pID As IntPtr

        With m_BInfo
            .hWndOwner = m_Owner
            .pidlRoot = IntPtr.Zero
            .lpszTitle = m_Description
            .ulFlags = BIF_NEWDIALOGSTYLE Or BIF_EDITBOX  'EditBox表示 
            .lpfn = AddressOf BrowseHookProc     'コールバック関数のアドレス 
        End With

        '「フォルダの参照」ダイアログの呼び出し 
        pID = SHBrowseForFolder(m_BInfo)

        If Not pID = IntPtr.Zero Then
            sRet = New String(vbNullChar, MAX_PATH)
            'SHBrowseForFolderで得られた値からフォルダのパス名を取得 
            SHGetPathFromIDList(pID, sRet)

            '割り当てられたメモリを開放 
            CoTaskMemFree(pID)
        End If

        Return sRet
    End Function

    'コールバック 
    Private Function BrowseHookProc(ByVal hwnd As IntPtr, ByVal uMsg As Integer, _
                        ByVal lParam As IntPtr, ByVal lpData As IntPtr) As Integer
        Select Case uMsg
            Case BFFM_INITIALIZED 'ダイアログ初期化 
                Dim ret As Integer = SendMessage(hwnd, BFFM_SETSELECTION, 1, _
                                 m_InitDirectory)  '初期フォルダ指定 
                'Console.WriteLine(ret.ToString()) 
                Return True
        End Select
        Return False
    End Function

    '初期フォルダを指定する 
    Public Property InitDirectory() As String
        Get
            Return m_InitDirectory
        End Get
        Set(ByVal value As String)
            m_InitDirectory = value & vbNullChar
        End Set
    End Property

    'Descriptionの設定 
    Public Property Title() As String
        Get
            Return m_Description
        End Get
        Set(ByVal value As String)
            m_Description = value
        End Set
    End Property
End Class

////////////使い方///////////
Public Class Form1

    Private Sub Button1_Click(ByVal sender As System.ObjectByVal e As System.EventArgs) Handles Button1.Click
        Dim clsBrowse As New SHBrowseForFolderClass(Me.Handle)
        Dim sRet As String

        With clsBrowse
            .InitDirectory = "E:\VS2008"
            .Title = "フォルダを選択して下さい。"
            sRet = .ShowDirDialog()
        End With

        If sRet <> "" Then
            MessageBox.Show(sRet)
        End If
    End Sub
End Class


投稿者 neptune  (社会人) 投稿日時 2009/4/3 08:30:02
え~一人で書いてます。感想です。

最初FolderBrowserDialogコントロールでのRunDialogとHockProcの使い方で質問しました。
で、結果SHBrowseForFolder を使って、RunDialogとHockProcを使わず、
APIでのソースになってしまいました。

葉月 さんから
>RunDialogとHockProcの組み合わせも、コードをガリガリ書くイメージがあります。
のような書き込みがあり、調べていたんですが、例えば、ファイルを開くダイアログにしても
RunDialogとHockProcを使っても、結局GetOpenFileName APIを使うという事に
気が付きました。
要はOFNHookProc APIの代わりにHockProcという名前になりGetOpenFileName を
動作させる為にShowDialogを使ってRunDialogを動かす仕組みになったように感じています。

はっきり言って、個人的にはそれならその仕組み自体はAPIで書いたほうが
わかり易いですし、コード量も同じようなもんと思います。
RunDialogとHockProcで書くメリットが感じられません。

先日SHBrowseForFolder の(自信なし)ソースをUPしましたが、しっかり作りこんでおけば
それで、いくらでも使い回しが効くので、他のCommonDialogもその方が楽チンかなと思ってます。

以上RunDialogとHockProc感想でした。
これにて、一件落着とします。

それにしても、RunDialogとHockProcは皆さん殆ど利用しないのかサンプルは少なかったです。

#なお、先の(自信なし)ソースでこれはやばいってな箇所があればご指摘ください。
投稿者 葉月  (社会人) 投稿日時 2009/4/4 07:02:19
neptuneさん、お疲れ様です。
実装できたようで何よりです。

提示されている
http://support.microsoft.com/kb/306285/ja
のURLにも興味があります。

FolderBrowserDialogをあまり使わないので、何時いじるかわかりませんが、
取り組んでみようと思います。

>>>#なお、先の(自信なし)ソースでこれはやばいってな箇所があればご指摘ください。 
掲載されているコードは、InitDirectoryの部分が静的に記述されていました。
ディレクトリの指定は、動的に行った方がいいと思います。
静的に行うと環境が変わった時にエラーになる可能性があります。

With clsBrowse
.InitDirectory = "E:\VS2008"
        '省略 
End With


動的に扱う例として、自分が汎用的に使っている駄目クラスの一部を載せます。
横着してC#のままコピペしています。ご了承ください。

/// <summary>
/// ユーザー名までのディレクトリを返す。
/// </summary>
/// <returns>現在、ログインしているユーザのディレクトリ</returns>
public String GetUserPath()
{
    // ドライブ名のみを取得。
    string strDrive = Directory.GetCurrentDirectory();
    strDrive = strDrive.Substring(0, strDrive.IndexOf('\\')); 

    return string.Concat(strDrive, @"\Users\", Environment.UserName);
}

/// <summary>
/// ドキュメントまでのディレクトリを返す。
/// </summary>
/// <returns>ドキュメントまでのパスを返す</returns>
public String GetDocument()
{
    return string.Concat(this.GetUserPath(), @"\Documents");
}

// 他のメソッドを省略



メイン機のOSがVistaなので、ドキュメントを基準にしています。
自分がパスを指定する際は、このクラスを中継して記述します。
それから、MSDNの命名規則に違反してたりして、微妙なクラスになっているのはご了承ください。
投稿者 neptune  (社会人) 投稿日時 2009/4/6 07:55:49
葉月さん
アドバイスありがとうございます。

私は、汎用性を持たせるときは、特殊フォルダ取得にはSHGetSpecialFolderPath APIを
使用するようにしています。
と、言ってもまだ、vb6以降では配布するようなものを作ったことないので、
本当の汎用性を持たせる際参考にさせていただきます。

所で、今日GetOpenFileNameの方でクラス作成していたんですが、構造体の
宣言って、ややこしいですねぇ。OPENFILENAME構造体なんですが、
構造体のサイズって、
.lStructSize = Marshal.SizeOf(ofn)
とやらなければならないのがなかなかわかりませんでした。

文字列のメンバも
MarshalAs(UnmanagedType・・・
ってな感じで宣言しなければならないし。
自由度は高くなりますけどその分鬱陶しいです。
Unmanaged扱いしなければならなくなった事でAPIは本当に使いにくくなった様に感じます。

なのでできれば使わないに越したことはないですね。