Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > С/С++: Кроссплатформенное программирование, Qt/Gtk+/wxWidgets > [wx] Создание динамического массива виджетов


Автор: Fabio 24.9.2008, 20:33
Доброго времени суток, форумчане!

Столкнулся со следующей проблемой:
Пытаюсь создать на wxPanel "таблицу" из wxTextCtrl, допустим 15x7 (не спрашивайте зачем и не
предлагайте wxGrid, сам про него знаю, просто вопрос - дело принципа).
Сама wxPanel располагается в wxFrame, т. е. стандартное "борландовское" окошко-диалог smile
Обьявлять 105 указателей вручную мне не очень улыбается, поэтому пришла идея создать
двухмерный динамический массив указателей на wxTextCtrl, и в конструкторе wxFrame распределить
под них память. Вот как это у меня выглядит:
Код

// объявление наследника wxApp и т. д.
...

class MyFrame : public wxFrame
{
public:
  MyFrame(const wxString &title, const wxSize &size, long style);
  void SomeMethod();
private:
  wxPanel *panel;
  wxTextCtrl ***table;
  wxBoxSizer *container, **columns;
};

MyFrame::MyFrame(const wxString &title, const wxSize &size, long style)
         : wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, size, style)
{
  int i, j;
  panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(800, 250), wxTAB_TRAVERSAL);

  //==== создание массива полей ввода =======
  table = new wxTextCtrl **[7];
  container = new wxBoxSizer(wxHORIZONTAL);
  columns   = new wxBoxSizer *[7];
  for (i=0; i<7; i++) { // по столбцам
    columns[i] = new wxBoxSizer(wxVERTICAL);
    table[i] = new wxTextCtrl *[15];
    for (j=0; j<15; j++) { // по строкам
      table[i][j] = new wxTextCtrl(panel, wxID_ANY, wxT("SomeText"), wxDefaultPosition,
                                wxSize(60, 18));
      columns[i]->Add((wxWindow *)table[i][j]);
    }
    container->Add(columns[i]);
  }
  SetSizer(container);
  //========================================================
  table[0][0]->SetValue(wxT("MyFrame constructor")); // проходит без ошибок!
}
void MyFrame::SomeMethod()
{
  // например поле ввода на первой строке первого столбца
  table[0][0]->SetValue(wxT("SomeMethod call")); // <- прога падает с ошибкой Segmentation fault
}

