<2021SC@SDUSC>开源游戏引擎Overload代码分析三(OvWindowing结束):OvWindowing——Dialogs

Posted chenxiang_200108

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了<2021SC@SDUSC>开源游戏引擎Overload代码分析三(OvWindowing结束):OvWindowing——Dialogs相关的知识,希望对你有一定的参考价值。

2021SC@SDUSC

前言

这是Overload引擎相关的第五篇文章,同时也是OvWindowing分析的第三篇。Overload引擎的Github主页在这里。
本篇文章主要会介绍OvWindowing中Dialogs文件夹所包含的h和cpp文件,同时会把内容比较少,比较简单Inputs文件夹内的文件一并介绍,就算是把OvWindowing中剩余的内容全部讲完了。

Dialogs

首先我们来讲Dialogs,顾名思义,这个文件夹下的文件都是和对话框有关的,我们把h文件和cpp文件一对一的讲。

一、FileDialog

FileDialog.h

我们先看头文件,头文件代码如下:

	/**
	* Some flags that can be passed to FileDialog instances
	*/
	enum class EExplorerFlags
	{
		READONLY                 = 0x00000001,
		OVERWRITEPROMPT          = 0x00000002,
		HIDEREADONLY             = 0x00000004,
		NOCHANGEDIR              = 0x00000008,
		SHOWHELP                 = 0x00000010,
		ENABLEHOOK               = 0x00000020,
		ENABLETEMPLATE           = 0x00000040,
		ENABLETEMPLATEHANDLE     = 0x00000080,
		NOVALIDATE               = 0x00000100,
		ALLOWMULTISELECT         = 0x00000200,
		EXTENSIONDIFFERENT       = 0x00000400,
		PATHMUSTEXIST            = 0x00000800,
		FILEMUSTEXIST            = 0x00001000,
		CREATEPROMPT             = 0x00002000,
		SHAREAWARE               = 0x00004000,
		NOREADONLYRETURN         = 0x00008000,
		NOTESTFILECREATE         = 0x00010000,
		NONETWORKBUTTON          = 0x00020000,
		NOLONGNAMES              = 0x00040000,	// force no long names for 4.x modules
		EXPLORER                 = 0x00080000,	// new look commdlg
		NODEREFERENCELINKS       = 0x00100000,	
		LONGNAMES                = 0x00200000,	// force long names for 3.x modules
		ENABLEINCLUDENOTIFY      = 0x00400000,	// send include message to callback
		ENABLESIZING             = 0x00800000,	
		DONTADDTORECENT          = 0x02000000,	
		FORCESHOWHIDDEN          = 0x10000000	// Show All files including System and hidden files
	};

	inline EExplorerFlags operator~ (EExplorerFlags a) { return (EExplorerFlags)~(int)a; }
	inline EExplorerFlags operator| (EExplorerFlags a, EExplorerFlags b) { return (EExplorerFlags)((int)a | (int)b); }
	inline EExplorerFlags operator& (EExplorerFlags a, EExplorerFlags b) { return (EExplorerFlags)((int)a & (int)b); }
	inline EExplorerFlags operator^ (EExplorerFlags a, EExplorerFlags b) { return (EExplorerFlags)((int)a ^ (int)b); }
	inline EExplorerFlags& operator|= (EExplorerFlags& a, EExplorerFlags b) { return (EExplorerFlags&)((int&)a |= (int)b); }
	inline EExplorerFlags& operator&= (EExplorerFlags& a, EExplorerFlags b) { return (EExplorerFlags&)((int&)a &= (int)b); }
	inline EExplorerFlags& operator^= (EExplorerFlags& a, EExplorerFlags b) { return (EExplorerFlags&)((int&)a ^= (int)b); }

	/**
	* FileDialog is the base class for any dialog window that asks the user to select/save a file from/to the disk
	*/
	class FileDialog
	{
	public:
		/**
		* Constructor
		* @param p_callback
		* @param p_dialogTitle
		*/
		FileDialog(std::function<int(tagOFNA*)> p_callback, const std::string& p_dialogTitle);

		/**
		* Defines the initial directory (Where the FileDialog will open)
		* @param p_initalDirectory
		*/
		void SetInitialDirectory(const std::string& p_initialDirectory);

		/**
		* Show the file dialog
		* @param p_flags
		*/
		virtual void Show(EExplorerFlags p_flags = EExplorerFlags::DONTADDTORECENT | EExplorerFlags::FILEMUSTEXIST | EExplorerFlags::HIDEREADONLY | EExplorerFlags::NOCHANGEDIR);

		/**
		* Returns true if the file action succeeded
		*/
		bool HasSucceeded() const;

		/**
		* Returns the selected file name (Make sur that HasSucceeded() returned true before calling this method)
		*/
		std::string GetSelectedFileName();

		/**
		* Returns the selected file path (Make sur that HasSucceeded() returned true before calling this method)
		*/
		std::string GetSelectedFilePath();

		/**
		* Returns some information about the last error (Make sur that HasSucceeded() returned false before calling this method)
		*/
		std::string GetErrorInfo();

		/**
		* Returns true if the selected file exists
		*/
		bool IsFileExisting() const;

	private:
		void HandleError();

	protected:
		std::function<int(tagOFNA*)> m_callback;
		const std::string m_dialogTitle;
		std::string m_initialDirectory;
		std::string m_filter;
		std::string m_error;
		std::string m_filename;
		std::string m_filepath;
		bool m_succeeded;
	};

