赞
踩
目录
最近项目中遇到了需要调用系统对话框选择文件的问题,上网搜索时发现很多教程不够详细,费了不少功夫才解决。在项目开发时,程序运行在UnityEditor环境中,而打包后的程序须在Windows环境下运行,这两种环境调用系统文件对话框的方法不同,以下是我的思路
当项目在运行在UnityEditor时,可调用EditorUtility.OpenFilePanel()方法打开对话框。这个函数通常用于编辑器扩展(Editor Extensions)或自定义编辑器窗口中,以便让开发者在编辑器中选择文件。
以下是EditorUtility.OpenFilePanel()的基本用法和一些参数说明:
- // 打开文件面板时显示的标题。
- string title = "Open File";
-
- // 打开文件面板时默认的目录。这应该是相对于项目文件夹的路径。
- string directory = "Assets";
-
- // 打开文件面板时默认选择的文件扩展名,可以是空字符串。
- string extension = "txt";
-
- // 函数返回一个字符串,表示用户选择的文件路径。如果用户取消选择或关闭文件面板,返回的路径将为空字符串。
- string path = EditorUtility.OpenFilePanel(title, directory, extension);
注意,该方法只能在UnityEditor环境下使用,打包后的程序无法使用该方法。
既然打包后无法使用EditorUtility.OpenFilePanel(),我们只能另寻他法。
因此我们需要调用本机Windows系统所提供的动态链接库Comdlg32.dll,这个库是Windows操作系统中包含通用对话框函数(如文件对话框)的库。代码如下:
[DllImport("Comdlg32.dll", SetLastError = true, CharSet = CharSet.Auto)]
其中SetLastError = true表示将上一个错误代码设置为非零值。这对于调试和错误处理非常有用。如果调用失败,可以通过检查 Marshal.GetLastWin32Error() 获取错误代码。
而CharSet = CharSet.Auto表示字符集由运行时环境自动选择。
然后我们需要调用GetOpenFileName函数。这个函数是 Windows 操作系统提供的一个 API 函数,用于显示标准的文件打开对话框。该对话框允许用户选择一个或多个文件,并返回用户所选文件的路径信息。代码如下:
private static extern bool GetOpenFileName([In, Out] FileDialogWin.OPENFILENAME ofn);
从关键字extern可以看出,该函数是一个本机方法,其实现并不在C#中,而是在本机代码中。
[In, Out]是修饰符,用于指定参数是既输入又输出的(类似于引用传参)。
该函数接受一个FileDialogWin.OPENFILENAME类型的参数ofn,因此我们需要封装一个该类型的对象:
- public static class FileDialogWin
- {
- [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 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;
- }
- }

该结构体需要满足WindowsAPI所期望的格式,所以可以将它当作固定用法,这里就不作解释。
上述准备工作做完后就可以调用函数了,代码如下:
- private string OpenWindowsFileDialog(string title, string defaultPath, string extension)
- {
- //Debug.Log("运行");
- FileDialogWin.OPENFILENAME ofn = new FileDialogWin.OPENFILENAME();
- ofn.structSize = System.Runtime.InteropServices.Marshal.SizeOf(ofn);
- ofn.filter =extension + "Files\0*.extension\0All Files\0*.*\0\0";
- ofn.file = new string(new char[256]);
- ofn.maxFile = ofn.file.Length;
- ofn.title = title;
- ofn.initialDir = defaultPath;
-
- //Debug.Log(GetOpenFileName(ofn));
-
- if (GetOpenFileName(ofn))
- {
- return ofn.file;
- }
-
- return null;
- }

首先实例化一个 FileDialogWin.OPENFILENAME对象,并给其属性初始化,其中ofn.filter是通过正则表达式匹配文件扩展名。
然后调用GetOpenFileName函数,该函数会返回一个bool值,通常用于表示用户是否成功选择了文件并点击了文件对话框的 "打开" 按钮。因此当返回值为true时,返回获取到的文件地址即可。
注意,经过实际测试,想要正常打开文件对话框似乎需要确保以管理员权限运行
最后将上述两个方法整合一下,得到最终代码:
- public class FileDialog : MonoBehaviour
- {
-
- // 留出一个统一的调用接口
- public string OpenFilePanel()
- {
- //定义返回的文件路径
- string selectedFilePath = string.Empty;
- #if UNITY_EDITOR
- // 编辑器环境
- selectedFilePath = EditorUtility.OpenFilePanel("Select a file", "", "");
- #elif UNITY_STANDALONE_WIN
- // Windows环境
- selectedFilePath = OpenWindowsFileDialog("Select a file", "", "");
- #endif
-
- // 处理选中的文件路径
- if (!string.IsNullOrEmpty(selectedFilePath))
- {
- Debug.Log("Selected file: " + selectedFilePath);
- }
- else
- {
- Debug.Log("File selection canceled.");
- }
-
- return selectedFilePath;
- }
-
-
- #if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN
- // Windows平台的文件对话框
- [DllImport("Comdlg32.dll", SetLastError = true, CharSet = CharSet.Auto)]
- private static extern bool GetOpenFileName([In, Out] FileDialogWin.OPENFILENAME ofn);
-
- private string OpenWindowsFileDialog(string title, string defaultPath, string extension)
- {
- //Debug.Log("运行");
- FileDialogWin.OPENFILENAME ofn = new FileDialogWin.OPENFILENAME();
- ofn.structSize = System.Runtime.InteropServices.Marshal.SizeOf(ofn);
- ofn.filter ="G-code Files\0*.gcode\0All Files\0*.*\0\0";
- ofn.file = new string(new char[256]);
- ofn.maxFile = ofn.file.Length;
- ofn.title = title;
- ofn.initialDir = defaultPath;
-
- //Debug.Log(GetOpenFileName(ofn));
-
- if (GetOpenFileName(ofn))
- {
- return ofn.file;
- }
-
- return null;
- }
-
- public static class FileDialogWin
- {
- [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 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;
- }
- }
- #endif
- }

只需在其他脚本调用OpenFilePanel()函数,将根据运行环境自动选择方案。
UnityEditor
Windows
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。