<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