迭代结构;在RichEdit框中轻松显示结构字段和值

时间:2009-12-10 02:26:13

标签: c++ user-interface struct richedit c++builder-5

是否有更简单的方法可以在struct控件中显示RichEdit字段及其对应的值?

这就是我现在正在做的事情:

AnsiString s;

s = IntToStr(wfc.fontColor);
RichEdit1->Lines->Append(s);

等...

是否有比单独呼叫每个人更简单的方法?我想读取一个二进制文件,然后在RichEdit控件中显示我正在构建的小实用程序中的相应结构,并且没有找到其他方法。我知道如何读取二进制文件并将值读入struct

9 个答案:

答案 0 :(得分:24)

BOOST_FUSION_ADAPT_STRUCT似乎很适合这里。例如:

// Your existing struct
struct Foo
{
    int i;
    bool j;
    char k[100];
};

// Generate an adapter allowing to view "Foo" as a Boost.Fusion sequence
BOOST_FUSION_ADAPT_STRUCT(
    Foo,
    (int, i)
    (bool, j)
    (char, k[100])
)

// The action we will call on each member of Foo
struct AppendToTextBox
{
    AppendToTextBox(RichEditControl& Ctrl) : m_Ctrl(Ctrl){}

    template<typename T>
    void operator()(T& t)const
    {

        m_Ctrl.Lines.Append(boost::lexical_cast<std::string>(t));
    }

    RichEditControl& m_Ctrl;

};

// Usage:
void FillTextBox(Foo& F, RichEditControl& Ctrl)
{
    boost::fusion::for_each(F, AppendToTextBox(Ctrl));
}

答案 1 :(得分:11)

如果我理解正确,原始问题的核心是如何迭代结构。简而言之,正如Jerry Coffin在评论中指出的那样,这是不可能做到的。我将尝试解释原因,然后我将尝试解释如何做下一个最好的事情。

结构作为整体数据存储在内存中,没有任何描述其结构的元数据。例如,以下结构:

struct Foo {
    char a;
    char b;
    char c;
    int i;
}

Foo f = {'x', 'y', 'z', 122};

可以使用十六进制表示法在内存中表示如下

78 79 7A FF 7A 00 00 00

其中前3个字节包含char字段,第四个是用于填充的随机值,接下来的四个字节是整数122的little-endian表示。这种布局因编译器和编译器以及系统而异。系统。简而言之,二进制表示不会告诉您数据是什么或存储单个字段的位置。

那么编译器如何访问结构中的字段?代码

char c = f.c;

被翻译成像

这样的指令
COPY BYTE FROM ([address of f] + 2) TO [address of c]
换句话说,编译器将字段的文字偏移量编码到代码中。同样,这对我们没有帮助。

因此,我们必须自己注释结构。这可以通过在结构内添加信息以将其转换为一种键值存储或通过添加第二结构来完成。您不想更改原始结构,因此可以采用第二种结构。

我假设你的结构只包含基本类型:int,char等。如果你是结构中复杂的其他类,那么我建议将一个ToString()方法添加到它们的基类并调用该方法 - 这是C#和Java是如何做到的。

Foo tmp;

#define FIELD_OFFSET(f) ((char*)&(tmp.f) - (char*)&tmp)

enum FieldType { INT_FIELD, CHAR_FIELD, OBJECT_FIELD };

struct StructMeta {
    FieldType type;
    size_t offset;
};

StructMeta[] metadata = {
   {CHAR_FIELD, FIELD_OFFSET(a)},
   {CHAR_FIELD, FIELD_OFFSET(b)},   
   {CHAR_FIELD, FIELD_OFFSET(c)},
   {INT_FIELD, FIELD_OFFSET(i)},
   {OBJECT_FIELD, FIELD_OFFSET(o)},
}

void RenderStruct(Foo* f)
{
    for (int i = 0; i < sizeof(metadata)/sizeof(StructMeta); i++)
    {
        switch (metadata[i].type)
        {
             case CHAR_FIELD:
                 char c = *((char*)f + metadata[i].offset);
                 // render c
                 break;
             case INT_FIELD:
                 int i = *(int*)((char*)f + metadata[i].offset);
                 // render i
                 break;
             case OBJECT_FIELD:
                 Object* o = (object*)((char*)f + metadata[i].offset);
                 const char* s = o->ToString();
                 // render s
                 break;    
        }
    }
}

注意:所有指针算术都应该在(char *)指针上完成,以确保偏移量被解释为字节。

答案 2 :(得分:5)

除非您构建自己的元数据来描述结构,否则无法迭代结构的成员。 C ++编译器根本不会自动发出您需要的信息。

