Starting in Windows Vista, Microsoft implemented a new common dialog control for selecting files or folders known as the Common Item Dialog.
Unlike the earlier common dialog window however, this one isn't accessible through a simple Windows API call. Instead, it requires a COM implementation, which makes it a bit trickier to access from PowerBuilder. Fortunately, this StackOverflow posting provided an example of using the dialog from within C#. Once we have that, it was only a matter of tweaking it slightly to remove it's reliance on Windows Forms classes and then creating a COM visible assembly from it that we can then invoke from a PowerBuilder Classic Win32 target via OLE Automation or from one of the .Net target types in PowerBuilder Classic or PowerBuilder.Net by adding the assembly as a reference. The resulting C# code looks like the following, and the assembly is then made COM Visible and signed so it can be added to the Global Assembly Cache if needed.
using System;
using System.Runtime.InteropServices;
namespace FolderBrowser2
{
public class FolderBrowser2
{
public string DirectoryPath { get; set; }
public int ShowDialog( ulong handle){
IntPtr hwndOwner = (IntPtr)handle;
IFileOpenDialog dialog = (IFileOpenDialog)new FileOpenDialog();
try
{
IShellItem item;
if (!string.IsNullOrEmpty(DirectoryPath))
{
IntPtr idl;
uint atts = 0;
if (SHILCreateFromPath(DirectoryPath, out idl, ref atts) == 0)
{
if (SHCreateShellItem(IntPtr.Zero, IntPtr.Zero, idl, out item) == 0)
{
dialog.SetFolder(item);
}
}
}
dialog.SetOptions(FOS.FOS_PICKFOLDERS | FOS.FOS_FORCEFILESYSTEM);
uint hr = dialog.Show(hwndOwner);
if (hr == ERROR_CANCELLED)
return FB2_CANCEL;
if (hr != 0)
return FB2_ERROR;
dialog.GetResult(out item);
string path;
item.GetDisplayName(SIGDN.SIGDN_FILESYSPATH, out path);
DirectoryPath = path;
return FB2_SUCCESS;
}
finally
{
Marshal.ReleaseComObject(dialog);
}
}
[DllImport("shell32.dll")]
private static extern int SHILCreateFromPath([MarshalAs(UnmanagedType.LPWStr)] string pszPath, out IntPtr ppIdl, ref uint rgflnOut);
[DllImport("shell32.dll")]
private static extern int SHCreateShellItem(IntPtr pidlParent, IntPtr psfParent, IntPtr pidl, out IShellItem ppsi);
[DllImport("user32.dll")]
private static extern IntPtr GetActiveWindow();
private const uint ERROR_CANCELLED = 0x800704C7;
private const int FB2_SUCCESS = 1;
private const int FB2_CANCEL = 0;
private const int FB2_ERROR = -1;
[ComImport]
[Guid("DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7")]
private class FileOpenDialog
{
}
[ComImport]
[Guid("42f85136-db7e-439c-85f1-e4075d135fc8")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IFileOpenDialog
{
[PreserveSig]
uint Show([In] IntPtr parent); // IModalWindow
void SetFileTypes(); // not fully defined
void SetFileTypeIndex([In] uint iFileType);
void GetFileTypeIndex(out uint piFileType);
void Advise(); // not fully defined
void Unadvise();
void SetOptions([In] FOS fos);
void GetOptions(out FOS pfos);
void SetDefaultFolder(IShellItem psi);
void SetFolder(IShellItem psi);
void GetFolder(out IShellItem ppsi);
void GetCurrentSelection(out IShellItem ppsi);
void SetFileName([In, MarshalAs(UnmanagedType.LPWStr)] string pszName);
void GetFileName([MarshalAs(UnmanagedType.LPWStr)] out string pszName);
void SetTitle([In, MarshalAs(UnmanagedType.LPWStr)] string pszTitle);
void SetOkButtonLabel([In, MarshalAs(UnmanagedType.LPWStr)] string pszText);
void SetFileNameLabel([In, MarshalAs(UnmanagedType.LPWStr)] string pszLabel);
void GetResult(out IShellItem ppsi);
void AddPlace(IShellItem psi, int alignment);
void SetDefaultExtension([In, MarshalAs(UnmanagedType.LPWStr)] string pszDefaultExtension);
void Close(int hr);
void SetClientGuid(); // not fully defined
void ClearClientData();
void SetFilter([MarshalAs(UnmanagedType.Interface)] IntPtr pFilter);
void GetResults([MarshalAs(UnmanagedType.Interface)] out IntPtr ppenum); // not fully defined
void GetSelectedItems([MarshalAs(UnmanagedType.Interface)] out IntPtr ppsai); // not fully defined
}
[ComImport]
[Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IShellItem
{
void BindToHandler(); // not fully defined
void GetParent(); // not fully defined
void GetDisplayName([In] SIGDN sigdnName, [MarshalAs(UnmanagedType.LPWStr)] out string ppszName);
void GetAttributes(); // not fully defined
void Compare(); // not fully defined
}
private enum SIGDN : uint
{
SIGDN_DESKTOPABSOLUTEEDITING = 0x8004c000,
SIGDN_DESKTOPABSOLUTEPARSING = 0x80028000,
SIGDN_FILESYSPATH = 0x80058000,
SIGDN_NORMALDISPLAY = 0,
SIGDN_PARENTRELATIVE = 0x80080001,
SIGDN_PARENTRELATIVEEDITING = 0x80031001,
SIGDN_PARENTRELATIVEFORADDRESSBAR = 0x8007c001,
SIGDN_PARENTRELATIVEPARSING = 0x80018001,
SIGDN_URL = 0x80068000
}
[Flags]
private enum FOS
{
FOS_ALLNONSTORAGEITEMS = 0x80,
FOS_ALLOWMULTISELECT = 0x200,
FOS_CREATEPROMPT = 0x2000,
FOS_DEFAULTNOMINIMODE = 0x20000000,
FOS_DONTADDTORECENT = 0x2000000,
FOS_FILEMUSTEXIST = 0x1000,
FOS_FORCEFILESYSTEM = 0x40,
FOS_FORCESHOWHIDDEN = 0x10000000,
FOS_HIDEMRUPLACES = 0x20000,
FOS_HIDEPINNEDPLACES = 0x40000,
FOS_NOCHANGEDIR = 8,
FOS_NODEREFERENCELINKS = 0x100000,
FOS_NOREADONLYRETURN = 0x8000,
FOS_NOTESTFILECREATE = 0x10000,
FOS_NOVALIDATE = 0x100,
FOS_OVERWRITEPROMPT = 2,
FOS_PATHMUSTEXIST = 0x800,
FOS_PICKFOLDERS = 0x20,
FOS_SHAREAWARE = 0x4000,
FOS_STRICTFILETYPES = 4
}
}
}
Once you have the assembly, if you want to call it from a PowerBuilder Classic Win32 application you'll need to take the following steps.
1. Run regasm on it to create the registry entries that PowerBuilder needs to use it via OLE Automation. If you are on a 64 bit system, you'll want to generate a reg file using the /regfile: argument and then edit it so that the entries are created in the Wow6432Node/CLSID portoin of the registry rather than the default (64bit) CLSID section.
2. Run gacutil on it to load it into the Global Assembly Cache (GAC).
To call it from a PowerBuilder Classic Win32 application, you would then only need to do the following:
integer li_rc
string ls_folder
ulong ll_handle
oleobject loo_fb2
loo_fb2 = Create oleobject
li_rc = loo_fb2.ConnectToNewObject ( "FolderBrowser2.FolderBrowser2" )
ll_handle = Handle ( parent )
li_rc = loo_fb2.ShowDialog ( ll_handle )
IF li_rc = 1 THEN
ls_folder = loo_fb2.DirectoryPath
MessageBox ( "Folder", ls_folder )
END IF
loo_fb2.DisconnectObject()
Destroy loo_fb2
This particular example looks for the user to select a folder. Slight modification of the sample would allow you to select items instead.
The sample code (both C# and PowerBuilder Classic) is available on my Goggle Drive. Simply run the FolderBrowser2_32.reg file on a 32 bit system or the FolderBrowser2_64.reg file on a 64 bit system to add the registry entries from REGASM and then run gacutil on the assembly to add it to the GAC. At that point the PowerBuilder Classic demo should run for you and you'll see a window like shown above.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
User | Count |
---|---|
4 | |
4 | |
4 | |
3 | |
3 | |
3 | |
3 | |
3 | |
3 | |
2 |