После компиляции программа запускается нормально, появляется импровизированная таблица полей ввода,
но, как показано в коде, при обращении к массиву из конструктора MyFrame всё проходит нормально
и текст меняется, но стоит вызвать метод SomeMethod из любого места, напр., при нажатии на кнопку,
то программа вылетает с ошибкой.
Хочу узнать мнение профессионалов - может меня вообще не туда понесло? smile
Есть подозрение, что C-массивы в таком виде не годятся для объектов, т. к. объекты сложно устроены в памяти. Пробовал wxObjArray для одномерного массива - компилятор ругается на то, что у wxTextCtrl
закрытый конструктор копирования :(.
Интересно, как-бы этот вопрос решили вы? Какие существуют методы работы с массивами объектов?

Автор: mes 24.9.2008, 22:49
я не пойму зачем пользоваться Си масивами особенно в случае многомерных массивов , когда есть стловские контейнеры.
плюс способ реализации массива всегда должен оставаться за кадром, для этого он должен быть разгружен функцией.

Цитата(Fabio @  24.9.2008,  20:33 Найти цитируемый пост)
компилятор ругается на то, что у wxTextCtrl
закрытый конструктор копирования :(.

А при чем тут закрытость  конструктора, когда хранить вы должны указатели, а не сами объекты. Соответственно wxObjArray не подходит.
см. WX_DEFINE_ARRAY_PTR(wxTextCtrl*, ..)

Цитата(Fabio @  24.9.2008,  20:33 Найти цитируемый пост)
Интересно, как-бы этот вопрос решили вы? Какие существуют методы работы с массивами объектов? 

Я бы создал класс создал бы класс WTextCtrlTable с методами Resize и GetPtr  , которые предоставляли бы работу с двухмерным массивом, а для контейнера использовал бы одномерный массив указателей (std::vector или wxPtrArray). 

Автор: Fabio 24.9.2008, 23:44
Цитата

А при чем тут закрытость  конструктора...

В этом случае я пытался сделать массив не из указателей, с помощью описанного в справке к wxWidgets 2.8.8 способа,
приведу кусок примера из раздела про wxArray:
Код

#include <wx/dynarray.h>

// we must forward declare the array because it is used inside the class
// declaration
class MyDirectory;
class MyFile;

// this defines two new types: ArrayOfDirectories and ArrayOfFiles which can be
// now used as shown below
WX_DECLARE_OBJARRAY(MyDirectory, ArrayOfDirectories);
WX_DECLARE_OBJARRAY(MyFile,      ArrayOfFiles);

class MyDirectory
{
...
    ArrayOfDirectories m_subdirectories; // all subdirectories
    ArrayOfFiles       m_files;          // all files in this directory
};

...

// now that we have MyDirectory declaration in scope we may finish the
// definition of ArrayOfDirectories -- note that this expands into some C++
// code and so should only be compiled once (i.e., don't put this in the
// header, but into a source file or you will get linking errors)
#include <wx/arrimpl.cpp> // this is a magic incantation which must be done!
WX_DEFINE_OBJARRAY(ArrayOfDirectories);

// that's all!

Что-то типа этого у меня получилось с wxTextCtrl. Но при компиляции вываливается ошибка (дословно не помню)
и открывается заголовочный файл textctrl.h с установленным курсором ошибки на строке с текстом
DECLARE_NO_COPY_CLASS(wxTextCtrlBase)
Код

#define DECLARE_NO_COPY_CLASS(classname)        \
    private:                                    \
        classname(const classname&);            \
        classname& operator=(const classname&);

Из файла defs.h, в доказательство к утверждению про конструкторы копирования.
Вот такие вот дела...

Вообще странно как-то, как будто указатели в массиве становятся недействительными после отработки
конструктора MyFrame. Закрытый указатель ***table находится в области видимости метода SomeMethod,
а всё равно при table[m][n]->AnyMethod прога падает.

Работаю в C::B + MinGW + wxWidgets 2.8.8 под MSW.

PS. Заметил в своём первом посте ошибку (в данном случае не принципиальную) - в циклах распределения
памяти перепутал строки со столбцами - массив 7x15, а не 15x7, но графически в окне выглядит всё правильно из-за wxBoxSizer-ов smile .

Автор: mes 25.9.2008, 00:24
Цитата(Fabio @  24.9.2008,  23:44 Найти цитируемый пост)
В этом случае я пытался сделать массив не из указателей, с помощью описанного в справке к wxWidgets 2.8.8 способа,..доказательство к утверждению про конструкторы копирования

a никто и не спорит, что конструктор копирования закрыт. 
Цитата(mes @  24.9.2008,  22:49 Найти цитируемый пост)
Соответственно wxObjArray не подходит.

Я лишь сказал что делать массив надо указателей, а не объектов, поэтому конструктор копирования просто по боку. )

Цитата(Fabio @  24.9.2008,  23:44 Найти цитируемый пост)
Вообще странно как-то, как будто указатели в массиве становятся недействительными после отработки конструктора MyFrame. Закрытый указатель ***table находится в области видимости метода SomeMethod,
а всё равно при table[m][n]->AnyMethod прога падает

такого быть не может, где то косяк. Можно и поискать, но я бы посоветовал сразу переписать с нормальным подходом
и  ошибка сама должна вылезти наружу или самоликвидироваться.

Автор: mes 25.9.2008, 00:42
вставил Ваш код в проект - никаких вылетов не наблюдается. Текст в 1 м контроле меняется на SomeMethod call.

Автор: Fabio 27.9.2008, 00:33
Спасибо за ответы, mes.
То, что код работает - это хорошо, но я писАл вначале
Цитата

... стоит вызвать метод SomeMethod из любого места, напр., при нажатии на кнопку,
то программа вылетает с ошибкой

т. е. если создадим кнопку на панели, законнектим (я сделал обработчик события через метод Connect,
можно и через таблицу событий, результат один и тотже) к ней процедуру OnMyClick, отрабатывающую
по клику на кнопку, и если в OnMyClick вызвать SomeMethod, то программа завершает выполнение с
ошибкой! Даже если просто напрямую обратиться к массиву эдитов, то прога падает smile .

Автор: mes 27.9.2008, 00:49
Цитата(Fabio @  27.9.2008,  00:33 Найти цитируемый пост)
но я писАл вначале

Цитата(Fabio @  27.9.2008,  00:33 Найти цитируемый пост)

т. е. если создадим кнопку на панели, законнектим (я сделал обработчик события через метод Connect,
можно и через таблицу событий, результат один и тотже) к ней процедуру OnMyClick


Я так и подумал что Вы расцените мои слова неправильно ) 
Цитата(mes @  25.9.2008,  00:42 Найти цитируемый пост)
Текст в 1 м контроле меняется на SomeMethod call. 

