camera raw 界面如下:
需求就是根据 windows api 来操作界面右边的色温、色调、曝光等属性,进而对图片进行调色。根据 spy++ 捕获的窗口信息,理论上是可以拿到并修改值的。
根据 class 可以先拿到窗口句柄:
#define CAMERA_RAW_CLASS_NAME "PSFilter_WindowClass"
HWND getCameraRawHwnd()
{
HWND h;
int len;
while (true) {
h = FindWindow(TEXT(CAMERA_RAW_CLASS_NAME), NULL);
if (h == 0) {
return 0;
}
len = GetWindowTextLength(h);
if (len == 0) { // 删除空窗口句柄
SendMessage(h, WM_CLOSE, NULL, NULL);
SendMessage(h, WM_DESTROY, NULL, NULL);
SendMessage(h, WM_NCDESTROY, NULL, NULL);
} else {
break;
}
}
return h;
}
这里并没有直接调用 FindWindow() 方法,是因为在后来的实践中发现,当关掉 camera raw 窗口时,句柄可能还在,这样当再次打开 camera raw 窗口时,就存在两个 class 都为 PSFilter_WindowClass 的的句柄,像下面这种情况:
而 FindWindow() 方法只能获取到第一个,而这个正是已关闭窗口的句柄缓存,拿不到任何数据。解决这个问题其实也可以调用 EnumWindows() 方法枚举出所有的窗口,然后拿 text 是否为空进行比较。但私以为这样效率较低,故而写了个 while 循环,若查询到的窗口 text 为空,那么直接调用 SendMessage() 方法向系统发送 WM_DESTROY 等消息删除该句柄,再重新查询,直到找到这个窗口为止,就目前来看循环也只会执行两三次而已。
找到主窗口句柄后,就该寻找具体某个属性的值了,其实这里每个属性也都是一个子窗口。
代码如下:
TestCache testCache; // TestCache 类型后文有说明
static int num = 0;
EnumChildWindows(h, getMapData, NULL);
// 获取所有属性及对应值并写入 map
BOOL CALLBACK getMapData(HWND hwnd, LPARAM lparam)
{
int len = SendMessage(hwnd, WM_GETTEXTLENGTH, NULL, NULL);
if (len == 0) {
cout << endl;
return true;
}
++num;
++len;
TCHAR *text = (TCHAR *)malloc(sizeof(TCHAR) * len);
memset(text, 0, len);
SendMessage(hwnd, WM_GETTEXT, (WPARAM)len, (LPARAM)text);
try {
float val = stof(text);
testCache[num].h = hwnd;
testCache[num].val = val;
} catch (exception &e) {
cout << e.what() << endl;
}
return true;
}
这里主要是调用 EnumChildWindows() 方法枚举出 camera raw 的所有子窗口,并逐一调用自定义的 getMapData() 方法,方法体中主要是调用 SendMessage() 向系统发送消息 WM_GETTEXTLENGTH 和 WM_GETTEXT 来获取具体的值,可以看到程序中定义了个全局 static 变量 num,是因为每个属性并没有唯一的 id 与其对应,只能按某种规则对其筛选并排序,然后将排序的序号和每个属性对应,所以 ++num 的位置就比较重要了。
拿到值后将所有的值以及对应的序号就存入了 map,如果只是查询属性值,直接定义个简单的 map<int, float> 就可以了,但是如果要修改这个值呢?是再重新调用 EnumChildWindows() 方法枚举所有子窗口,找到要修改的序号,修改对应的值?这样虽然是可以,并且在修改对应值后立即中止枚举,但效率还是蛮低下的,
另外,我们已经知道获取元素值是调用 SendMessage() 方法向窗口句柄发送 WM_GETTEXT 消息,那么修改自然是向对应窗口发送 WM_SETTEXT 消息,既然是通过窗口句柄直接操作,那么在第一次枚举查询时可以将每个属性的窗口句柄一起记下来,这样在修改时直接向这个窗口发送修改消息即可。所以 testCache 结构定义如下:
typedef struct hwnd_val {
HWND h; // 窗口句柄
float val; // 属性值
} hwndVal;
typedef std::map<int, hwndVal> TestCache;
当遍历完所有属性后,属性序号、属性所属窗口句柄、属性值就都存在 testCache 中了,这时就可以根据序号对某个属性进行修改了:
int id = 2;
float step = 1.0;
string vals = "";
float new_val = testCache[id].val + step;
if (new_val > 0) {
vals = "+" + to_string(new_val);
} else if (new_val < 0) {
vals = to_string(new_val);
} else {
vals = "0";
}
char* val = const_cast<char*>(vals.c_str());
SendMessage(testCache[id].h, WM_SETTEXT, 0, (LPARAM)val);
这里假设的是对序号为 2 的属性值做 +1 处理,待程序执行后,就可以看到 camera raw 界面的色温值 +1 了。
至此也就实现了对 camera raw 界面元素数据的获取和修改,但具体到项目中应用,还需解决字符集、宽字符与字符转化、生成 dll 等诸多问题,此处不再赘述。