但是,通过一些宏魔术,您可以非常轻松地构建您需要的元数据。多年前我写了一些代码来实现这一点(实际上是一个完整的Windows自定义控件),我仍然一直使用它。

基本技巧是使用一点宏魔法来获取编译器来帮助你构建元数据。

// this is the structure I want to iterate
typedef struct {
   int foo;
   char bar[16];
} StructIWantToIterate;

// this is the metadata I need for each field of the structure
typedef struct {
   char * pszFieldName;
   size_t oFieldOffset;
   size_t cbFieldSize;
   int    eType;
} MyStructMeta;

// these are the field types I need to handle.
enum {
  type_is_int,
  type_is_char,
};

// these macros help to emit the metadata
#define NUMELMS(ary)     (sizeof(ary)/(sizeof(ary)[0]))
#define FIELDOFF(tag,fld)  ((size_t)&(((tag *)0)->fld))
#define FIELDSIZ(tag,fld)  sizeof(((tag *)0)->fld)
#define STDFLD(tag,fld,as)  #fld, FIELDOFF(tag,fld), FIELDSIZ(tag,fld), as

// now we declare the metadata for the StructIWantToIterate structure
#undef MYFLD
#define MYFLD(fld,as) STDFLD(StructIWantToIterate,fld,as)
static const MyStructMeta aMeta[] = {
   MYFLD(foo, type_is_int), // expands to "foo", 0, sizeof(int), type_is_int
   MYFLD(bar, type_is_char),// expands to "bar", sizeof(int), 16, type_is_char
};

// and when we want to do the iteration,  assume ptr is a pointer to an instance
// of StructIWantToIterate

for (int ii = 0; ii < NUMELMS(aMeta); ++ii)
{
   char szLine[100]; // pick your own worst case line size.

   // get a pointer to the current field within the struct
   void * pfld = ((byte*)ptr) + aMeta[ii].oFieldOffset;

   // print out the field data based on the type_is_xxx information
   switch (aMeta[ii].eType)
   {
      case type_is_int:
         sprintf(szLine, "%s : %d", aMeta[ii].pszFieldName, *(int*)pfld);
         break;

      case type_is_char:
         sprintf(szLine, "%s : %*s", 
                aMeta[ii].pszFieldName, 
                aMeta[ii].cbFieldSize, 
                pfld);
         break;
   }
   // send it to the richedit control
   RichEdit1->Lines->Append(asLine);    
}

答案 3 :(得分:3)

没有办法迭代普通结构的成员。 您必须在结构声明之外提供此信息。

您可以在编译时执行此操作,如前面的一些答案所示。 但是,您也可以在运行时执行此操作。 这类似于一些“序列化”库的工作方式。

您可以拥有以下课程:

class MemberStore
{
public:
  template<typename Base>
  MemberStore(const Base &base) : 
    m_basePtr(reinterpret_cast<const char*>(&base))
  {}

  template<typename Member>
  MemberStore& operator&(const Member &classMember){
    DataInfo curMember;
    curMember.m_offset = reinterpret_cast<const char*>(&classMember) - m_basePtr;
    curMember.m_func = &CvtChar<Member>;
    m_members.push_back(curMember);
    return *this;
  }

  std::string convert(size_t index) {
    return m_members[index].m_func(m_basePtr + m_members[index].m_offset);
  }

  size_t size() const {
    return m_members.size();
  }

protected:
  template<typename Type> 
  static std::string CvtChar(const void *data) {
    std::stringstream str;
    str << *reinterpret_cast<const Type*>(data);
    return str.str();
  }

private:
  struct DataInfo {
    size_t m_offset;
    std::string (*m_func)(const void *data);
  };
  std::vector<DataInfo> m_members;
  const char *m_basePtr;
};

此类包含“类成员”(MemberStore :: DataInfo)的向量,每个成员都有:

  • 从班级抵消。
  • 将它们转换为std :: strings的方法。如果可以使用std :: stringstream进行转换,则会自动生成此方法。如果不可能,则应该可以对模板进行专门化。

您可以使用&amp;添加元素到此类。运算符(您可以连接多个&amp;运算符)。之后,您可以迭代到成员并使用其索引将它们转换为std :: string:

struct StructureIWantToPrint
{
  char a;
  int b;
  double c;
};

int main(int argc, wchar_t* argv[])
{
  StructureIWantToPrint myData;
  myData.a = 'b';
  myData.b = 18;
  myData.c = 3.9;

  MemberStore myDataMembers(myData);
  myDataMembers & myData.a & myData.b & myData.c;

  for(size_t i=0;i<myDataMembers.size();++i) {
    std::cout << myDataMembers.convert(i) << std::endl;
  }

    return 0;
}

