2

I'm attempting to p/invoke the SHBrowseForFolder API to prompt the user to select a folder and I am seeing the title static / label field float on top of the tree control.

Folder browse with bad layout

The code was downloaded from an article referenced in multiple place on the net and the converted from C# to VB.NET (it was being dropped into an existing app).

If anyone has any tips on either getting this to layout correctly, please enlighten me.

The calling code is simple:

  Using getLocDialog As New FolderBrowserDialog()

     getLocDialog.Description = "This is a test"
     getLocDialog.ShowDialog()

  End Using

Next, here is the VB.NET version of the code:

Public Class FolderBrowserDialog
   Inherits CommonDialog

   '---------------------------------------------------------------------------
   ' instance
   '---------------------------------------------------------------------------

   Private info As BrowseInfo
   Private folder As String = String.Empty

   '---------------------------------------------------------------------------
   ' ctor
   '---------------------------------------------------------------------------

   Public Sub New()
      info = New BrowseInfo()
      info.Title = String.Empty
      InitCommonControls()
   End Sub

   '---------------------------------------------------------------------------
   ' public
   '---------------------------------------------------------------------------

   Public Shadows Function ShowDialog() As DialogResult

      Dim pitemidlist As IntPtr

      Try
         pitemidlist = SHBrowseForFolder(info.ToByteArray())
      Catch mme As MissingMethodException
         Throw New PlatformNotSupportedException("Your platform doesn't support the SHBrowseForFolder API", mme)
      End Try

      If pitemidlist = IntPtr.Zero Then
         Return DialogResult.Cancel
      End If

      '//maxpath unicode chars
      Dim buffer As Byte() = New Byte(519) {}
      Dim success As Boolean = SHGetPathFromIDList(pitemidlist, buffer)
      '//get string from buffer
      If success Then

         folder = System.Text.Encoding.Unicode.GetString(buffer, 0, buffer.Length)

         Dim nullindex As Integer = folder.IndexOf(Convert.ToChar(0))
         If nullindex <> -1 Then
            folder = folder.Substring(0, nullindex)
         End If
      End If
      LocalFree(pitemidlist)

      Return DialogResult.OK

   End Function

   Public ReadOnly Property SelectedPath() As String
      Get
         Return folder
      End Get
   End Property

   Public Property Description() As String
      Get
         Return info.Title
      End Get
      Set(ByVal value As String)
         info.Title = value
      End Set
   End Property

   '---------------------------------------------------------------------------
   ' private 
   '---------------------------------------------------------------------------

   <DllImport("commctrl", SetLastError:=True)> _
   Private Shared Sub InitCommonControls()
   End Sub

   <DllImport("ceshell", SetLastError:=True)> _
   Private Shared Function SHBrowseForFolder(ByVal lpbi As Byte()) As IntPtr
   End Function

   <DllImport("ceshell", SetLastError:=True)> _
   Private Shared Function SHGetPathFromIDList(ByVal pidl As IntPtr, ByVal pszPath As Byte()) As Boolean
   End Function

   <DllImport("coredll", SetLastError:=True)> _
   Private Shared Function LocalFree(ByVal ptr As IntPtr) As IntPtr
   End Function

   '---------------------------------------------------------------------------
   ' inner classes
   '---------------------------------------------------------------------------


   Private Class BrowseInfo

      Private m_data As Byte()
      Private m_displayname As Byte()
      Private m_title As Byte()
      Private namehandle As GCHandle
      Private titlehandle As GCHandle

      Public Sub New()

         m_data = New Byte(31) {}
         m_displayname = New Byte(511) {}
         m_title = New Byte(127) {}

         namehandle = GCHandle.Alloc(m_displayname, GCHandleType.Pinned)
         titlehandle = GCHandle.Alloc(m_title, GCHandleType.Pinned)

         BitConverter.GetBytes(namehandle.AddrOfPinnedObject().ToInt32 + 4).CopyTo(m_data, 8)
         BitConverter.GetBytes(titlehandle.AddrOfPinnedObject().ToInt32 + 4).CopyTo(m_data, 12)

      End Sub

      Public Function ToByteArray() As Byte()
         Return m_data
      End Function

      Protected Overrides Sub Finalize()
         namehandle.Free()
         titlehandle.Free()
         MyBase.Finalize()
      End Sub

      Public Property Title() As String
         Get
            Dim ttl As String = System.Text.Encoding.Unicode.GetString(m_title, 0, m_title.Length)
            Dim nullindex As Integer = ttl.IndexOf(Convert.ToChar(0))
            If nullindex = -1 Then

               Return ttl
            End If
            Return ttl.Substring(0, ttl.IndexOf(Convert.ToChar(0)))
         End Get
         Set(ByVal value As String)
            Dim titlebytes As Byte() = System.Text.Encoding.Unicode.GetBytes(value & Convert.ToChar(0))
            If titlebytes.Length > m_title.Length Then
               Throw New ArgumentException("Description must be no longer than 64 characters")
            End If
            Try
               Buffer.BlockCopy(titlebytes, 0, m_title, 0, titlebytes.Length)
            Catch ex As Exception
            End Try
         End Set
      End Property

      Public Property FileName() As String
         Get
            Dim fn As String = System.Text.Encoding.Unicode.GetString(m_displayname, 0, m_displayname.Length)
            Dim nullindex As Integer = fn.IndexOf(Convert.ToChar(0))
            If nullindex = -1 Then
               Return fn
            End If
            Return fn.Substring(0, fn.IndexOf(Convert.ToChar(0)))
         End Get
         Set(ByVal value As String)
            Dim filenamebytes As Byte() = System.Text.Encoding.Unicode.GetBytes(value & Convert.ToChar(0))
            If filenamebytes.Length > m_title.Length Then
               Throw New ArgumentException("SelectedFolder must be no longer than 256 characters")
            End If
            Buffer.BlockCopy(filenamebytes, 0, m_displayname, 0, filenamebytes.Length)
         End Set
      End Property

      '/*HWND hwndOwner;
      'LPCITEMIDLIST pidlRoot;
      'LPTSTR pszDisplayName;
      'LPCTSTR lpszTitle;
      'UINT ulFlags;
      'BFFCALLBACK lpfn;
      'LPARAM lParam;
      'int iImage;*/

   End Class