Я тоже законнектил, правда через статическую карту. И при событии текст в первом контроле менялся.
Никаких вылетов не было. ) Так что причина в другом месте )

Автор: Fabio 27.9.2008, 09:35
Спасибо за помощь!
Было-бы прекрасно, если-бы Вы привели Ваш код здесь, тогда я сравню со своим и надеюсь
найду косяк. Хочу просто разобраться в этом вопросе до конца smile .
Вот мой код полностью:
Код

// объявление наследника wxApp и т. д.
...

class MyFrame : public wxFrame
{
public:
  MyFrame(const wxString &title, const wxSize &size, long style);
  void SomeMethod();
  void OnMyClick(wxCommandEvent &event);
private:
  wxPanel *panel;
  wxButton *btnTest;
  wxTextCtrl ***table;
  wxBoxSizer *container, **columns;
};

MyFrame::MyFrame(const wxString &title, const wxSize &size, long style)
         : wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, size, style)
{
  int i, j;
  panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(800, 250), wxTAB_TRAVERSAL);

  //==== создание массива полей ввода =======
  table = new wxTextCtrl **[7];
  btnTest = new wxButton(panel, wxID_ANY, wxT("Test"), wxDefaultPosition,
                                          wxSize(60, 20));
  container = new wxBoxSizer(wxHORIZONTAL);
  columns   = new wxBoxSizer *[7];
  for (i=0; i<7; i++) { // по столбцам
    columns[i] = new wxBoxSizer(wxVERTICAL);
    table[i] = new wxTextCtrl *[15];
    for (j=0; j<15; j++) { // по строкам
      table[i][j] = new wxTextCtrl(panel, wxID_ANY, wxT("SomeText"), wxDefaultPosition,
                                wxSize(60, 18));
      columns[i]->Add((wxWindow *)table[i][j]);
    }
    container->Add(columns[i]);
  }
  btnTest->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
                                wxCommandEventHandler(MyFrame::OnMyClick));
  container->Add(btnTest);
  SetSizer(container);
  //========================================================
  table[0][0]->SetValue(wxT("MyFrame constructor")); // проходит без ошибок!
}
void MyFrame::SomeMethod()
{
  // например поле ввода на первой строке первого столбца
  table[0][0]->SetValue(wxT("SomeMethod call")); // <- прога падает с ошибкой Segmentation fault
}
void MyFrame::OnMyClick(wxCommandEvent &event)
{
  SomeMethod(); // ошибка
  table[0][0]->SetValue(wxT("SomeMethod call")); // ошибка
}

Автор: mes 27.9.2008, 11:03
Цитата(Fabio @  27.9.2008,  09:35 Найти цитируемый пост)
Было-бы прекрасно, если-бы Вы привели Ваш код здесь


Код

class MyFrame : public wxFrame
{
    DECLARE_EVENT_TABLE();
        ....
    wxButton *WxButton1;
       ...
        enum
        {
            ID_WXBUTTON1 = 1003
        };
};


BEGIN_EVENT_TABLE(MyFrame ,wxFrame)
    EVT_CLOSE(Project2Frm::OnClose)
    EVT_BUTTON(ID_WXBUTTON1,MyFrame ::WxButton1Click)
END_EVENT_TABLE()

