【问题标题】:Faster way to create TCombobox at Runtime在运行时创建 TCombobox 的更快方法
【发布时间】:2016-06-27 03:14:26
【问题描述】:

我想在运行时用许多具有相同列表的组合框填写表单。它们还获得相同的事件处理程序,该处理程序取决于Sender 对象的名称。但是,这需要很长时间,我猜我做错了什么。

我正在使用 XE2 Rad Studio C++ Builder 和 VCL GUI。

编辑:这些框包含不同类型的内容,并分布在表单内的几个标签页中。但是,有必要一目了然地显示它选择的至少 80 个。在单击 TLabel 以选择不同的元素时,将它们替换为 TLabel 并创建一个 TCombobox 会更好吗?

代码与此类似:

void __fastcall TForm::TForm(){
    int i=0;
    TStringList* targetlist = new TStringList();
    targetlist->Add("Normal");
    targetlist->Add("Inverted");
    Vcl::Stdctrls::TComboBox **com = new Vcl::Stdctrls::TComboBox[512];
    for(i=0;i<512;++i){
        com[i]=new Vcl::Stdctrls::TComboBox(this);
        com[i]->Parent=this;
        com[i]->Name.printf(L"Combo_%d", i);
        com[i]->SetBounds(10, 198 + 20 * i, 130, 200);
        com[i]->Items = targetlist;
        com[i]->ItemIndex = 0;
        com[i]->Style = csDropDownList;
        com[i]->OnChange = MyComboTriggerChange;
    }
}

在我的机器上一次迭代似乎需要大约 20 毫秒(使用 std::clock 测试),这使得这部分大约需要 10 秒。指针在表单销毁时被删除。为了简化,我只是把他们的声明放在这里。

有没有更好的方法来创建多个组合框?也许克隆它们?

【问题讨论】:

  • 很抱歉,一个表单上的 512 个组合框简直太荒谬了。您需要认真重新考虑您的用户界面。它们都包含相同的项目这一事实使它更加可笑。
  • @KenWhite 感谢您的评论!好吧,老实说,有 8 种不同类型的内容,但我在这里简化了一点,因为这对问题无关紧要……表单需要显示很多信息。我可以用标签替换 512 个框,然后在单击标签时显示一个框,但我不确定这会更快...
  • @Julian:当然会。一方面,TLabel 是一个图形控件,因此它不会像TComboBox 这样的窗口控件占用那么多资源。 512 图形控件将比 512 窗口控件运行得更快。如果你真的很喜欢冒险,你可以编写自己的自定义图形控件,用单个控件替换这 512 个单独的标签(我曾经这样做是用几个自定义图形控件实例替换数百个彩色 TPanel 控件,显示多个彩色方块,性能显着提高,同时开销减少了 20 多%)。
  • @Julian:第二,现有的控件可以显示多个项目并在单击时提供对它们的嵌入式编辑,例如TListViewTStringGridTValueListEditor等。

标签: c++ c++builder vcl c++builder-xe2 tcombobox


【解决方案1】:

真的需要重新设计您的 UI。在一个屏幕上使用具有相同值列表的 512 TComboBox 控件没有逻辑意义,而且浪费时间和资源。有更好的方法可以在屏幕上显示 512 个字符串,例如报告模式下的 TListViewTListBox(它们都支持虚拟模式,因此它们可以共享公共数据而不会浪费内存)。或者使用TValueListEditorTStringGridesPickList 内联编辑器。或者,如果您真的很喜欢冒险,可以从头开始编写自定义控件,这样您就可以使用 1 个高效控件而不是 512 个单独的控件。任何东西都比 512 个TComboBox 控件好。