End Class

The original C# was this:

public class FolderBrowserDialog : CommonDialog
    {
        private BrowseInfo info;
        private string folder = string.Empty;

        /// <summary>
        /// Initializes a new instance of the FolderBrowserDialog class.
        /// </summary>
        public FolderBrowserDialog()
        {
            info = new BrowseInfo();
            info.Title = string.Empty;
            InitCommonControls();
        }

        /// <summary>
        /// Runs a common dialog box with a default owner.
        /// </summary>
        /// <returns></returns>
        public new DialogResult ShowDialog()
        {
            IntPtr pitemidlist;

            try
            {
                pitemidlist = SHBrowseForFolder(info.ToByteArray());
            }
            catch(MissingMethodException mme)
            {
                throw new PlatformNotSupportedException("Your platform doesn't support the SHBrowseForFolder API",mme);
            }

            if(pitemidlist==IntPtr.Zero)
            {
                return DialogResult.Cancel;
            }

            //maxpath unicode chars
            byte[] buffer = new byte[520];
            bool success = SHGetPathFromIDList(pitemidlist, buffer);
            //get string from buffer
            if(success)
            {
                folder = System.Text.Encoding.Unicode.GetString(buffer, 0, buffer.Length);

                int nullindex = folder.IndexOf('\0');
                if(nullindex!=-1)
                {
                    folder = folder.Substring(0, nullindex);
                }
            }
            LocalFree(pitemidlist);

            return DialogResult.OK;
        }

        /// <summary>
        /// Gets the path selected by the user.
        /// </summary>
        public string SelectedPath
        {
            get
            {
                return folder;
            }
        }

        /// <summary>
        /// Gets or sets the descriptive text displayed above the tree view control in the dialog box.
        /// </summary>
        public string Description
        {
            get
            {
                return info.Title;
            }
            set
            {
                info.Title = value;
            }
        }


        #region P/Invokes

        [DllImport("commctrl", SetLastError=true)]
        private static extern void InitCommonControls();

        [DllImport("ceshell", SetLastError=true)]
        private static extern IntPtr SHBrowseForFolder(byte[] lpbi);

        [DllImport("ceshell", SetLastError=true)]
        private static extern bool SHGetPathFromIDList(IntPtr pidl, byte[] pszPath); 

        [DllImport("coredll", SetLastError=true)]
        private static extern IntPtr LocalFree(IntPtr ptr);

        #endregion

        #region helper class for BROWSEINFO struct
        private class BrowseInfo
        {
            private byte[] m_data;
            private byte[] m_displayname;
            private byte[] m_title;
            private GCHandle namehandle;
            private GCHandle titlehandle;

            public BrowseInfo()
            {
                m_data = new byte[32];
                m_displayname = new byte[512];
                m_title = new byte[128];

                namehandle = GCHandle.Alloc(m_displayname, GCHandleType.Pinned);
                titlehandle = GCHandle.Alloc(m_title, GCHandleType.Pinned);

                BitConverter.GetBytes((int)namehandle.AddrOfPinnedObject() + 4).CopyTo(m_data, 8);
                BitConverter.GetBytes((int)titlehandle.AddrOfPinnedObject() + 4).CopyTo(m_data, 12);
            }

            public byte[] ToByteArray()
            {
                return m_data;
            }

            ~BrowseInfo()
            {
                namehandle.Free();
                titlehandle.Free();
            }

            public string Title
            {
                get
                {
                    string title = System.Text.Encoding.Unicode.GetString(m_title, 0, m_title.Length);
                    int nullindex = title.IndexOf('\0');
                    if(nullindex==-1)
                    {
                        return title;
                    }
                    return title.Substring(0, title.IndexOf('\0'));
                }
                set
                {
                    byte[] titlebytes = System.Text.Encoding.Unicode.GetBytes(value + '\0');
                    if(titlebytes.Length > m_title.Length)
                    {
                        throw new ArgumentException("Description must be no longer than 64 characters");
                    }
                    try
                    {
                        Buffer.BlockCopy(titlebytes, 0, m_title,0, titlebytes.Length); 
                    }
                    catch
                    {
                    }
                }
            }

            public string FileName
            {
                get
                {
                    string filename = System.Text.Encoding.Unicode.GetString(m_displayname, 0, m_displayname.Length);
                    int nullindex = filename.IndexOf('\0');
                    if(nullindex==-1)
                    {
                        return filename;
                    }
                    return filename.Substring(0, filename.IndexOf('\0'));
                }
                set
                {
                    byte[] filenamebytes = System.Text.Encoding.Unicode.GetBytes(value + '\0');
                    if(filenamebytes.Length > m_title.Length)
                    {
                        throw new ArgumentException("SelectedFolder must be no longer than 256 characters");
                    }
                    Buffer.BlockCopy(filenamebytes, 0, m_displayname,0, filenamebytes.Length); 
                }
            }

                        /*HWND hwndOwner;
                        LPCITEMIDLIST pidlRoot;
                        LPTSTR pszDisplayName;
                        LPCTSTR lpszTitle;
                        UINT ulFlags;
                        BFFCALLBACK lpfn;
                        LPARAM lParam;
                        int iImage;*/

        }
        #endregion
    }

Additional details: Dev environment is Visual Studio 2008, Compact Framework 3.5. The device runs Windows CE 4.2.

tcarvin
  • 10,715
  • 3
  • 31
  • 52

1 Answers1

1

I had the same experience with SHBrowseForFolder, that FolderBrowserDialog and Windows CE 5.0.

A workaround could be to set BIF_STATUSTEXT for the flags. This way the layout is displaced and the title appears above the TreeView.

To do this, add a data member uint m_flags to the BrowseInfo class. In it's constructor add :

const uint BIF_STATUSTEXT = 0x00000004;
m_flags |= BIF_STATUSTEXT;

And in the ToByteArray() method add:

BitConverter.GetBytes(m_flags).CopyTo(m_data, 16);
John Ledbetter
  • 13,557
  • 1
  • 61
  • 80
Georgi
  • 11
  • 1
  • Didn't quite work. While the title label was surpressed, it caused the top of treeview to slide down such that the top 30% of the dialog is just blank. – tcarvin Mar 05 '14 at 17:06