一聚教程网:一个值得你收藏的教程网站

热门教程

C#如何使用SHBrowseForFolder导出中文文件夹详解

时间:2022-06-25 07:40:18 编辑:袖梨 来源:一聚教程网

前言

从业以来,数次踩中编码的坑, 这次又马失前蹄 , 真是事不过三此非彼白.

本来这个小问题不打算拿出来说 , 但是翻看谷歌发现若干年前也有寥寥数人遇到碰到这个问题 ,而且都并没有给出一个可行的解决方案 ,现在问题依然挂在CSDN等地方 , 似乎不会再有人去回答了, 或者其实题主们后面解决了但并没有回头来提供解决方案. 现在由我来”终结此贴”

SHBrowseForFolder是一个可以用于获取文件夹路径的Windows API。使用起来可以方便很多,文中将详细介绍关于C#使用SHBrowseForFolder导出中文文件夹的相关内容 ,下面话不多说了,来一起看看详细的介绍吧

0x00.使用SHBrowseForFolder选择文件夹

(大段代码来袭 , 不想看可直接拉到底看关键的几行)

底层接口 – 选择文件夹相关

//-------------------------------------------------------------------------
class Win32API
{
 // C# representation of the IMalloc interface.
 [InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
 Guid("00000002-0000-0000-C000-000000000046")]
 public interface IMalloc
 {
 [PreserveSig]
 IntPtr Alloc([In] int cb);
 [PreserveSig]
 IntPtr Realloc([In] IntPtr pv, [In] int cb);
 [PreserveSig]
 void Free([In] IntPtr pv);
 [PreserveSig]
 int GetSize([In] IntPtr pv);
 [PreserveSig]
 int DidAlloc(IntPtr pv);
 [PreserveSig]
 void HeapMinimize();
 }

 [StructLayout(LayoutKind.Sequential, Pack = 8)]
 public struct BROWSEINFO
 {
 public IntPtr hwndOwner;
 public IntPtr pidlRoot;
 public IntPtr pszDisplayName;
 [MarshalAs(UnmanagedType.LPTStr)]
 public string lpszTitle;
 public int ulFlags;
 [MarshalAs(UnmanagedType.FunctionPtr)]
 public Shell32.BFFCALLBACK lpfn;
 public IntPtr lParam;
 public int iImage;
 }

 [Flags]
 public enum BffStyles
 {
 RestrictToFilesystem = 0x0001, // BIF_RETURNONLYFSDIRS
 RestrictToDomain = 0x0002, // BIF_DONTGOBELOWDOMAIN
 RestrictToSubfolders = 0x0008, // BIF_RETURNFSANCESTORS
 ShowTextBox = 0x0010, // BIF_EDITBOX
 ValidateSelection = 0x0020, // BIF_VALIDATE
 NewDialogStyle = 0x0040, // BIF_NEWDIALOGSTYLE
 BrowseForComputer = 0x1000, // BIF_BROWSEFORCOMPUTER
 BrowseForPrinter = 0x2000, // BIF_BROWSEFORPRINTER
 BrowseForEverything = 0x4000, // BIF_BROWSEINCLUDEFILES
 }