上来首先是一个枚举定义,事实上表示了对话框的性质,比如第一个READONLY就表示只读,这个看名字很明白了,不容易理解的还有注释,因为定义的比较多,所以我就不一一说明意思了,总之知道它是对话框性质就好了。

接下来一系列的内联函数都是对运算符的重载,需要知道EExplorerFlags实际上就是一个数,所以做运算是很正常的,而重载运算符最后又把运算结果转换成了EExplorerFlags类型,所以这些运算符重载后的作用就是改变对话框的性质。

接着它定义了FileDialog类这就是咱们文件相关的对话框的基类了,具体的函数作用在这有注释,我们到cpp文件中实现的时候再细说。

FileDialog.cpp

因为文件并不是很长,函数实现也并不算复杂,所以咱们把整个文件放在一起讲了,先上代码:

OvWindowing::Dialogs::FileDialog::FileDialog(std::function<int(tagOFNA*)> p_callback, const std::string & p_dialogTitle) :
	m_callback(p_callback),
	m_dialogTitle(p_dialogTitle),
	m_initialDirectory("")
{
}

void OvWindowing::Dialogs::FileDialog::SetInitialDirectory(const std::string & p_initialDirectory)
{
	m_initialDirectory = p_initialDirectory;
}

void OvWindowing::Dialogs::FileDialog::Show(EExplorerFlags p_flags)
{
	OPENFILENAME ofn;

	if (!m_initialDirectory.empty())
		m_filepath = m_initialDirectory;

	m_filepath.resize(MAX_PATH);

	ZeroMemory(&ofn, sizeof(ofn));
	ofn.lStructSize = sizeof(ofn);
	ofn.hwndOwner = NULL;  // If you have a window to center over, put its HANDLE here
	ofn.lpstrFilter = m_filter.c_str();
	ofn.lpstrFile = m_filepath.data();
	ofn.nMaxFile = MAX_PATH;
	ofn.lpstrTitle = m_dialogTitle.c_str();

	if (!m_initialDirectory.empty())
		ofn.lpstrInitialDir = m_initialDirectory.c_str();

	ofn.Flags = static_cast<DWORD>(p_flags);

	m_succeeded = m_callback(&ofn);

	if (!m_succeeded)
		HandleError();
	else
		m_filepath = m_filepath.c_str();

	/* Extract filename from filepath */
	m_filename.clear();
	for (auto it = m_filepath.rbegin(); it != m_filepath.rend() && *it != '\\\\' && *it != '/'; ++it)
		m_filename += *it;
	std::reverse(m_filename.begin(), m_filename.end());
}

bool OvWindowing::Dialogs::FileDialog::HasSucceeded() const
{
	return m_succeeded;
}

std::string OvWindowing::Dialogs::FileDialog::GetSelectedFileName()
{
	return m_filename;
}

