PropertyGrid 属性网格
调参数、改配置、做 Inspector——PropertyGrid 是专门为这种键值对编辑场景打造的控件。左列属性名,右列编辑器,支持文本、复选框、滑块、枚举、按钮,还能自定义编辑器,几行代码搭出一个专业属性面板。
函数原型
namespace HX {
enum class PropertyType {
String,
Bool,
Int,
Float,
Enum,
Button
};
struct PropertyItem {
HXString Name;
HXString StringValue;
bool BoolValue = false;
float FloatValue = 0.0f;
int IntValue = 0;
PropertyType Type = PropertyType::String;
std::vector<HXString> EnumOptions;
HXString Id;
};
struct PropertyGridProfile {
HXPoint Size = {300, 400};
int RowHeight = 28;
int NameWidth = 120;
std::function<bool(HXBufferPainter* p, const HXRect& valueRect, PropertyItem& item)> CustomEditorCallback;
};
int PropertyGrid(const HXString &id, std::vector<PropertyItem> &items, PropertyGridProfile &profile);
}
PropertyGrid 的返回值是被点击的按钮索引。如果没有任何按钮被点击,返回 -1。其他类型的编辑结果都直接写回 PropertyItem 的各个字段里。
参数详解
PropertyItem 属性项
| 成员 | 类型 | 默认值 | 说明 |
|---|---|---|---|
Name | HXString | 空 | 左列显示的属性名 |
StringValue | HXString | 空 | String / Enum / Button 类型的值或标签 |
BoolValue | bool | false | Bool 类型的值 |
FloatValue | float | 0.0f | Float 类型的值 |
IntValue | int | 0 | Int 类型的值 |
Type | PropertyType | String | 该属性使用的编辑器类型 |
EnumOptions | std::vector<HXString> | 空 | Enum 类型的下拉选项列表 |
Id | HXString | 空 | 可选标识,方便你区分属性 |
PropertyGridProfile 配置
| 成员 | 类型 | 默认值 | 说明 |
|---|---|---|---|
Size | HXPoint | {300, 400} | 控件宽高 |
RowHeight | int | 28 | 每行高度 |
NameWidth | int | 120 | 左列(属性名)的固定宽度 |
CustomEditorCallback | std::function | 空 | 自定义编辑器回调,返回 true 表示已接管绘制与输入 |
返回值
| 值 | 含义 |
|---|---|
>= 0 | 被点击的 Button 类型属性在 items 中的索引 |
-1 | 本帧没有按钮被点击 |
不要把返回值当成「哪个属性被修改了」的标志。文本框、滑块、复选框的修改是即时写回 PropertyItem 的。返回值只负责通知你「有按钮被按了」。
核心特性
两列 K-V 展示
左列 Name,右列编辑器,中间一条细线分割,整体风格干练整洁。NameWidth 控制左列固定宽度,右列自动占满剩余空间。
static std::vector<HX::PropertyItem> items = {
{HXStr("Player Name"), HXStr("Hero"), false, 0.0f, 0, HX::PropertyType::String},
{HXStr("HP"), HXStr(""), false, 100.0f, 0, HX::PropertyType::Float},
{HXStr("Is Alive"), HXStr(""), true, 0.0f, 0, HX::PropertyType::Bool},
{HXStr("Difficulty"), HXStr("Normal"), false, 0.0f, 0, HX::PropertyType::Enum, {HXStr("Easy"), HXStr("Normal"), HXStr("Hard")}},
{HXStr("Action"), HXStr("Respawn"), false, 0.0f, 0, HX::PropertyType::Button},
};
值编辑器一览
PropertyType | 右列表现 | 数据读写位置 |
|---|---|---|
String | 单行文本输入框 | StringValue |
Bool | 复选框 ☑ | BoolValue |
Int | 整型滑块(带数字显示) | IntValue |
Float | 浮点滑块(带数字显示) | FloatValue |
Enum | 点击循环切换枚举值 | StringValue(当前选中的文本) |
Button | 按钮 | 点击事件通过返回值通知 |
Int 和 Float 滑块目前采用默认范围。如果你有特殊范围需求,可以先做成 String 类型,然后在 CustomEditorCallback 里自己画一个带范围的滑块。
自定义编辑器回调
如果内置的六种编辑器满足不了你,CustomEditorCallback 让你完全接管右列的绘制和交互。
profile.CustomEditorCallback = [](HXBufferPainter* p, const HX::HXRect& rc, HX::PropertyItem& item) -> bool {
if (item.Name == HXStr("Custom Color")) {
// 自己在这里画一个颜色选择器...
return true; // 返回 true = 我已处理,控件不再画默认编辑器
}
return false; // 返回 false = 交还给默认逻辑
};
回调返回 true 后,控件不会再为该属性绘制任何默认内容,包括背景、边框、文字。如果你只是想在默认编辑器旁边加一个小图标,建议把 Type 设为 String,然后在主循环里额外绘制,而不是用这个回调。
完整示例
下面是一个游戏角色属性面板,涵盖所有六种编辑器类型和自定义编辑器。
#include <include/hex.h>
#include <include/impl/EasyX/hex_impl_easyx.h>
#include <vector>
int main() {
initgraph(800, 600);
setbkcolor(WHITE);
cleardevice();
HX::HXInitForEasyX();
HX::SetBuffer(GetWorkingImage());
BeginBatchDraw();
// 属性数据
static std::vector<HX::PropertyItem> items = {
{HXStr("Name"), HXStr("Archer"), false, 0.0f, 0, HX::PropertyType::String},
{HXStr("Level"), HXStr(""), false, 0.0f, 12, HX::PropertyType::Int},
{HXStr("HP"), HXStr(""), false, 85.5f, 0, HX::PropertyType::Float},
{HXStr("MP"), HXStr(""), false, 40.0f, 0, HX::PropertyType::Float},
{HXStr("Invincible"), HXStr(""), false, 0.0f, 0, HX::PropertyType::Bool},
{HXStr("Class"), HXStr("Ranger"), false, 0.0f, 0, HX::PropertyType::Enum,
{HXStr("Warrior"), HXStr("Mage"), HXStr("Ranger"), HXStr("Assassin")}},
{HXStr("Skill Points"),HXStr(""), false, 0.0f, 5, HX::PropertyType::Int},
{HXStr("Reset"), HXStr("Restore"), false, 0.0f, 0, HX::PropertyType::Button},
};
static HX::PropertyGridProfile pg;
pg.Size = {360, 480};
pg.NameWidth = 130;
ExMessage msg;
while (true) {
HX::HXBegin();
while (peekmessage(&msg)) {
HX::PushMessage(HX::GetHXMessage(&msg));
}
HX::Window(HXStr("PropertyGrid Demo"), HX::WindowProfile());
HX::Text(HXStr("Character Inspector"));
int clicked = HX::PropertyGrid(HXStr("char_props"), items, pg);
if (clicked >= 0) {
// 按钮被点击
wprintf(HXStr("Button clicked: %s\n"), items[clicked].Name.c_str());
if (items[clicked].Name == HXStr("Reset")) {
items[1].IntValue = 1;
items[2].FloatValue = 100.0f;
items[3].FloatValue = 50.0f;
items[5].StringValue = HXStr("Warrior");
}
}
// 演示:实时显示 HP
HX::Text(HXStr("Current HP: ") + std::to_wstring((int)items[2].FloatValue));
HX::End();
HX::Render();
FlushBatchDraw();
Sleep(16);
}
closegraph();
return 0;
}
items 和 pg 都用了 static。这不仅仅是为了持久化——PropertyGrid 内部通过 id 在 HXStatePool 中缓存了滚动位置和焦点状态。如果你每帧都新建 items 向量,焦点和选中状态会全部丢失,编辑体验非常糟糕。
常见问题
Q: Enum 类型是怎么切换的?
A: 点击右列文本即可循环切换到 EnumOptions 的下一项。如果你想做成下拉框样式,目前需要借助 CustomEditorCallback 自己实现。
Q: Int / Float 滑块的范围是多少?
A: 默认滑块范围通常是 0~100(浮点)或一个常用整型范围。如果你的数值超出这个区间,建议先显示为 String 让用户直接输入,或者使用自定义编辑器画一个带精确范围的滑块。
Q: 能不能支持嵌套对象 / 分组折叠?
A: 当前版本 PropertyGrid 是扁平列表。如果你需要分组,可以在 items 里插入 Type = Button 的项作为视觉分组头,或者并列使用多个 PropertyGrid。
Q: 我想让属性名右对齐,怎么改?
A: 目前 NameWidth 是固定左对齐。如果排版需求强烈,可以用 CustomEditorCallback 拦截整行绘制,完全自定义布局。