MyFrame::MyFrame(const wxString &title, const wxSize &size, long style)
         : wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, size, style)
{
    WxBoxSizer1 = new wxBoxSizer(wxHORIZONTAL);
    this->SetSizer(WxBoxSizer1);
    this->SetAutoLayout(true);

    WxButton1 = new wxButton(this, ID_WXBUTTON1, wxT("WxButton1"), wxPoint(5,5), wxSize(75,25), 0, wxDefaultValidator, wxT("WxButton1"));
    WxButton1->SetFont(wxFont(8, wxSWISS, wxNORMAL,wxNORMAL, false, wxT("Tahoma")));
    WxBoxSizer1->Add(WxButton1,0,wxALIGN_CENTER | wxALL,5);

  int i, j;
  panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(800, 250), wxTAB_TRAVERSAL);
  //==== создание массива полей ввода =======
  //  Ваш код из 1го примера
  // ...

 //  вместо  SetSizer(container);
   panel->SetSizer(container);

  GetSizer()->Add (panel, 0, wxALL, 5);
  //========================================================
  table[0][0]->SetValue(wxT("MyFrame constructor")); // проходит без ошибок!

    GetSizer()->Layout();
    GetSizer()->Fit(this);
    GetSizer()->SetSizeHints(this);
    Center();
}

void MyFrame::WxButton1Click(wxCommandEvent& event)
{
     SomeMethod();
     table[0][1]->SetValue(wxT("Direct call"));
}


Добавлено через 13 минут и 8 секунд
Цитата(Fabio @  27.9.2008,  09:35 Найти цитируемый пост)
      table[i][j]->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
                        wxCommandEventHandler(OnMyClick::OnTempToggle));

Нельзя ли поподробней как Вы пришли к этой строчке?  ) 
Вот тут у Вас и вылетает.
При таком коде при событии  wxEVT_COMMAND_BUTTON_CLICKED в обработчике ячейки таблицы (wxTextCtrl)
вызовется метод класса wxTextCtrl со смещением метода OnTempToggle в классе OnMyClick (кстати а где он у Вас описан ?).

Автор: Fabio 27.9.2008, 15:58
Извиняюсь, вышла описка! Писал по памяти с просони smile . Свой пост поправил - именно это не работает.
Но!, попробовав снова со статической картой, основываясь на Вашем примере всё действительно
заработало, спасибо! И всё же нужно добить этот вопрос, похоже я как-то не так работаю с
методом Connect. Надо разбираться, так как у меня есть необходимость менять динамически
обработчики в ходе выполнения программы, да и к примеру назначить один и тот же обработчик
OnClick для n-го количества кнопок через карту - гхммм...

PS. Такие ламерские вопросы у меня из-за того, что только недавно начал разбираться с
кроссплатформенными GUI-библиотеками (выбор пал на wxWidgets).
Раньше GUI (и всё остальное smile ) писал в Borland C++ Builder-е.

Автор: mes 28.9.2008, 00:28
Цитата(Fabio @  27.9.2008,  15:58 Найти цитируемый пост)
Свой пост поправил - именно это не работает.


Цитата(Fabio @  27.9.2008,  09:35 Найти цитируемый пост)
 btnTest->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
                                wxCommandEventHandler(MyFrame::OnMyClick));


это фактически тоже самое что и было до этого

Добавлено через 31 секунду
Цитата(Fabio @  27.9.2008,  15:58 Найти цитируемый пост)
И всё же нужно добить этот вопрос, похоже я как-то не так работаю с
методом Connect.

Ага,  именно так.

Добавлено через 1 минуту и 57 секунд
Цитата(Fabio @  27.9.2008,  15:58 Найти цитируемый пост)
Надо разбираться, так как у меня есть необходимость менять динамически
обработчики в ходе выполнения программы, да и к примеру назначить один и тот же обработчик
OnClick для n-го количества кнопок через карту - гхммм...


Если для таблицы то вполне подходит и статическая карта. Только каждой ячейке задавайте уникальный id.

Добавлено через 7 минут и 22 секунды
А.. да..  не создавайте ячейки сразу в главном окне - лучше объедените с панелью и пусть обработчик панели обрабатывает все ячейки,
а для внешнего мира генерирует подходящие сообщения. (сообщения можно использовать как от какого нибудь подходяшего контрола, либо определить самим.)

