手动创建 SOUI 项目¶
本章详细介绍如何从零开始手动创建一个完整的 SOUI 项目,包括环境配置、资源准备、代码编写等所有步骤。通过本教程,您将深入理解 SOUI 项目的内部结构和工作原理。
概述¶
为什么选择手动创建¶
- 深入理解:了解 SOUI 项目的底层结构和配置细节
- 自定义控制:完全控制项目配置和依赖关系
- 学习价值:掌握 SOUI 框架的核心概念和使用方式
- 问题排查:便于理解和解决项目配置相关问题
项目创建流程¶
- 基础项目创建:从 Win32 应用程序模板开始
- 环境配置:设置 SOUI 库的包含路径和链接库
- 资源准备:创建必要的 XML 资源文件
- 代码实现:编写主程序和窗口类
- 编译运行:测试和验证项目
基础项目创建¶
SOUI 项目本质上是一个基于 Win32 窗口的应用程序。我们首先从 Visual Studio 的 Win32 应用程序向导创建基础项目结构。
创建步骤¶
- 启动 Visual Studio,选择"新建项目"
- 选择 Win32 应用程序模板
- 在向导第 3 页选择"Window 应用程序"
- 点击"完成"生成项目骨架
创建完成后,项目将包含以下基本文件结构:
项目名/
├── 项目名.cpp # 主程序文件
├── 项目名.h # 主头文件
├── stdafx.h # 预编译头文件
├── stdafx.cpp # 预编译源文件
├── targetver.h # 目标版本定义
└── 项目名.vcxproj # 项目配置文件
环境配置¶
获取 SOUI 库文件¶
首先需要获取已编译的 SOUI 库文件。编译SOUI5后,执行install项目,会将编译后的文件复制到soui4_install
目录下。'soui4_install'是你使用cmake config命令时指定的安装目录。
建议将这些文件组织到项目的 soui5
目录下:
项目名/
├── soui5/
│ ├── include/
│ │ ├── soui/ # SOUI 核心头文件
│ │ ├── utilities/ # 工具类头文件
│ │ ├── components/ # 组件头文件
│ │ └── config/ # 配置头文件(v2.3.1.1+)
│ ├── lib/ # 静态库文件
│ └── bin/ # 动态库文件
└── ...
Visual Studio 项目配置¶
1. 包含目录设置¶
在项目属性中添加以下包含目录:
../soui5/include/soui
../soui5/include/utilities
../soui5/include/config
../soui5/include/components
操作步骤: 1. 右键项目 → 属性 2. C/C++ → 常规 → 附加包含目录 3. 添加上述路径
2. 库目录设置¶
设置附加库目录:../soui5/lib
操作步骤: 1. 链接器 → 常规 → 附加库目录 2. 添加路径:../soui5/lib/${ConfigurationName}
3. 依赖库设置¶
添加以下库文件:
soui4.lib
utilities4.lib
操作步骤: 1. 链接器 → 输入 → 附加依赖项 2. 添加上述库文件名
4. 编译配置调整¶
重要配置: - 代码生成:根据编译soui时配置的MT/MD设置项目的MT/MD配置。 - wchar_t 处理:根据编译soui时配置的"将 wchar_t 视为内置类型"修改当前项目的对应配置。
这些是 SOUI 的默认编译配置要求。
资源准备¶
资源文件概述¶
SOUI 项目需要以下基本资源文件:
文件名 | 用途 | 是否必需 | 文件名是否固定 |
---|---|---|---|
uires.idx | 资源索引文件 | ✅ | ✅ 固定 |
init.xml | 全局 UI 属性定义 | ✅ | ❌ 可自定义 |
dlg_main.xml | 主窗口布局 | ✅ | ❌ 可自定义 |
创建资源目录结构¶
建议创建以下资源目录结构:
项目名/
├── uires/ # 资源根目录
│ ├── uires.idx # 资源索引文件
│ └── xml/ # XML 文件目录
│ ├── init.xml # 全局初始化文件
│ └── dlg_main.xml # 主窗口布局文件
└── ...
资源文件内容¶
1. uires.idx - 资源索引文件¶
<resource>
<UIDEF>
<file name="XML_INIT" path="xml\init.xml" />
</UIDEF>
<LAYOUT>
<file name="XML_MAINWND" path="xml\dlg_main.xml" />
</LAYOUT>
</resource>
说明: - UIDEF
类型用于全局初始化文件 - LAYOUT
类型用于界面布局文件 - name
属性是资源的唯一标识符 - path
属性指定文件的相对路径
2. init.xml - 全局初始化文件¶
<?xml version="1.0" encoding="utf-8"?>
<UIDEF>
<!-- 默认字体设置 -->
<font face="微软雅黑" size="14"/>
<!-- 字符串资源 -->
<string>
<title value="SOUI 示例程序"/>
<ver value="1.0"/>
</string>
<!-- 皮肤定义 -->
<skin>
<!-- 可在此定义自定义皮肤 -->
</skin>
<!-- 样式定义 -->
<style>
<class name="normalbtn"
font="size:14"
colorText="#385e8b"
colorTextDisable="#91a7c0"
textMode="25"
cursor="hand"
margin-x="0"/>
</style>
<!-- 控件默认属性 -->
<objattr>
<!-- 可在此定义控件默认属性 -->
</objattr>
</UIDEF>
主要元素说明: - font
:设置应用程序默认字体 - string
:定义字符串资源,支持国际化 - skin
:自定义皮肤定义 - style
:CSS 样式类定义 - objattr
:控件类型默认属性
3. dlg_main.xml - 主窗口布局¶
<SOUI name="mainWindow"
title="%title% ver:%ver%"
width="800"
height="600"
appWnd="1"
margin="20,5,5,5"
resizable="1"
translucent="0">
<root skin="_skin.sys.wnd.bkgnd">
<!-- 标题栏 -->
<caption pos="0,0,-0,30" layout="hbox" gravity="center">
<text >@string/title</text>
<text >ver:@string/ver</text>
<window size="0,0" weight="1">
<imgbtn name="btn_min"
skin="_skin.sys.btn.minimize"
animate="1"/>
<window>
<imgbtn pos="0,0" name="btn_max"
skin="_skin.sys.btn.maximize"
animate="1"/>
<imgbtn pos="0,0" name="btn_restore"
skin="_skin.sys.btn.restore"
show="0"
animate="1"/>
</window>
<imgbtn name="btn_close"
skin="_skin.sys.btn.close"
tip="关闭"
animate="1"/>
</caption>
<!-- 主内容区域 -->
<window pos="5,30,-5,-5">
<text pos="|0,|0"
pos2type="center"
colorText="#ff0000"
font="size:16,bold:1">
Hello World! 欢迎使用 SOUI!
</text>
<button class="normalbtn"
pos="|-75,[20,@150,@35"
name="btn_msgbox">
显示消息框
</button>
</window>
</root>
</SOUI>
布局说明: - SOUI
根元素定义窗口基本属性 - caption
元素定义标题栏区域 - window
元素定义主内容区域 - 使用 @string/title
等占位符引用字符串资源
代码实现¶
修改预编译头文件¶
首先修改 stdafx.h
文件,添加 SOUI 相关头文件:
#pragma once
#include "targetver.h"
#define _CRT_SECURE_NO_WARNINGS
#define DLL_SOUI // SOUI 以 DLL 提供时需要定义此宏
#include <souistd.h>
#include <core/SHostDialog.h>
#include <control/SMessageBox.h>
#include <control/souictrls.h>
using namespace SOUI;
头文件说明: - souistd.h
:SOUI 标准头文件 - SHostDialog.h
:主机窗口类 - SMessageBox.h
:消息框控件 - souictrls.h
:标准控件库
创建主窗口类¶
创建 MainWnd.h
文件,定义主窗口类:
#pragma once
class CMainWnd : public SHostWnd
{
public:
CMainWnd()
: SHostWnd(_T("LAYOUT:XML_MAINWND")) // 指定布局资源
{
m_bLayoutInited = FALSE;
}
protected:
// 事件处理函数
void OnClose()
{
PostMessage(WM_QUIT);
}
void OnMaximize()
{
SendMessage(WM_SYSCOMMAND, SC_MAXIMIZE);
}
void OnRestore()
{
SendMessage(WM_SYSCOMMAND, SC_RESTORE);
}
void OnMinimize()
{
SendMessage(WM_SYSCOMMAND, SC_MINIMIZE);
}
void OnBtnMsgBox()
{
SMessageBox(NULL, _T("这是一个 SOUI 消息框示例"), _T("提示"), MB_OK | MB_ICONINFORMATION);
}
void OnSize(UINT nType, CSize size)
{
SetMsgHandled(FALSE);
if (!m_bLayoutInited) return;
if (nType == SIZE_MAXIMIZED)
{
FindChildByName(L"btn_restore")->SetVisible(TRUE);
FindChildByName(L"btn_max")->SetVisible(FALSE);
}
else if (nType == SIZE_RESTORED)
{
FindChildByName(L"btn_restore")->SetVisible(FALSE);
FindChildByName(L"btn_max")->SetVisible(TRUE);
}
}
BOOL OnInitDialog(HWND hWnd, LPARAM lParam)
{
m_bLayoutInited = TRUE;
return 0;
}
protected:
// 控件事件映射表
EVENT_MAP_BEGIN()
EVENT_NAME_COMMAND(L"btn_close", OnClose)
EVENT_NAME_COMMAND(L"btn_min", OnMinimize)
EVENT_NAME_COMMAND(L"btn_max", OnMaximize)
EVENT_NAME_COMMAND(L"btn_restore", OnRestore)
EVENT_NAME_COMMAND(L"btn_msgbox", OnBtnMsgBox)
EVENT_MAP_END()
// 窗口消息映射表
BEGIN_MSG_MAP_EX(CMainWnd)
MSG_WM_INITDIALOG(OnInitDialog)
MSG_WM_CLOSE(OnClose)
MSG_WM_SIZE(OnSize)
CHAIN_MSG_MAP(SHostWnd) // 将未处理消息交给基类
REFLECT_NOTIFICATIONS_EX()
END_MSG_MAP()
private:
BOOL m_bLayoutInited; // 布局是否已初始化
};
实现主程序¶
修改主程序文件,实现完整的应用程序框架:
#include "stdafx.h"
#include "MainWnd.h"
#include <com-loader.hpp>
// 组件库定义
#define COM_IMGDECODER _T("imgdecoder-stb.dll")
#define COM_RENDER_GDI _T("render-gdi.dll")
#define SYS_NAMED_RESOURCE _T("soui-sys-resource.dll")
int WINAPI _tWinMain(HINSTANCE hInstance,
HINSTANCE /*hPrevInstance*/,
LPTSTR /*lpstrCmdLine*/,
int /*nCmdShow*/)
{
// 初始化 COM
HRESULT hRes = OleInitialize(NULL);
SASSERT(SUCCEEDED(hRes));
int nRet = 0;
SComLoader imgDecLoader;
SComLoader renderLoader;
// 设置工作目录(确保能找到资源文件)
TCHAR szCurrentDir[MAX_PATH] = {0};
GetModuleFileName(NULL, szCurrentDir, sizeof(szCurrentDir));
LPTSTR lpInsertPos = _tcsrchr(szCurrentDir, _T('\\'));
_tcscpy(lpInsertPos + 1, _T(".."));
SetCurrentDirectory(szCurrentDir);
{
// 创建图像解码器和渲染器
SAutoRefPtr<SOUI::IImgDecoderFactory> pImgDecoderFactory;
SAutoRefPtr<SOUI::IRenderFactory> pRenderFactory;
imgDecLoader.CreateInstance(COM_IMGDECODER, (IObjRef**)&pImgDecoderFactory);
renderLoader.CreateInstance(COM_RENDER_GDI, (IObjRef**)&pRenderFactory);
pRenderFactory->SetImgDecoderFactory(pImgDecoderFactory);
// 创建 SOUI 应用程序对象
SApplication* theApp = new SApplication(pRenderFactory, hInstance);
// 加载系统资源
HMODULE hSysResource = LoadLibrary(SYS_NAMED_RESOURCE);
if (hSysResource)
{
SAutoRefPtr<IResProvider> sysResProvider;
CreateResProvider(RES_PE, (IObjRef**)&sysResProvider);
sysResProvider->Init((WPARAM)hSysResource, 0);
theApp->LoadSystemNamedResource(sysResProvider);
}
// 加载应用程序资源
SAutoRefPtr<IResProvider> pResProvider;
CreateResProvider(RES_FILE, (IObjRef**)&pResProvider);
if (!pResProvider->Init((LPARAM)_T("uires"), 0))
{
SASSERT(0);
return 1;
}
theApp->AddResProvider(pResProvider);
// 创建并显示主窗口
{
CMainWnd wndMain;
wndMain.Create(GetActiveWindow(), 0, 0, 800, 600);
wndMain.SendMessage(WM_INITDIALOG);
wndMain.CenterWindow(wndMain.m_hWnd);
wndMain.ShowWindow(SW_SHOWNORMAL);
// 进入消息循环
nRet = theApp->Run(wndMain.m_hWnd);
}
delete theApp;
}
OleUninitialize();
return nRet;
}
编译与运行¶
编译步骤¶
- 检查配置:确保所有项目设置正确
- 编译项目:按 F7 或选择"生成解决方案"
- 处理错误:根据编译错误信息调整配置
常见编译问题¶
问题 | 原因 | 解决方案 |
---|---|---|
找不到头文件 | 包含目录设置错误 | 检查附加包含目录配置 |
链接错误 | 库文件路径或名称错误 | 检查库目录和依赖项设置 |
运行时错误 | DLL 文件缺失 | 确保 DLL 文件在输出目录 |
运行结果¶
编译成功后,运行程序将显示一个包含以下元素的窗口:
- 标题栏:显示应用程序名称和版本
- 窗口控制按钮:最小化、最大化、关闭
- 中心文本:"Hello World! 欢迎使用 SOUI!"
- 按钮:"显示消息框",点击会弹出不同类型的消息框
总结¶
通过手动创建 SOUI 项目,您已经:
✅ 掌握了项目结构:理解 SOUI 项目的基本组成和文件组织
✅ 学会了环境配置:掌握 Visual Studio 中的 SOUI 项目配置方法
✅ 了解了资源管理:学会创建和配置 XML 资源文件
✅ 实现了代码框架:编写了完整的 SOUI 应用程序代码
✅ 完成了编译运行:成功构建并运行了第一个 SOUI 项目
这为您进一步学习 SOUI 框架的高级特性打下了坚实的基础。下一步可以学习更复杂的控件使用、事件处理和界面设计等内容。