跳转至

手动创建 SOUI 项目

本章详细介绍如何从零开始手动创建一个完整的 SOUI 项目,包括环境配置、资源准备、代码编写等所有步骤。通过本教程,您将深入理解 SOUI 项目的内部结构和工作原理。

概述

为什么选择手动创建

  • 深入理解:了解 SOUI 项目的底层结构和配置细节
  • 自定义控制:完全控制项目配置和依赖关系
  • 学习价值:掌握 SOUI 框架的核心概念和使用方式
  • 问题排查:便于理解和解决项目配置相关问题

项目创建流程

  1. 基础项目创建:从 Win32 应用程序模板开始
  2. 环境配置:设置 SOUI 库的包含路径和链接库
  3. 资源准备:创建必要的 XML 资源文件
  4. 代码实现:编写主程序和窗口类
  5. 编译运行:测试和验证项目

基础项目创建

SOUI 项目本质上是一个基于 Win32 窗口的应用程序。我们首先从 Visual Studio 的 Win32 应用程序向导创建基础项目结构。

创建步骤

  1. 启动 Visual Studio,选择"新建项目"
  2. 选择 Win32 应用程序模板
  3. 在向导第 3 页选择"Window 应用程序"
  4. 点击"完成"生成项目骨架

创建完成后,项目将包含以下基本文件结构:

项目名/
├── 项目名.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;
}

编译与运行

编译步骤

  1. 检查配置:确保所有项目设置正确
  2. 编译项目:按 F7 或选择"生成解决方案"
  3. 处理错误:根据编译错误信息调整配置

常见编译问题

问题 原因 解决方案
找不到头文件 包含目录设置错误 检查附加包含目录配置
链接错误 库文件路径或名称错误 检查库目录和依赖项设置
运行时错误 DLL 文件缺失 确保 DLL 文件在输出目录

运行结果

编译成功后,运行程序将显示一个包含以下元素的窗口:

  • 标题栏:显示应用程序名称和版本
  • 窗口控制按钮:最小化、最大化、关闭
  • 中心文本:"Hello World! 欢迎使用 SOUI!"
  • 按钮:"显示消息框",点击会弹出不同类型的消息框

总结

通过手动创建 SOUI 项目,您已经:

掌握了项目结构:理解 SOUI 项目的基本组成和文件组织
学会了环境配置:掌握 Visual Studio 中的 SOUI 项目配置方法
了解了资源管理:学会创建和配置 XML 资源文件
实现了代码框架:编写了完整的 SOUI 应用程序代码
完成了编译运行:成功构建并运行了第一个 SOUI 项目

这为您进一步学习 SOUI 框架的高级特性打下了坚实的基础。下一步可以学习更复杂的控件使用、事件处理和界面设计等内容。