Автор: Fabio 28.9.2008, 10:47
Большое спасибо за советы! Буду разбираться дальше.
Кстати, в обработчике OnMyClick, который я назначил кнопке btnTest через Connect, любой другой код
работает нормально, а с массивом wxTextCtrl-ов работать всёравно не хочет, ну да ладно, так и придётся
работать со статическими картами сообщений, чего не хотелось.

Автор: mes 28.9.2008, 12:34
Цитата(Fabio @  28.9.2008,  10:47 Найти цитируемый пост)
Кстати, в обработчике OnMyClick, который я назначил кнопке btnTest через Connect,


В вашем куске кода :
Код

 btnTest->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
                                wxCommandEventHandler(MyFrame::OnMyClick));


вы используете this от wxButton (btnTest), a смещение метода MyFrame::OnMyClick(), я не представляю как подобный код может хоть иногда давать правильный результат.

нужно писать так :
Код

MyFrame:: ...
{
// назначить свой метод обработчиком
 this->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
                                wxCommandEventHandler(MyFrame::OnMyClick));

// ну и соответственно если обработчик описан в кнопке
 btnTest ->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
                                wxCommandEventHandler(MyButton::OnMyClick));

}

ну а для определения инициатора события использовать wxID

Автор: Fabio 28.9.2008, 22:00
И всётаки я добился своего! Косяк найден! Спасибо, mes!
Хотел сделать инкремент Вашей репутации, но до 100 сообщений мне ещё далеко smile .

Дело оказалось в этой строчке:

/*btnTest->*/Connect(ID_WXBUTTON, wxEVT_COMMAND_BUTTON_CLICKED,
                                wxCommandEventHandler(MyFrame::OnMyClick));

Нужно было вызывать метод Connect по указателю this, а не btnTest и добавить идентификатор
источника события - ID_WXBUTTON.
В таком виде код из OnMyClick может обращаться к массиву полей ввода.

Автор: mes 29.9.2008, 00:47
Цитата(Fabio @  28.9.2008,  22:00 Найти цитируемый пост)
Нужно было вызывать метод Connect по указателю this, а не btnTest и добавить идентификатор
источника события - ID_WXBUTTON.


ага, нужно чтоб "this"  был того класса , чей метод используется как обработчик
вот выделил главные места в коде приведенного выше :

Цитата

(MyFrame*) this->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
                                wxCommandEventHandler(MyFrame::OnMyClick));

(MyButton*) btnTest ->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
                                wxCommandEventHandler(MyButton::OnMyClick));


Автор: nickless 29.9.2008, 22:56
Цитата(Fabio @  28.9.2008,  21:00 Найти цитируемый пост)
Хотел сделать инкремент Вашей репутации, но до 100 сообщений мне ещё далеко

Всегда можно кого-нибудь попросить smile 
Done

Автор: Fabio 1.10.2008, 19:35
Цитата

Всегда можно кого-нибудь попросить

Спасибо, nickless smile

Автор: Static 13.11.2008, 09:58
Похоже, что я расплачиваюсь за тесное знакомство с Borland C++ Builder smile
Могу ли я собрать в массив уже существующие контролы? В VCL все было просто, например так:
Код

TEdit *EditArray[3];
EditArray[0]=Edit1;
EditArray[1]=Edit2;
EditArray[2]=Edit3;

Подобный финт ушами smile с wxWid не прокатывает... Как все же этого добиться?

Автор: mes 13.11.2008, 10:15
Цитата(Static @  13.11.2008,  09:58 Найти цитируемый пост)
Подобный финт ушами smile с wxWid не прокатывает... Как все же этого добиться?

прокатывает. покаьите часть код который не получается, и сообщения компилятора об ошибке.

Автор: Static 13.11.2008, 10:25
мда.. действительно прокатывает, извините.
Ошибка была с последующим приведением к необходимым классам.
был массив wxObject'ов *objs[n], который в некоторых случаях я хотел обрабатывать как (wxTextCtrl*)objs[n]. В итоге оказалось, что валидна лишь такая формулировка:
Код

((wxTextCtrl*)(objs[n]))

А почему я решил, что ошибка с созданием массива - потому что сначала нарвался на сообщение о приватности текстконтролов, объявив массив не в том месте.
Еще раз извините.

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)