std::string OvWindowing::Dialogs::FileDialog::GetSelectedFilePath()
{
	return m_filepath;
}

std::string OvWindowing::Dialogs::FileDialog::GetErrorInfo()
{
	return m_error;
}

bool OvWindowing::Dialogs::FileDialog::IsFileExisting() const
{
	return std::filesystem::exists(m_filepath);
}

void OvWindowing::Dialogs::FileDialog::HandleError()
{
	switch (CommDlgExtendedError())
	{
	case CDERR_DIALOGFAILURE:	m_error = "CDERR_DIALOGFAILURE";   break;
	case CDERR_FINDRESFAILURE:	m_error = "CDERR_FINDRESFAILURE";  break;
	case CDERR_INITIALIZATION:	m_error = "CDERR_INITIALIZATION";  break;
	case CDERR_LOADRESFAILURE:	m_error = "CDERR_LOADRESFAILURE";  break;
	case CDERR_LOADSTRFAILURE:	m_error = "CDERR_LOADSTRFAILURE";  break;
	case CDERR_LOCKRESFAILURE:	m_error = "CDERR_LOCKRESFAILURE";  break;
	case CDERR_MEMALLOCFAILURE: m_error = "CDERR_MEMALLOCFAILURE"; break;
	case CDERR_MEMLOCKFAILURE:	m_error = "CDERR_MEMLOCKFAILURE";  break;
	case CDERR_NOHINSTANCE:		m_error = "CDERR_NOHINSTANCE";     break;
	case CDERR_NOHOOK:			m_error = "CDERR_NOHOOK";          break;
	case CDERR_NOTEMPLATE:		m_error = "CDERR_NOTEMPLATE";      break;
	case CDERR_STRUCTSIZE:		m_error = "CDERR_STRUCTSIZE";      break;
	case FNERR_BUFFERTOOSMALL:	m_error = "FNERR_BUFFERTOOSMALL";  break;
	case FNERR_INVALIDFILENAME: m_error = "FNERR_INVALIDFILENAME"; break;
	case FNERR_SUBCLASSFAILURE: m_error = "FNERR_SUBCLASSFAILURE"; break;
	default:					m_error = "You cancelled.";
	}
}

首先是构造函数,只做了属性的赋值,p_callback类型中可以看到tagOFNA,这是OPENFILENAME相关的内部结构,比较复杂,咱们真的用到再讲。m_dialogTitle是对话框的标题。

接下来的SetInitialDirectory()就是普通的赋值,更改m_initialDirectory,就是初始目录的位置。

再下来是一个稍微复杂点的Show()函数,函数先定义了一个OPENFILENAME类型的变量,事实上是OPENFILENAMEA类型,而OPENFILENAMEA就是tagOFNA结构。接下来函数确认是否有初始路径,如果有就把这个初始路径赋值给当前文件路径。之后给当前文件路径分配更多的空间,也给ofn分配了一块新的空间。接下来就是对ofn的属性进行赋值,我们来讲一下它的属性,实际就是tagOFNA的属性:

lStructSize是当前结构的大小,单位是字节;
hwndOwner是所有者对话框窗口的句柄;
lpstrFilter是指定文件名筛选字符串,决定了对话框中“文件类型”下拉式列表框中的内容,赋值比较复杂;
lpstrFile应指向一个包含文件名的缓冲区;
nMaxFile指定lpstrFile参数指向的缓冲区的长度,单位为TCHARs;
lpstrTitle应当指向一个缓冲区,用来接收用户选择的文件的文件名和扩展名;
lpstrInitialDir是一个指向对话框的初始化目录的以空字符结束的字符串,在咱们的Show()函数里只有有初始路径时才赋值;
Flags这个标志字段决定了对话框的不同行为。
这就是我们在函数里提到的所有的属性,想要全面了解tagOFNA的属性的话,可以去看这篇文章,当然也可以看windows官方的说明

在对ofn操作完成后,会用m_succeeded来接收一下回调,确认是否创建赋值成功。如果失败了,那么我们会返回一个错误,如果成功的话,我们就把真实的文件路径赋给咱们当前实例的属性。

