欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2023-12-04 16:37:12
前言 从业以来,数次踩中编码的坑, 这次又马失前蹄 , 真是事不过三此非彼白. 本来这个小问题不打算拿出来说 , 但是翻看谷歌发现若干年前也有寥寥数人遇到碰到这个问题...

前言

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

本来这个小问题不打算拿出来说 , 但是翻看谷歌发现若干年前也有寥寥数人遇到碰到这个问题 ,而且都并没有给出一个可行的解决方案 ,现在问题依然挂在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("\0", "");
 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("\0", "");

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

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。