Skip to content

自定义控件开发指南

Warning

The current page still doesn't have a translation for this language.

You can read it through google translate.

概述

SOUI 框架提供了丰富的内置控件,但在实际项目开发中,往往需要根据特定需求开发自定义控件。本章将详细介绍 SOUI 自定义控件的开发流程,包括控件扩展和绘图对象扩展两个方面。

为什么需要自定义控件? - 内置控件无法满足特定的 UI 设计需求 - 需要实现特殊的交互逻辑 - 需要支持特定的数据格式(如 GIF 动画) - 需要优化特定场景下的性能

控件扩展

基础概念

SOUI 中的自定义控件开发遵循面向对象的继承原则,通过继承现有控件类来扩展功能。所有控件都继承自 [SWindow] 基类,它提供了基本的窗口管理和消息处理机制。

开发步骤

1. 选择合适的基类

选择合适的基类是开发自定义控件的关键。SOUI 提供了多层次的控件继承体系:

基类 用途 适用场景
[SWindow] 最基础的窗口控件 完全自定义的控件
[SStatic] 静态文本控件 显示类控件扩展
[SButton] 按钮控件 交互类控件扩展
[SEdit] 编辑框控件 输入类控件扩展

2. 实现控件类

以扩展 [SRadioBox] 控件为例,演示自定义控件的实现过程:

class SRadioBox2 : public SRadioBox
{
public:
 SRadioBox2(void);
 ~SRadioBox2(void);
}

需要注意的是,所有 SOUI 控件都是在 namespace SOUI 中,因此自定义控件也最好是 放在 SOUI 这个 namespace 里。 有了上面的骨架,下面来逐步添加内容。 首先我们需要给自定义控件定义一个在 XML 中可以识别的标签。 只需要在类的最开始增加一行:

class SRadioBox2 : public SRadioBox
 {
DEF_SOBJECT(SRadioBox, L"radio2")  // 定义标签为 radio2
 public:
 SRadioBox2(void);
 ~SRadioBox2(void);
 }
 ```

 DEF_SOBJECT 告诉 XML 解析器碰到 radio2 时自动创建 SRadioBox2 对象
实际上这一行更重要的作用是用来做对象类型运行时识别RTTI,有了这个机制在编译
器关闭 C++ RTTI 时仍然可以安全的进行类型转换
然后我们需要处理控件的 WM_PAINT 消息
为了处理这个消息我们需要加入消息映射表及消息处理函数

```cpp
class SRadioBox2 : public SRadioBox
 {
DEF_SOBJECT(SRadioBox, L"radio2")
 public:
SRadioBox2(void);
 ~SRadioBox2(void); 
 protected: 
 void OnPaint(IRenderTarget *pRT);
 SOUI_MSG_MAP_BEGIN()
 MSG_WM_PAINT_EX(OnPaint)
 SOUI_MSG_MAP_END()
 };

常用的 SOUI 特有消息映射宏:

消息类型 SOUI 宏 参数差异
绘制消息 MSG_WM_PAINT_EX IRenderTarget* 而非 HDC
背景擦除 MSG_WM_ERASEBKGND_EX IRenderTarget* 而非 HDC
字体设置 MSG_WM_SETFONT_EX IFont* 而非 HFONT
定时器 MSG_WM_TIMER_EX char 而非 UINT_PTR

4. 属性映射系统

SOUI_ATTRS_BEGIN()
    ATTR_CUSTOM(L"skin", OnAttrSkin)
SOUI_ATTRS_END()

控件注册与使用

// 注册控件
theApp->RegisterWndFactory(TplSWindowFactory<SRadioBox2>());
<!-- XML 中使用 -->
<radio2 pos="10,10,100,30" text="自定义单选框"/>

绘图对象(ISkinObj)扩展

核心接口

class ISkinObj {
public:
    virtual void Draw(IRenderTarget *pRT, LPCRECT rcDraw, 
                     DWORD dwState, BYTE byAlpha=0xFF) = 0;
    virtual SIZE GetSkinSize() { return {0, 0}; }
    virtual int GetStates() { return 1; }
};

GIF 绘图对象实现

class SSkinGif : public ISkinObj {
    DEF_SOBJECT(ISkinObj, L"gif")
public:
    virtual void Draw(IRenderTarget *pRT, LPCRECT rcDraw, 
                     DWORD dwState, BYTE byAlpha=0xFF) override;
    virtual int GetStates() override { return m_nFrames; }

    // GIF 特有功能
    int LoadFromFile(LPCTSTR pszFileName);
    void ActiveNextFrame();
};

注册与使用

theApp->RegisterSkinFactory(TplSkinFactory<SSkinGif>());
<skin>
    <gif name="gif_animation" src="gif:sample"/>
</skin>

高级控件示例

GIF 播放器控件

class SGifPlayer : public SWindow {
    DEF_SOBJECT(SWindow, L"gifplayer")
protected:
    void OnPaint(IRenderTarget *pRT) override;
    void OnTimer(char cTimerID);

    SOUI_MSG_MAP_BEGIN()
        MSG_WM_TIMER_EX(OnTimer)
        MSG_WM_PAINT_EX(OnPaint)
    SOUI_MSG_MAP_END()
};

最佳实践

  1. 设计原则:单一职责、接口简洁、性能优先
  2. 开发流程:需求分析 → 类型选择 → 分步实现 → 测试验证
  3. 注意事项:内存管理、线程安全、兼容性

总结

SOUI 的自定义控件开发提供了灵活而强大的扩展机制。通过继承现有控件或实现新的绘图对象,开发者可以满足各种复杂的 UI 需求。关键在于理解 SOUI 的设计哲学,遵循最佳实践,并在实际项目中不断练习和优化。