再往下,就是如果咱们指定读取了一个路径,就会先清空已保存的当前路径,然后读取我们指定的路径。

接下来的函数是HasSucceeded(),GetSelectedFileName(),GetSelectedFilePath(),GetErrorInfo(),IsFileExisting(),都是一行就结束的函数,我们在一起讲。分别是用于读取属性m_succeeded,来确定是否成功生成;读取选中文件名,读取选中文件路径,获得报错信息,以及判断我们选中的文件是否存在。

最后是HandleError(),用于先从CommDlgExtendedError获取错误类型,然后对不同的错误类型报不同的错误。

二、MessageBox

接下来讲MessageBox这个文件夹,先讲头文件:

MessageBox.h

	/**
	* Displays a modal dialog box that contains a system icon,
	* a set of buttons, and a brief application-specific message,
	* such as status or error information
	*/
	class MessageBox
	{
	public:
		/**
		* Defines some severity levels for MessageBox instances
		*/
		enum class EMessageType
		{
			QUESTION	= 0x00000020L,
			INFORMATION = 0x00000040L,
			WARNING		= 0x00000030L,
			ERROR		= 0x00000010L
		};

		/**
		* Defines some button layouts for MessageBox instances
		*/
		enum class EButtonLayout
		{
			OK							= 0x00000000L,
			OK_CANCEL					= 0x00000001L,
			YES_NO						= 0x00000004L,
			YES_NO_CANCEL				= 0x00000003L,
			RETRY_CANCEL				= 0x00000005L,
			ABORT_RETRY_IGNORE			= 0x00000002L,
			CANCEL_TRYAGAIN_CONTINUE	= 0x00000006L,
			HELP						= 0x00004000L
		};

		/**
		* Defines some actions that the MessageBox should provide
		*/
		enum class EUserAction
		{
			OK			= 1,
			CANCEL		= 2,
			YES			= 6,
			NO			= 7,
			CONTINUE	= 11,
			IGNORE		= 5,
			RETRY		= 4,
			TRYAGAIN	= 10
		};

		/**
		* Create the MessageBox
		* @param p_title
		* @param p_message
		* @param p_messageType
		* @param p_buttonLayout
		* @param p_autoSpawn
		*/
		MessageBox(std::string p_title, std::string p_message, EMessageType p_messageType = EMessageType::INFORMATION, EButtonLayout p_buttonLayout = EButtonLayout::OK, bool p_autoSpawn = true);

		/**
		* Show the MessageBox on the screen
		*/
		void Spawn();

		/**
		* Return the user action
		*/
		const EUserAction& GetUserAction() const;

	private:
		std::string		m_title;
		std::string		m_message;
		EButtonLayout	m_buttonLayout;
		EMessageType	m_messageType;
		EUserAction		m_userResult;
	};

这个头文件定义了MessageBox,其实和咱们windows的MessageBox差不多。我们可以看到它有三个枚举:EMessageType是定义这个box的级别,是问题、信息、警告还是错误;EButtonLayout则是咱们的按钮类型;EUserAction则是用户做出的选择。其他的函数实现,我们在cpp文件中讲:

MessageBox.cpp

OvWindowing::Dialogs::MessageBox::MessageBox(std::string p_title, std::string p_message, EMessageType p_messageType, EButtonLayout p_buttonLayout, bool p_autoSpawn) :
	m_title(p_title),
	m_message(p_message),
	m_buttonLayout(p_buttonLayout),
	m_messageType(p_messageType)
{
	if (p_autoSpawn)
		Spawn();
}

const OvWindowing::Dialogs::MessageBox::EUserAction& OvWindowing::Dialogs::MessageBox::GetUserAction() const
{
	return m_userResult;
}

void OvWindowing::Dialogs::MessageBox::Spawn()
{
	int msgboxID = MessageBoxA
	(
		nullptr,
		static_cast<LPCSTR>(m_message.c_str()),
		static_cast<LPCSTR>(m_title.c_str()),
		static_cast<UINT>(m_messageType) | static_cast<UINT>(m_buttonLayout) | MB_DEFBUTTON2
	);

	m_userResult = static_cast<EUserAction>(msgboxID);
}

