C# - Mappa választó dialóg ablak

forráskód letöltése
A Windows-ból jól ismert mappa választó dialóg ablakot varázsoljuk elő ebben a cikkben. Az ablak segítségével a helyi gépen, illetve a hálózat valamely gépén egy tetszőleges mappát választhatunk ki, vagy akár egy újat is létrehozhatunk. A választás után megkapjuk az adott mappa teljes elérési útvonalát, melyet tetszés szerint felhasználhatunk.
Mellékelt példában készítünk egy komponenst, melyet feltéve bármely Form-ra, a mappa választó ablakot könnyedén megjeleníthetjük.
A projekt lefordítása után a Toolbox-ra felvehető egy BrowseForFolder komponens.
A megoldáshoz a shell32.dll-ben lévő SHBrowseForFolder függvényt kell felhasználnunk, mely megjeleníti a mappa választó ablakot. Mivel a függvény nem része a .NET Framework-nek, így külsőként kell deklarálnunk azt. Mivel a függvény mutatókkal dolgozik, így használnunk kell az unsafe kulcsszót a függvény deklarációjában.
    [DllImport("Shell32.dll", EntryPoint="SHBrowseForFolder")]
    private unsafe static extern ITEMIDLIST * BrowseForFolders(ref BROWSEINFO browseInfo);
Ahhoz, hogy alkalmazásunkban olyan függvényeket használjunk, melyeknél meg van adva az unsafe kulcsszó, engedélyeznünk kell a projekt konfigurációs beállításainál. Ehhez válasszuk a Project - Properties menüpontot, majd a megjelenő ablakban a Configuration Properties - Build elemet. A jobb oldali listában az Allow unsafe code blocks értékét állítsuk igazra.
A teljes megvalósításhoz szükségünk lesz további külső DLL-ekben lévő függvényekre. Ezeket szintén deklaráljuk.
Továbbá meg kell adnunk az IMalloc interfész deklarációját is.
    [Guid("00000002-0000-0000-C000-000000000046")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    internal interface IMalloc
    {
      [PreserveSig]
      IntPtr Alloc([MarshalAs(UnmanagedType.U4)] int cb);
      [PreserveSig]
      IntPtr Realloc(IntPtr pv, [MarshalAs(UnmanagedType.U4)] int cb);
      [PreserveSig]
      void Free(IntPtr pv);
      [PreserveSig]
      [return: MarshalAs(UnmanagedType.U4)]
      int GetSize(IntPtr pv);
      [PreserveSig]
      int DidAlloc(IntPtr pv);
      [PreserveSig]
      void HeapMinimize();
    }
A rendszerünkben vannak olyan speciális mappák, melyeknek elérési helye gépenként változó lehet. Amennyiben ezeket szeretnénk elérni, használhatjuk majd az alábbi SystemFolders felsorolt típus elemeit. Így például a Programs elem a Program files mappára mutat, melynek alapértelmezett helye a C:\Program files. Mivel ez gépenként más-más mappába is kerülhet, így ha a programok mappáját szeretnénk elérni, akkor arra nem tényleges elérési úttal, hanem a SystemFolders Programs elemével hivatkozhatunk.
    public enum SystemFolders
    {
      Desktop = 0x0,
      Internet = 0x1,
      Programs = 0x2,
      ControlPanel = 0x3,
      Printers = 0x4,
      ...
    }
A megjelenő dialóg ablak tulajdonságait befolyásolhatjuk a Flags felsorolt típus elemeinek választásával.
    public enum Flags
    {
      ReturnOnlyFileSystemFolders = 0x1,
      DontGoBelowDomain = 0x2,
      StatusText = 0x4,
      ...
    }
Létrehozunk egy InitialPath nevű property-t, ezen keresztül megadhatunk egy olyan elérési útvonalat, amelyben tallózni szeretnénk a mappákat.
    private string initialPath;
    public string InitialPath
    {
      get
      { 
        return initialPath;
      }
      set
      {
        initialPath = value;
      }
    }
Ha nem használjuk az InitialPath property-t, akkor a RootFolder property-n keresztül kiválaszthatunk egy előre definiált elérési utat a SystemFolders elemi közül.
    private SystemFolders rootFolder;
    public SystemFolders RootFolder
    {
      get
      { 
        return rootFolder;
      }
      set
      {
        rootFolder = value;
      }
    }
Szükségünk lesz még egy Prompt nevű property-re. Ebben egy tetszőleges sztringet megadhatunk. Ez a szöveg megjelenik a dialóg ablakban, így tetszőleges üzenetet küldhetünk a felhasználónak.
    private string prompt;
    public string Prompt
    {
      get
      { 
        return prompt;
      }
      set
      {
        prompt = value;
      }
    }
Dialóg ablak megjelenítéséhez az Execute függvényt kell meghívnunk. Első paraméterében a hívó ablak azonosítóját, mint szülő ablakot kell megadnunk, másodikban a Flags felsorolt típus elemeinek tetszőleges kombinációját.
    public unsafe string Execute(IntPtr handle, Flags flags)
    {
      BROWSEINFO browseInfo = new BROWSEINFO();
      ITEMIDLIST * itemIdList = null;
      IMalloc malloc = null;
      Marshal.ThrowExceptionForHR(GetSpecialFolderLocation(handle, rootFolder, out itemIdList));
      try
      {
        Marshal.ThrowExceptionForHR(GetMalloc(out malloc));
        Callbacks callbacks = new Callbacks(initialPath);
A már létrehozott BROWSEINFO struktúrát fel kell töltenünk a megfelelő adatokkal. Így például a hwndOwner mezőbe kerül a szülő ablak azonosítója, a pidlRoot mezőbe kerül a kiinduló mappa elérési útvonalának azonosítója, melyet a Shell_GetSpecialFolderLocation függvény szolgáltat az itemIdList változóba.
        browseInfo.hwndOwner = handle;
        browseInfo.pidlRoot = new IntPtr(itemIdList);
        browseInfo.lpszTitle = prompt;
        browseInfo.ulFlags = (uint) flags;
        browseInfo.lpfn = new BrowseForFolderCallbackProc(callbacks.BrowseForFolderCallback);
        browseInfo.lParam = 0;
Ezt követően meghívhatjuk a Shell_BrowseForFolder függvényt, mely megjeleníti a mappa választó ablakot. Visszatérési értékként egy mutatót kapunk, mely ITEMIDLIST típusra mutat.
        itemIdList = BrowseForFolders(ref browseInfo);
        if (itemIdList == null)
        {
          return "";
        }
Ahhoz, hogy ebből szöveges elérési útvonal legyen, a Shell_GetPathFromIDList függvényünket kell meghívni.
        StringBuilder path = new StringBuilder(260);
        GetPathFromIDList(itemIdList, path);  
        return path.ToString();
      }
      finally
      {
        ...
      }
    }
  }
}
Ezt követően a komponens felhasználása már egyszerű, a szükséges property beállítása után csak meg kell hívni az Execute függvényét. Visszatérési értékként a választott mappát kapjuk meg, illetve üres sztringet kapunk, amennyiben a felhasználó a Mégsem gombbal zárta be az ablakot.
    private void button1_Click(object sender, System.EventArgs e)
    {
      label1.Text = browseForFolder1.Execute(this.Handle, BrowseForFolder.Flags.NewDialogStyle);
    }