话虽如此,TComboBox 不支持虚拟模式,例如 TListBoxTListView 支持,但您仍然可以进行一些优化以加快您的 TComboBoxes 一点点:

  1. 不要对相同的TStringList 内容制作 512 个副本。您添加到TComboBox::Items 的任何内容都存储在TComboBox 的内存中。您应该努力重用您的单个TStringList,并根据需要让一切委托给它。在这种情况下,您可以将TComboBox::Style 属性设置为csOwnerDrawFixed,并使用TComboBox::OnDrawItem 事件按需绘制TStringList 字符串。您仍然需要为每个 TComboBox 添加字符串,但它们至少可以是空字符串。

  2. 子类TComboBox 覆盖其虚拟CreateParams() 方法并删除CBS_HASSTRINGS 窗口样式,然后TComboBox 实际上不需要在其内存中存储空字符串。

试试这样的:

class TMyComboBox : public Vcl::Stdctrls::TComboBox
{
    typedef Vcl::Stdctrls::TComboBox inherited;

private:
    TStrings *fSharedItems;

    void __fastcall SetSharedItems(TStrings *Values)
    {
        if (fSharedItems != Values)
        {
            fSharedItems = Values;

            Items->BeginUpdate();
            try
            {
                Items->Clear();
                if (fSharedItems)
                {
                    for (int i = 0; i < fSharedItems->Count; ++i)
                        Items->Add(L"");
                }
            }
            __finally
            {
                Items->EndUpdate();
            }
        }
    }

protected:
    virtual void __fastcall CreateParams(TCreateParams &Params)
    {
        inherited::CreateParams(Params);
        Params.Style &= ~CBS_HASSTRINGS;
    }

    virtual __fastcall DrawItem(int Index, TRect Rect, TOwnerDrawState State)
    {
        // draw the items however you want...

        if (fSharedItems)
            Canvas->TextRect(Rect.Left, Rect.Top, fSharedItems->Strings[Index]);
    }

public:
    __fastcall TMyComboBox(TComponent *Owner)
        : Vcl::Stdctrls::TComboBox(Owner)
    {
        Style = csOwnerDrawFixed;
    }

    __property TStrings* SharedItems = {read=fSharedItems, write=SetSharedItems};
};

class TMyForm : public TForm
{
    ...
private:
    TStringList* targetlist;
    TMyComboBox **com;
    void __fastcall MyComboTriggerChange(TObject *Sender);
    ...
public:
    __fastcall TMyForm(TComponent *Owner);
    __fastcall ~TMyForm();
    ...
};

__fastcall TMyForm::TMyForm(TComponent *Owner)
    : TForm(Owner)
{
    targetlist = new TStringList;
    targetlist->Add("Normal");
    targetlist->Add("Inverted");

    com = new TMyComboBox*[512];
    for(int i=0;i<512;++i)
    {
        com[i] = new TMyComboBox(this);
        com[i]->Parent = this;
        com[i]->Name = String().sprintf(L"Combo_%d", i);
        com[i]->SetBounds(10, 198 + 20 * i, 130, 200);
        com[i]->SharedItems = targetlist;
        com[i]->ItemIndex = 0;
        com[i]->OnChange = &MyComboTriggerChange;
    }
}

__fastcall TMyForm::~TMyForm()
{
    delete targetlist;
    delete[] com;
}

void __fastcall TMyForm::MyComboTriggerChange(TObject *Sender)
{
    TMyComboBox *cb = static_cast<TMyComboBox*>(Sender);
    // use targetlist->Strings[cb->ItemIndex] as needed...
}

【讨论】:

  • 感谢您的详细回答,回答我的问题并为我提供更好的解决方案的建议!不过,我不喜欢使用纯表格布局,因为这些信息被分割并与标签和按钮组合在一起......现在我计划编写一个自定义 TLabel,如果点击显示的信息时会弹出一个 TEdit 或 TCombobox。 . 我需要 32 个自定义 TLabels 然后在表单上...你认为这是一种有效的方法还是对于表单来说仍然是太多的对象?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-26
  • 1970-01-01
  • 1970-01-01
  • 2012-06-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多