首先是构造函数,同样也是比较简单的赋值,唯一的一个判断是是否自动显现,如果是的话就显现窗口。

GetUserAction()只是返回一个属性,这个属性是用于记录用户的动作结果。

Spawn()是用于显示窗口的,其实就是调用了windows的函数MessageBoxA(),这个函数会显示一个对话框,之后会返回的整数表示用户所点击的按钮,所以我们会把那个得到的整数值存入m_userResult,也就是GetUserAction()返回的属性。

三、OpenFileDialog

接下来是OpenFileDialog文件夹内的文件。

OpenFileDialog.h

	/**
	* Dialog window that asks the user to select a file from the disk
	*/
	class OpenFileDialog : public FileDialog
	{
	public:
		/**
		* Constructor
		* @param p_dialogTitle
		*/
		OpenFileDialog(const std::string& p_dialogTitle);

		/**
		* Add a supported file type to the dialog window
		* @param p_label
		* @param p_filter
		*/
		void AddFileType(const std::string& p_label, const std::string& p_filter);
	};

这个头文件很短,定义了一个OpenFileDialog类,是让用户从磁盘选择一个文件的对话框,具体的函数实现在cpp文件中说明:

OpenFileDialog.cpp

OvWindowing::Dialogs::OpenFileDialog::OpenFileDialog(const std::string & p_dialogTitle) : FileDialog(GetOpenFileNameA, p_dialogTitle)
{
}

void OvWindowing::Dialogs::OpenFileDialog::AddFileType(const std::string & p_label, const std::string & p_filter)
{
	m_filter += p_label + '\\0' + p_filter + '\\0';
}

这个实现实际上也很简单,构造函数就是调用继承的FileDialog类的构造函数,而AddFileType()也就是往我们的文件过滤器添加了一种类型,让我们能支持新的文件格式罢了。

四、SaveFileDialog

这里是SaveFileDialog文件夹内的文件:

SaveFileDialog.h

	/**
	* Dialog window that asks the user to save a file to the disk
	*/
	class SaveFileDialog : public FileDialog
	{
	public:
		/**
		* Constructor
		* @param p_dialogTitle
		*/
		SaveFileDialog(const std::string& p_dialogTitle);

		/**
		* Show the file dialog
		* @param p_flags
		*/
		virtual void Show(EExplorerFlags p_flags = EExplorerFlags::DONTADDTORECENT | EExplorerFlags::FILEMUSTEXIST | EExplorerFlags::HIDEREADONLY | EExplorerFlags::NOCHANGEDIR) override;

		/**
		* Define the extension of the saved file
		* @param p_label
		* @param p_extension
		*/
		void DefineExtension(const std::string& p_label, const std::string& p_extension);

	private:
		void AddExtensionToFilePathAndName();

	private:
		std::string m_extension;
	};

这个头文件也比较简单,定义了一个SaveFileDialog类,是保存文件到磁盘的对话框,具体函数实现看下面:

SaveFileDialog.cpp

OvWindowing::Dialogs::SaveFileDialog::SaveFileDialog(const std::string & p_dialogTitle) : FileDialog(GetSaveFileNameA, p_dialogTitle)
{
}

void OvWindowing::Dialogs::SaveFileDialog::Show(EExplorerFlags p_flags)
{
	FileDialog::Show(p_flags);

	if (m_succeeded)
		AddExtensionToFilePathAndName();
}

void OvWindowing::Dialogs::SaveFileDialog::DefineExtension(const std<2021SC@SDUSC>开源游戏引擎Overload代码分析三(OvWindowing结束):OvWindowing——Dialogs

<2021SC@SDUSC>开源游戏引擎Overload代码分析一:OvWindowing——Window.cpp

<2021SC@SDUSC> 开源游戏引擎 Overload 代码模块分析 之 OvGame—— Utils(终)大纲及 FPSCounter & Debug(上)大纲及 DriverInfo

2021SC@SDUSC Zxing开源代码Zxing编码思路及代码分析

2021SC@SDUSC hadoop源码分析

2021SC@SDUSC-SEAL全同态加密库