Unity在Windows操作系统下使用保存和打开文件的对话框
前言
这里我仅说明使用CSharp的情况。其实网上已经有很多类似的文章,你在网上搜便能找到一堆。下面的代码其实也是我从网络上抄过来的。但是大多数的文章就仅仅是给代码然后不说为什么这样。如果只是满足当前的需求,那这样做也没什么问题。只是当这些代码出现了一些问题,或者突然有些需求要进行扩展的时候,这些如同黑盒子一样的代码就显得麻烦了。
因为我很懒,所以我这里的代码说明也只到我现在使用的程度。
环境
Windows10
Unity 2022.3.8f1c1
正文
要想在Windows操作系统下打开其保存和打开文件的对话框,我们必须要使用其Comdlg32.dll
。如果你有搜索并看过这些方面的文章。你会发现文章中都会预先让你声明一个结构体:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto) ] public struct FileOpenDialog{ public int structSize; public IntPtr dlgOwner; public IntPtr instance; public string filter; public string customFilter; public int maxCustFilter; public int filterIndex; public string file; public int maxFile; public string fileTitle; public int maxFileTitle; public string initialDir; public string title; public int flags; public short fileOffset; public short fileExtension; public string defExt; public IntPtr custData; public IntPtr hook; public string templateName; public IntPtr reservedPtr; public int reservedInt; public int flagsEx; }
这个结构体是用来和Comdlg32.dll
中的GetOpenFileName
和GetSaveFileName
进行信息传输的类。这个其实是OPENFILENAME
(关于其信息和上述变量的意思,可查看微软的文档:OPENFILENAMEA
结构 (commdlg.h) )的封装实现。
2025.2.27修正:将类改为结构体。这里并不能使用类来做操作,这样会导致在选取多个文件时结果错误,具体缘由我未能找到。我只能猜测是因为类和结构体在赋值方式上的不同导致的。
我们为了调用Comdlg32.dll
中的GetOpenFileName
和GetSaveFileName
。我们需要写入下面的代码:
1 2 3 4 5 6 7 8 public class DialogShow { [DllImport("Comdlg32.dll" , SetLastError = true, ThrowOnUnmappableChar = true, CharSet = CharSet.Auto) ] public static extern bool GetOpenFileName ([In, Out] FileOpenDialog dialog ) ; [DllImport("Comdlg32.dll" , SetLastError = true, ThrowOnUnmappableChar = true, CharSet = CharSet.Auto) ] public static extern bool GetSaveFileName ([In, Out] FileOpenDialog dialog ) ; }
而在微软文档中也是有类似的声明(下面只显示GetOpenFileName文档 的声明)
1 2 3 BOOL GetOpenFileNameA ( [in, out] LPOPENFILENAMEA unnamedParam1 ) ;
虽然这里的函数名字为GetOpenFileNameA
,但是其实就是GetOpenFileName
。接下来我们就要进行具体的操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 public static string OpenFile (string title, string openPath = "" ){ FileOpenDialog dialog = new FileOpenDialog(); dialog.structSize = Marshal.SizeOf(dialog); dialog.filter = "Json Files(*json文件)\0*.json\0" ; dialog.file = new string (new char [256 ]); dialog.maxFile = dialog.file.Length; dialog.fileTitle = new string (new char [64 ]); dialog.maxFileTitle = dialog.fileTitle.Length; dialog.initialDir = string .IsNullOrEmpty(openPath) ? UnityEngine.Application.dataPath : openPath; dialog.title = title; dialog.defExt = null ; dialog.flags = 0x00080000 | 0x00001000 | 0x00000800 | 0x00000200 | 0x00000008 ; if (DialogShow.GetOpenFileName(dialog)) { return (dialog.file); } return "" ; } public static string SaveFile (string fileName, string title, string openPath = "" ,string filter = "" ,string defExt = "" ){ FileOpenDialog pth = new FileOpenDialog(); pth.structSize = Marshal.SizeOf(pth); pth.filter = string .IsNullOrEmpty(filter) ? "All Files\0*.*\0\0" : filter; pth.file = new string (new char [256 ]); pth.maxFile = pth.file.Length; pth.file = fileName; pth.fileTitle = new string (new char [64 ]); pth.maxFileTitle = pth.fileTitle.Length; pth.initialDir = string .IsNullOrEmpty(openPath) ? UnityEngine.Application.dataPath : openPath; pth.title = title; pth.defExt = defExt; pth.flags = 0x00080000 | 0x00001000 | 0x00000800 | 0x00000200 | 0x00000008 | 0x00000002 ; if (DialogShow.GetSaveFileName(pth)) { return (pth.file); } return "" ; }
其中关于其变量设置的说明在OPENFILENAMEA
结构
(commdlg.h) 文档中都有。这其中特别是关于flags
变量的赋值。其不同的赋值会导致我们所设定的变量值不同。具体的大家还是看文档吧。这里我特别说明一下filter
变量,虽然你们看文档也能知道这点,但我还是额外说明一下。文档中对于其的部分描述:
1 每对中的第一个字符串是描述筛选器 (的显示字符串,例如,“文本文件”) ,第二个字符串指定筛选器模式 (例如 ".TXT" ,) 。 若要为单个显示字符串指定多个筛选器模式,请使用分号分隔模式 (例如 “.TXT;.DOC;”。BAK“) 。 模式字符串可以是有效文件名字符和星号 (*) 通配符的组合。 请勿在模式字符串中包含空格。
从此描述中,其实我们可传两个字符串。但是我们实际上将其声明为一个单独的string
类型,所以这里我使用了\0
来分隔字符串。关于\0
,我在网上找到了这篇文章——关于字符串中‘\0‘的坑 。
完整代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 using System;using System.Runtime.InteropServices;using UnityEngine;public class SelfOpenFile { [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto) ] public struct FileOpenDialog { public int structSize; public IntPtr dlgOwner; public IntPtr instance; public String filter; public String customFilter; public int maxCustFilter; public int filterIndex; public String file; public int maxFile; public String fileTitle; public int maxFileTitle; public String initialDir; public String title; public int flags; public short fileOffset; public short fileExtension; public String defExt; public IntPtr custData; public IntPtr hook; public String templateName; public IntPtr reservedPtr; public int reservedInt; public int flagsEx; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto) ] public struct OpenDialogDir { public IntPtr hwndOwner; public IntPtr pidlRoot; public String pszDisplayName; public String lpszTitle; public UInt32 ulFlags; public IntPtr lpfn; public IntPtr lParam; public int iImage; } public class DialogShow { [DllImport("Comdlg32.dll" , SetLastError = true, ThrowOnUnmappableChar = true, CharSet = CharSet.Auto) ] public static extern bool GetOpenFileName ([In, Out] FileOpenDialog dialog ) ; [DllImport("Comdlg32.dll" , SetLastError = true, ThrowOnUnmappableChar = true, CharSet = CharSet.Auto) ] public static extern bool GetSaveFileName ([In, Out] FileOpenDialog dialog ) ; [DllImport("shell32.dll" , SetLastError = true, ThrowOnUnmappableChar = true, CharSet = CharSet.Auto) ] public static extern IntPtr SHBrowseForFolder ([In, Out] OpenDialogDir ofn ) ; [DllImport("shell32.dll" , SetLastError = true, ThrowOnUnmappableChar = true, CharSet = CharSet.Auto) ] public static extern bool SHGetPathFromIDList ([In] IntPtr pidl, [In, Out] char [] fileName ) ; } public static string OpenFile (string title, string openPath = "" ) { FileOpenDialog dialog = new FileOpenDialog(); dialog.structSize = Marshal.SizeOf(dialog); dialog.filter = Getfilter(); dialog.file = new string (new char [256 ]); dialog.maxFile = dialog.file.Length; dialog.fileTitle = new string (new char [64 ]); dialog.maxFileTitle = dialog.fileTitle.Length; dialog.initialDir = string .IsNullOrEmpty(openPath) ? UnityEngine.Application.dataPath : openPath; dialog.title = title; dialog.defExt = null ; dialog.flags = 0x00080000 | 0x00001000 | 0x00000800 | 0x00000200 | 0x00000008 ; if (DialogShow.GetOpenFileName(dialog)) { return (dialog.file); } return "" ; } public static string SaveFile (string fileName, string title, string openPath = "" ,string filter = "" ,string defExt = "" ) { FileOpenDialog pth = new FileOpenDialog(); pth.structSize = Marshal.SizeOf(pth); pth.filter = string .IsNullOrEmpty(filter) ? "All Files\0*.*\0\0" : filter; pth.file = new string (new char [256 ]); pth.maxFile = pth.file.Length; pth.file = fileName; pth.fileTitle = new string (new char [64 ]); pth.maxFileTitle = pth.fileTitle.Length; pth.initialDir = string .IsNullOrEmpty(openPath) ? UnityEngine.Application.dataPath : openPath; pth.title = title; pth.defExt = defExt; pth.flags = 0x00080000 | 0x00001000 | 0x00000800 | 0x00000200 | 0x00000008 | 0x00000002 ; if (DialogShow.GetSaveFileName(pth)) { return (pth.file); } return "" ; } public static string ChooseDictionary (string title, string openPath = "" ) { OpenDialogDir openDir = new OpenDialogDir(); openDir.pszDisplayName = new string (new char [2000 ]); openDir.lpszTitle = title; openDir.ulFlags = 1 ; IntPtr pidl = DialogShow.SHBrowseForFolder(openDir); char [] path = new char [2000 ]; for (int i = 0 ; i < 2000 ; i++) path[i] = '\0' ; if (DialogShow.SHGetPathFromIDList(pidl, path)) { string str = new string (path); string DirPath = str.Substring(0 , str.IndexOf('\0' )); return DirPath; } return "" ; } static string Getfilter () { string filter = "Json Files(*json文件)\0*.json\0" ; return filter; } }
吐槽
这篇文章好像有点水,不过很多东西在微软的文章中也说的够详细了,想要直接使用的人也就只要赋值完整的代码用就好了。所以我这不算水吧(手动狗头)。
参考文章
OPENFILENAMEA 结构
(commdlg.h):https://learn.microsoft.com/zh-cn/windows/win32/api/commdlg/ns-commdlg-openfilenamea
GetOpenFileName文档:https://learn.microsoft.com/zh-cn/windows/win32/api/commdlg/nf-commdlg-getopenfilenamea
关于字符串中‘\0‘的坑:https://blog.csdn.net/qq_37286579/article/details/129926484