 [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
 public class OpenFileName
 {
 public int structSize = 0;
 public IntPtr dlgOwner = IntPtr.Zero;
 public IntPtr instance = IntPtr.Zero;
 public String filter = null;
 public String customFilter = null;
 public int maxCustFilter = 0;
 public int filterIndex = 0;
 public String file = null;
 public int maxFile = 0;
 public String fileTitle = null;
 public int maxFileTitle = 0;
 public String initialDir = null;
 public String title = null;
 public int flags = 0;
 public short fileOffset = 0;
 public short fileExtension = 0;
 public String defExt = null;
 public IntPtr custData = IntPtr.Zero;
 public IntPtr hook = IntPtr.Zero;
 public String templateName = null;
 public IntPtr reservedPtr = IntPtr.Zero;
 public int reservedInt = 0;
 public int flagsEx = 0;
 }

 public class Shell32
 {
 public delegate int BFFCALLBACK(IntPtr hwnd, uint uMsg, IntPtr lParam, IntPtr lpData);

 [DllImport("Shell32.DLL")]
 public static extern int SHGetMalloc(out IMalloc ppMalloc);

 [DllImport("Shell32.DLL")]
 public static extern int SHGetSpecialFolderLocation(
   IntPtr hwndOwner, int nFolder, out IntPtr ppidl);

 [DllImport("Shell32.DLL")]
 public static extern int SHGetPathFromIDList(
   IntPtr pidl, byte[] pszPath);

 [DllImport("Shell32.DLL", CharSet = CharSet.Auto)]
 public static extern IntPtr SHBrowseForFolder(ref BROWSEINFO bi);
 }

 public class User32
 {
 public delegate bool delNativeEnumWindowsProc(IntPtr hWnd, IntPtr lParam);

 [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
 public static extern bool EnumWindows(delNativeEnumWindowsProc callback, IntPtr extraData);

 [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
 public static extern int GetWindowThreadProcessId(HandleRef handle, out int processId);
 }
}
//-------------------------------------------------------------------------
class Win32Instance
{
 //-------------------------------------------------------------------------
 private HandleRef unityWindowHandle;
 private bool bUnityHandleSet;
 //-------------------------------------------------------------------------
 public IntPtr GetHandle(ref bool bSuccess)
 {
 bUnityHandleSet = false;
 Win32API.User32.EnumWindows(__EnumWindowsCallBack, IntPtr.Zero);
 bSuccess = bUnityHandleSet;
 return unityWindowHandle.Handle;
 }
 //-------------------------------------------------------------------------
 private bool __EnumWindowsCallBack(IntPtr hWnd, IntPtr lParam)
 {
 int procid;

 int returnVal =
  Win32API.User32.GetWindowThreadProcessId(new HandleRef(this, hWnd), out procid);

 int currentPID = System.Diagnostics.Process.GetCurrentProcess().Id;

 HandleRef handle =
  new HandleRef(this, 
  System.Diagnostics.Process.GetCurrentProcess().MainWindowHandle);

 if (procid == currentPID)
 {
  unityWindowHandle = new HandleRef(this, hWnd);
  bUnityHandleSet = true;
  return false;
 }

 return true;
 }
}
//-------------------------------------------------------------------------

简单介绍一下 Win32API 所有接口的结构体 都是参照SHBrowseForFolder函数而写 , Win32Instance 主要是精确的获取当前进程的ID

接下来是 获取文件夹路径的简单例子

//-------------------------------------------------------------------------
private void __SelectFolder(out string directoryPath)
{
 directoryPath = "null";
 try
 {
 IntPtr pidlRet = IntPtr.Zero;
 int publicOptions = (int)Win32API.BffStyles.RestrictToFilesystem |
 (int)Win32API.BffStyles.RestrictToDomain;
 int privateOptions = (int)Win32API.BffStyles.NewDialogStyle;

 // Construct a BROWSEINFO.
 Win32API.BROWSEINFO bi = new Win32API.BROWSEINFO();
 IntPtr buffer = Marshal.AllocHGlobal(1024);
 int mergedOptions = (int)publicOptions | (int)privateOptions;
 bi.pidlRoot = IntPtr.Zero;
 bi.pszDisplayName = buffer;
 bi.lpszTitle = "文件夹";
 bi.ulFlags = mergedOptions;

 Win32Instance w = new Win32Instance();
 bool bSuccess = false;
 IntPtr P = w.GetHandle(ref bSuccess);
 if (true == bSuccess)
 {
  bi.hwndOwner = P;
 }

 pidlRet = Win32API.Shell32.SHBrowseForFolder(ref bi);
 Marshal.FreeHGlobal(buffer);

 if (pidlRet == IntPtr.Zero)
 {
  // User clicked Cancel.
  return;
 }
 
 byte[] pp = new byte[2048];
 if (0 == Win32API.Shell32.SHGetPathFromIDList(pidlRet, pp))
 {
  return;
 }

 int nSize = 0;
 for (int i = 0; i < 2048; i++)
 {
  if (0 != pp[i])
  {
  nSize++;
  }
  else
  {
  break;
  }

 }

 if (0 == nSize)
 {
  return;
 }

 byte[] pReal = new byte[nSize];
 Array.Copy(pp, pReal, nSize);
 // 关键转码部分
 Gb2312Encoding gbk = new Gb2312Encoding();
 Encoding utf8 = Encoding.UTF8;
 byte[] utf8Bytes = Encoding.Convert(gbk, utf8, pReal);
 string utf8String = utf8.GetString(utf8Bytes);
 utf8String = utf8String.Replace("", "");
 directoryPath = utf8String.Replace("", "/") + "/";

 }
 catch (Exception e)
 {
 Console.WriteLine("获取文件夹目录出错:" + e.Message);
 }
}

以上用到的一个GBK转码库 位置查看 - github传送门

0x01.GBK转码

以下是关键的一段代码:

Gb2312Encoding gbk = new Gb2312Encoding();
Encoding utf8 = Encoding.UTF8;
byte[] utf8Bytes = Encoding.Convert(gbk, utf8, pReal);
string utf8String = utf8.GetString(utf8Bytes);
utf8String = utf8String.Replace("", "");

谷歌上找到的一个方案是把项目编码全部改为unicode , 但是C#项目里貌似没这个设定 , 所以使用SHGetPathFromIDList拿出的数据直接转码即可支持中文.(全部为英文的路径也不会有影响)

热门栏目