自定义控件开发指南¶
概述¶
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()
};
最佳实践¶
- 设计原则:单一职责、接口简洁、性能优先
- 开发流程:需求分析 → 类型选择 → 分步实现 → 测试验证
- 注意事项:内存管理、线程安全、兼容性
总结¶
SOUI 的自定义控件开发提供了灵活而强大的扩展机制。通过继承现有控件或实现新的绘图对象,开发者可以满足各种复杂的 UI 需求。关键在于理解 SOUI 的设计哲学,遵循最佳实践,并在实际项目中不断练习和优化。