应该可以修改MemberStore类,以便不会存储将成员转换为std :: string的方法,而是自动将数据插入到TextList中。

答案 4 :(得分:2)

我不使用C ++ Builder,所以有些细节可能有些偏差,但总体思路应该至少相当接近:

class richedit_stream { 
    TRichEditControl &ctrl;
public:
    richedit_stream(TRichEditControl &trc) : ctrl(trc) {}

    template <class T>
    richedit_stream &operator<<(T const &value) {
        std::stringstream buffer;
        buffer << value;
        ctrl.Lines->Append(value.str().c_str());
        return *this;
    }
};

基本思想非常简单:一个用于richedit控件的前端,它提供一个模板化的运算符&lt;&lt;。操作员将项目放入字符串流以将其转换为字符串。然后它获取结果字符串并将其附加到控件中的行。由于它是模板化的,它可以使用字符串流支持的所有常用类型。

这确实有缺点 - 如果没有更多工作,您将无法使用操纵器来控制数据的格式,因为它已转换为字符串。由于它使用字符串流将事物转换为字符串,因此它可能比显式编码每个转换类型的代码慢一些。与此同时,您可以使用相当干净,简单,惯用的代码,以换取相当小的投资。

答案 5 :(得分:1)

我建议创建用于写入文本框的模板化方法:

template <typename T>
void
Write_To_Textbox(const T& variable,
                 const std::string& variable_name,
                 TRichTextEdit & textbox)
{
  //...
}

然后使用一些剪切,复制,粘贴和正则表达式的替换编辑器功能,并创建一个“注释”功能:

void
annotate(TRichTextEdit& textbox)
{
  Write_To_Textbox(member1, "member1", textbox);
//...
}

注意:检查模板函数的语法,因为我不认为我在这个例子中说得对。

答案 6 :(得分:1)

由于结构中有相当多的字段,因此请使用解析器或编写自己的解析器来生成源代码以打印成员,其名称及其值。

作为一项有趣的练习,在编写实用程序时请自己动手。您可能会发现使用具有正则表达式搜索和替换功能的编辑器可能会更快。

否则抛弃您当前的设计并采用新的设计。我一直在使用记录和字段的设计。每个记录(结构)都有一个指向Field_Interface的一个或多个指针的向量。 Field_Interface包含get_field_name()get_sql_data_type_text()等方法。另外不要忘记Java收藏夹toString(),它将字段值作为字符串返回。此技术允许您迭代一个字段容器并打印出它们的值(使用toString)及其名称(使用get_field_name())。

添加访客模式以供阅读和写作(我称之为读者和作者),您可以拥有高度适应性的字段和记录,而无需更改其内部内容。此外,这可以很好地进入通用编程,您可以在不知道其类型的情况下操作字段和记录;或者在叶子层面处理过。

顺便说一句,在你等待完美答案的时候,你可以编写一个函数来“迭代”或访问结构的成员。

答案 7 :(得分:1)

因此,您需要在运行时提供类型信息。此元数据在编译时可用,但随后将被丢弃。我们只需要一种从编译器中拯救它的方法。

  1. 显式元数据,如antonmarkov和John Knoeller所证明的那样。您必须使其与结构保持同步,但它具有不触及原始结构定义的优点。

    1.1 代码生成 如果您的结构定义足够常规,您可以使用awk自动生成此元数据表。

  2. 元编程:如果你不介意重写结构(但保持布局相同,那么你保持二进制兼容性)你可以让编译器为你做繁重的工作。您可以使用Boost.tuple声明您的结构,并使用Boost.Fusion对其元素进行迭代。

答案 8 :(得分:1)

并不是说我认为这是一个很好的答案,但感觉应该包括它是出于完整性的原因。一种稍微不同的方法是使用Windows debugger extension APIs编写调试器扩展。您描述的任务几乎非常适合调试器扩展。我说几乎是因为我不确定在发布版本中包含它是一个非常好的计划。但是根据您需要此功能的位置,它可能是可能的。如果需要“内部”用于您自己的目的,它可能会起作用。如果需要在客户的网站上运行,那么由于需要运送额外的行李(调试符号),我不太愿意使用它。

您的环境也存在一个很大的潜在问题。您似乎正在使用C ++ Builder版本5.我不知道从该环境生成可与Windows调试工具一起使用的调试符号的方法。有一个实用程序map2dbg可以进行转换,但它显然至少需要C ++ Builder v6。