已解决:无法在 XtAddCallback 中将 Motif 小部件数组作为 client_data 传递?

问题描述

大家好,我得到的是一个真实程序的简化片段,该程序必须更新 4500 万个 Motif 标签小部件中的标签字符串。简化版:我创建了两个标签小部件并将它们的标签字符串设置为“文本”。我将这两个小部件放入标准 C 数组中,并将其作为 client_data 传递给使用 XtAddCallback 的回调函数。回调由按钮调用,它应该将标签字符串从“文本”更改为“不同文本”,但它只更改第一个标签小部件中的字符串。我假设无论出于何种原因,应该具有两个小部件数组的 client_data(一个 XtPointer)实际上只有第一个小部件。我很清楚我可以从作为 XtAddCallback 中第一个参数传入的父小部件访问标签小部件,然后使用 XtNametoWidget 来获取标签,但这将需要对 XtNametoWidget 进行 4500 万次调用,我宁愿避免这种情况。我也意识到我可以将所有标签小部件放入一个全局数组,然后在数组上索引以更改标签字符串,但全局变量也是我宁愿避免的。另一件很奇怪的事情是,如果没有第二个小部件,我会认为在其上调用 XtSetValues 会使程序崩溃,但事实并非如此。所以,如果有人有兴趣看看这个(可疑),这里是简化的片段,如果你将它命名为 arrays.c,它编译(至少在 Linux 上)使用 gcc -lXm -lXt -lX11 -o arrays arrays.c - 哦,出于不符合逻辑解释的原因,我被迫使用 Motif-2.1.32

/* Impossible to pass a widget array using XtAddCallback? */

#include <X11/Intrinsic.h>
#include <X11/Shell.h>
#include <stdio.h>
#include <Xm/Form.h>
#include <Xm/Label.h>
#include <Xm/PushB.h>

void pass_array_cb(Widget,XtPointer,XtPointer);

int main(int argc,char * argv[])
{
    XtAppContext app_context;
    xmstring text;

    Widget toplevel;
    Widget form,button,label_01,label_02;
    Arg av[10];
    int ac = 0;

    toplevel = XtOpenApplication(      
        &app_context,"arrays",NULL,&argc,argv,applicationShellWidgetClass,av,ac);

    form = XtVaCreateManagedWidget("form",xmFormWidgetClass,toplevel,XmNheight,100,XmNwidth,200,NULL);

    text = xmstringCreateLocalized((char *) "text");

    label_01 = XtVaCreateManagedWidget("label_01",xmLabelWidgetClass,form,60,20,XmNlabelString,text,XmNtopAttachment,XmATTACH_WIDGET,XmNtopWidget,NULL);

    label_02 = XtVaCreateManagedWidget("label_02",NULL);

    xmstringFree(text);

    Widget label_widgets[2] = { label_01,label_02 };

    button = XtVaCreateManagedWidget("button",xmpushButtonWidgetClass,label_02,NULL);

    XtAddCallback(button,XmNactivateCallback,pass_array_cb,*label_widgets);

    XtRealizeWidget   (toplevel);
    XtAppMainLoop     (app_context);

    return 0;
}

void pass_array_cb(Widget w,XtPointer client_data,XtPointer call_data)
{
    Widget (*label_widgets)[2] = (Widget (*)[2])client_data;
    xmstring text = xmstringCreateLocalized((char *)"different text");
    int i;
    for(i = 0; i < 2; i++)
    {
        printf("in the callback,widget index is %d\n",i);
        XtVaSetValues(*label_widgets[i],NULL);
    }
    xmstringFree(text);
}

解决方法

XtAddCallback(button,XmNactivateCallback,pass_array_cb,*label_widgets);

*label_widgetslabel_widgets[0] 完全相同,在您的情况下与 label_01 完全相同。您将 label_01 作为回调参数传递。它的类型为 Widget

Widget (*label_widgets)[2] = (Widget (*)[2])client_data;

label_widgets 现在是指向 Widget 的 2 元素数组的指针,它与您作为参数传递的 Widget 非常非常不同。

XtVaSetValues(*label_widgets[i],XmNlabelString,text,NULL);

*label_widgets[i]label_widgets[i][0] 相同。由于 label_widgets 是一个指针(见上文),因此它被视为指向数组第一个元素的指针。什么数组?二元素数组或Widget。所以它需要该数组的第 i 个元素,它恰好是一个 Widget 的 2 元素数组,并获取它的第 0 个小部件。不用说,任何地方都没有 Widget 的数组,所以这段代码只是徘徊在 La-La Land 上。

你想要这个:

传递一个指向 label_widgets 的第一个元素的指针。查看数组到指针衰减了解更多信息。

XtAddCallback(button,label_widgets);

接收指向 Widget 数组第一个元素的指针。

Widget* label_widgets = (Widget*)client_data;

使用指向第一个元素的指针对上述数组进行索引(所有数组索引都是这样工作的)。

XtVaSetValues(label_widgets[i],NULL);