I have a function that creates a button dynamically
void createBtn(News obj,TForm *Form1){
TButton *spam = new TButton(Form1);
spam->Parent = newsCard;
spam->Position->X = 280;
spam->Position->Y = 256;
spam->Text = "Spam";
}
I need to assign an OnClick event to it, so I added the following line to the function above:
spam->OnClick = spamClick;
The code for spamClick is:
void __fastcall TForm1::spamClick(TObject *Sender, News obj)
{
allUsers[userIndex].spamNews(obj);
ShowMessage("Done!");
}
The problem is, I need to pass the obj in the line, but the function requires 2 arguments, which are the Sender and the obj.
spam->OnClick = spamClick(obj); // error here
But I do not know what to pass. I've tried Form1, spam, and newsCard. But nothing works.
What do I pass as a Sender? Or, is there a way to assign an OnClick event to the button inside createBtn()?
Edit:
class News has the following definition
class News
{
public:
News(string, string, string, Dates);
string title;
string description;
Dates date;
int rate;
string category;
vector<Comment> comments;
int spamCount;
static int newsCount;
int newsID;
int numOfRatedUsers;
};
and spamNews is a function in the user class that pushes the obj.newsID into a vector in the user then increases the spamCount.
void user::spamNews(News& obj) {
//check that the news in not already spammed
if(!findNews(spammedNews,obj)){
spammedNews.push_back(obj.newsID);
obj.spamCount++;
}
}
Your second approach doesn't work, because you are trying to call spamClick() first and then assign its return value to the OnClick event.
Your first approach is the correct way, however you can't add parameters to the OnClick event handler.
TButton has Tag... properties for holding user-defined data. However, since the News object is not being passed around by pointer, the Tag... properties are not very helpful in this case (unless the News object is held in an array/list whose index can then be stored in the Tag).
Otherwise, I would suggest deriving a new class from TButton to hold the News object, eg:
class TMyButton : public TButton
{
public:
News NewsObj;
__fastcall TMyButton(TComponent *Owner, const News &obj)
: TButton(Owner), NewsObj(obj) {}
};
void TForm1::createBtn(const News &obj)
{
TMyButton *spam = new TMyButton(this, obj);
spam->Parent = newsCard;
spam->Position->X = 280;
spam->Position->Y = 256;
spam->Text = _D("Spam");
spam->OnClick = &spamClick;
}
void __fastcall TForm1::spamClick(TObject *Sender)
{
MyButton *btn = static_cast<TMyButton*>(Sender);
allUsers[userIndex].spamNews(btn->NewsObj);
ShowMessage(_D("Done!"));
}
UPDATE: Since your News objects are being stored in a vector that you are looping through, then a simpler solution would be to pass the News object to createBtn() by reference and then store a pointer to that object in the TButton::Tag property, eg:
void TForm1::createBtn(News &obj)
{
TButton *spam = new TButton(this);
spam->Parent = newsCard;
spam->Position->X = 280;
spam->Position->Y = 256;
spam->Text = _D("Spam");
spam->Tag = reinterpret_cast<NativeInt>(&obj);
spam->OnClick = &spamClick;
}
void __fastcall TForm1::spamClick(TObject *Sender)
{
TButton *btn = static_cast<TButton*>(Sender);
News *obj = reinterpret_cast<News*>(btn->Tag);
allUsers[userIndex].spamNews(*obj);
ShowMessage(_D("Done!"));
}
Or, using the TMyButton descendant:
class TMyButton : public TButton
{
public:
News *NewsObj;
__fastcall TMyButton(TComponent *Owner)
: TButton(Owner) {}
};
void TForm1::createBtn(News &obj)
{
TMyButton *spam = new TMyButton(this);
spam->Parent = newsCard;
spam->Position->X = 280;
spam->Position->Y = 256;
spam->Text = _D("Spam");
spam->NewsObj = &obj;
spam->OnClick = &spamClick;
}
void __fastcall TForm1::spamClick(TObject *Sender)
{
MyButton *btn = static_cast<TMyButton*>(Sender);
allUsers[userIndex].spamNews(*(btn->NewsObj));
ShowMessage(_D("Done!"));
}
Related
I'm making a simple hangman game in c++ builder community edition and my game consists of buttons that represents letters and if the letter doesn't appear inside of the word you lose a life, and so on, and so on...
But I'd though that is a little repetitive my code if I make a TButton for every letter in the abcedary. So I decide to make an array of TButton my surprise was when I code everything and any of them appear in my form :c.
If someone can help me a little I'll be so happy haha.
Tgame class...
class Tgame : public TForm
{
__published: // IDE-managed Components
TText *word;
private: // User declarations
TButton* chars[23];
public: // User declarations
__fastcall Tgame(TComponent* Owner);
void __fastcall FormClose(TObject *Sender, TCloseAction &Action);
};
And the constructor implementation...
for(int i = 0; i < 23; ++i) {
this->chars[i] = new TButton(this);
this->chars[i]->Height = 33;
this->chars[i]->Width = 49;
this->chars[i]->Position->X = startX;
this->chars[i]->Position->Y = startY;
startX += difX;
startY += difY;
this->chars[i]->Opacity = 1;
this->chars[i]->Visible = true;
this->chars[i]->Text = "A";
}
You construct the TButton setting its owner (the component responsible for deleting it).
this->chars[i] = new TButton(this);
But you don't set its Parent, which is the component in which the TButton will appear visually. So, add this line:
this->chars[i]->Parent = this;
Note: The default values for Opacity and Visible are 1 and true so you don't need to set those explicitly.
I have lots of custom Qt widgets that have their own layouts and contain lists of even more widgets.
I ended up having a ManagerWidget that controls everything (including listening to all events) and propagating them to the widget that needs to update its content. It seems there are too many methods that just pass the information along.
Is there a better way of doing this? Connecting the classes through signal/slots doesn't really work in this case because there's a map of them and I wouldn't know which one was fired.
Would the Mediator pattern fit in this case?
class ManagerWidget : public QWidget {
public:
map<ID, MainWidgetContainer*> mainWidgetMap;
RecordsContainer *recentRecords; // This widget can contain any number of widgets, each showing a record
// events are caught here and propagated to the widgets
void AddNewEntry(ID id) {
MainWidgetContainer *w = new MainWidgetContainer(id);
mainWidgetMap.insert(id, w);
}
void UpdatePictureWidget(ID id) {
mainWidgetMap.value(id)->UpdatePictureWidget();
}
void AddXItem(ID id, Type1 k) {
if(id == 0)
recentRecords->AddXItem(k);
else
mainWidgetMap.value(id)->AddXItem(k);
}
void UpdateEntry(ID id, string newName) { }
};
class MainWidgetContainer : public QWidget {
public:
NameWidget *name;
PictureWidget *pic;
RecordsContainer *records; // This widget can contain any number of widgets, each showing a record
MainWidgetContainer(ID id) {
name = new NameWidget(id);
records = new RecordsContainer();
pic = new PictureWidget();
// setup layout and add items to records as they come in later
}
AddXItem(Type1 k) { records->AddXItem(k); }
UpdatePictureWidget() { pic->Update(); }
};
class RecordsContainer : public QWidget {
public:
map<Type1, Item*> itemMap;
AddXItem(Type1 k) {
Item *item = new Item(k);
itemMap.insert(k, item);
mainLayout->addWidget(item);
}
AddYItem(Type2 k);
// etc
};
In my menu, I am setting data to the menu actions. How can I extract that data in my slot? Or even better, instead of connecting a slot, can I also connect a member function that is able to extract the action data (like in the 1st connect)? The action data is meant to identify each action. As a sidenode, I am not sure if I can use several menu action entries on only one openNote-action.
void Traymenu::createMainContextMenu() {
QAction *actionNewNote = m_mainContextMenu.addAction("Neue Notiz");
actionNewNote->setIcon(QIcon("C:\\new.ico"));
actionNewNote->setIconVisibleInMenu(true);
QObject::connect(actionNewNote,&QAction::triggered,this,&Traymenu::newNote);
QString menuEntryName;
QAction *openNote;
QVariant noteID;
for (int i = 0; i<m_noteList.count(); i++) {
std::string noteTitle = m_noteList[i].data()->getTitle();
menuEntryName = QString::fromStdString(noteTitle);
openNote = m_mainContextMenu.addAction(menuEntryName);
connect(openNote,SIGNAL(triggered()),this,SLOT(s_showNote()));
noteID.setValue(m_noteList[i].data()->getID());
openNote->setData(noteID);
}
m_mainIcon.setContextMenu(&m_mainContextMenu);
}
And the slot:
void Traymenu::s_showNote() {
QObject* obj = sender();
//int noteID = data.toInt();
//Search all notes in noteList for that ID and show it
}
Using QObject::sender()
You can use QObject::sender() to get the signal's sender, followed by qobject_cast to cast the sender pointer to the right type.
void Traymenu::s_showNote()
{
QAction* act = qobject_cast<QAction *>(sender());
if (act != 0)
{
QVariant data = act->data();
int noteID = data.toInt();
showNote(noteID); // isolate showNote logic from "get my ID" stuff
}
}
void Traymenu::showNote(int noteID)
{
// Do the real work here, now that you have the ID ...
}
As the Qt documentation warns, "This function violates the object-oriented principle of modularity." It's still a fairly safe and standard practice, though — just one with some shortcomings. In particular, note that you're committing to having a s_showNote method that only works when it's accessed as a slot (otherwise sender is 0).
Using QSignalMapper
Alternatively, you can use the QSignalMapper class to return a pointer to teh item or to associate a unique identifier (int or QString) with each item.
Something like this:
void Traymenu::createMainContextMenu()
{
signalMapper = new QSignalMapper(this); // (or initialize elsewhere)
// ... (create your newNote here same as before) ...
QString menuEntryName;
QAction *openNote;
int noteID;
for (int i = 0; i<m_noteList.count(); i++) {
std::string noteTitle = m_noteList[i].data()->getTitle();
menuEntryName = QString::fromStdString(noteTitle);
openNote = m_mainContextMenu.addAction(menuEntryName);
noteID = m_noteList[i].data()->getID();
openNote->setData(QVariant(noteID)); // (if you still need data in the QActions)
signalMapper->setMapping(openNote, noteID);
}
connect(signalMapper, SIGNAL(mapped(int)),
this, SLOT(showNote(int)));
m_mainIcon.setContextMenu(&m_mainContextMenu);
}
void Traymenu::showNote(int noteID) {
// Now you have the ID ...
}
This pattern has the benefit of isolating all the ugly "Wait, how do I get my identifier?" stuff in one spot, instead of having both the initialization code and the slot function having code for associating actions and IDs.
I would write it like:
void Traymenu::s_showNote() {
QObject* obj = sender();
QAction *action = qobject_cast<QAction *>(obj);
int id = action->data().toInt();
for (int i = 0; i < m_noteList.count(); i++) {
if (m_noteList[i].data()->getID() == id) {
[..]
}
}
}
I am using Google place API in my application for searching location. When user input text in edit field then the API called and resulted output will shown in a list.
I implemented it successfully but the problem is that each time edit field text changes, the list is not repainting and output is added to the end of the list. I want every time the text changes in the edit text field, the list must remove its previous content that are invalid.
This can be seen in pictures:
For Implementing this, I have written this code:
public final class MyScreen extends MainScreen {
/**
* Creates a new MyScreen object
*/
private Vector _listElements;
ListField list;
JSONObject[] jsonobject;
EditField editfield;
String url = "https://maps.googleapis.com/maps/api/place/autocomplete/json?input=";
String[] locationName;
VerticalFieldManager verticalFieldManager = new VerticalFieldManager();
public MyScreen() {
ButtonField search = new ButtonField("Search");
_listElements = new Vector();
list = new ListField();
ListCallback _callback = new ListCallback(this);
// Set the displayed title of the screen
setTitle("Search Edit Field");
editfield = new EditField();
editfield.setChangeListener(new FieldChangeListener() {
public void fieldChanged(Field field, int context) {
UiApplication.getUiApplication().invokeLater(new Runnable() {
public void run() {
list.invalidate();
createField();
}
});
}
});
list.setCallback(_callback);
add(editfield);
add(new SeparatorField());
verticalFieldManager.add(list);
add(verticalFieldManager);
}
protected void createField() {
ShowList();
reloadList();
}
private void reloadList() {
list.setSize(_listElements.size());
}
class ListCallback implements ListFieldCallback {
MyScreen listDemoScreen;
public ListCallback(MyScreen listDemoScreen) {
this.listDemoScreen = listDemoScreen;
}
public void drawListRow(ListField list, Graphics g, int index, int y,
int w) {
String text = (String) _listElements.elementAt(index);
list.setRowHeight(getFont().getHeight());
g.drawText(text, 0, y, 0, -1);
}
public Object get(ListField list, int index) {
return _listElements.elementAt(index);
}
public int indexOfList(ListField list, String prefix, int string) {
return _listElements.indexOf(prefix, string);
}
public int getPreferredWidth(ListField list) {
return Display.getWidth();
}
}
protected void ShowList() {
HttpConnection httpConn;
InputStream in;
ConnectionFactory connFact = new ConnectionFactory();
ConnectionDescriptor connDesc;
String response;
String fieldText = editfield.getText();
connDesc = connFact.getConnection(url + fieldText
+ "%#&sensor=true&key=xxxxxxxxxxxxx"
+ ConnectionType.getConnectionType());
if (connDesc != null) {
httpConn = (HttpConnection) connDesc.getConnection();
try {
int responseCode = httpConn.getResponseCode();
if (responseCode == HttpConnection.HTTP_OK) {
in = httpConn.openInputStream();
StringBuffer buf = new StringBuffer();
int read = -1;
while ((read = in.read()) != -1)
buf.append((char) read);
response = buf.toString();
try {
JSONObject object = new JSONObject(response);
JSONArray ar = object.getJSONArray("predictions");
jsonobject = new JSONObject[ar.length()];
locationName = new String[ar.length()];
list.invalidate();
for (int i = 0; i < ar.length(); i++) {
jsonobject[i] = ar.getJSONObject(i);
_listElements.addElement(jsonobject[i]
.getString("description"));
}
} catch (JSONException e) {
e.printStackTrace();
}
}
} catch (IOException e) {
e.printStackTrace();
}
} else {
Dialog.alert("Connection not succeded");
}
}
protected boolean onSavePrompt() {
return true;
}
}
Update and Solution:
only modify this and this rest is working fine. As Peter Suggests, we can also put a Thread.sleep(time); in order to get the UI not blocked:
UiApplication.getUiApplication().invokeLater(new Runnable() {
public void run() {
_listElements.removeAllElements();
createField();
}
});
I think your problem is simply that you do not clear the _listElements Vector when you request more data. So _listElements just gets bigger.
However there is a bigger problem here and that is that your code appears to be running networking operations on the Event Thread. What your should do in your changeListener. is start a Thread that requests the data, then repopulate the ListField when this tread gets data.
As a result of this change, the UI will not be blocked, and the List updates will become asynchronous, so your user could in fact enter another character into the EditField before the first Thread response comes back. To prevent this looking silly, you could delay the Thread processing for a fraction of second to see if another character is entered, and/or you could make sure that the EditField content was still the same as the requested characters before you repopulate it.
I personally prefer this asynchronous approach, but if it bothers you, you could put a 'please wait - loading' type screen to block the user until the response comes back.
Update
Remember that if you start a background Thread, you need to get back onto the Event Thread to do Ui Processing. Typically this is done simply by including your UI code within the run method of a Runnable that is invoked later, for example:
UiApplication.getUiApplication().invokeLater(new Runnable() {
public void run() {
// Ui Code in here
}
});
You should only put Ui Updating code in the runnable. Networking processing, or any other blocking action, should NOT be included.
I'm trying to write an OO menu system for a game, loosely based on the idea of a Model,View,Controller. In my app so far I've named the views "renderers" and the models are without a suffix. I created a generic menu class which stores the items of a menu, which are menu_item objects, and there is also a menu renderer class which creates renderers for each item and renders them. The problem is I'm not sure where to store the data and logic to do with where on the screen each item should be positioned, and how to check if it is being hovered over, etc. My original idea was to store and set a selected property on each menu item, which could be rendered differently by different views, but even then how to I deal with positioning the graphical elements that make up the button?
Code excerpts so far follow: (more code at https://gist.github.com/3422226)
/**
* Abstract menu model
*
* Menus have many items and have properties such as a title
*/
class menu {
protected:
std::string _title;
std::vector<menu_item*> _items;
public:
std::string get_title();
void set_title(std::string);
std::vector<menu_item*> get_items();
};
class menu_controller: public controller {
private:
menu* _menu;
public:
menu_controller(menu*);
virtual void update();
};
class menu_item {
protected:
std::string _title;
public:
menu_item(std::string title);
virtual ~menu_item();
std::string get_title();
};
class menu_renderer: public renderer {
private:
menu* _menu;
bitmap _background_bitmap;
static font _title_font;
std::map<menu_item*, menu_item_renderer*> _item_renderers;
public:
menu_renderer(menu*);
virtual void render();
};
font menu_renderer::_title_font = NULL;
menu_renderer::menu_renderer(menu* menu) {
_menu = menu;
_background_bitmap = ::load_bitmap("blackjack_menu_bg.jpg");
if (!_title_font)
_title_font = ::load_font("maven_pro_regular.ttf",48);
}
void menu_renderer::render() {
::draw_bitmap(_background_bitmap, 0, 0);
/* Draw the menu title */
const char* title = _menu->get_title().c_str();
int title_width = ::text_width(_title_font, title);
::draw_text(title, color_white, _title_font, screen_width() - title_width - 20, 20);
/* Render each menu item */
std::vector<menu_item*> items = _menu->get_items();
for (std::vector<menu_item*>::iterator it = items.begin(); it != items.end(); ++it) {
menu_item* item = *it;
if (!_item_renderers.count(item))
_item_renderers[item] = new menu_item_renderer(item, it - items.begin());
_item_renderers[item]->render();
}
}
class menu_item_renderer: public renderer {
private:
unsigned _order;
menu_item* _item;
static font _title_font;
public:
menu_item_renderer(menu_item*, unsigned);
virtual ~menu_item_renderer();
virtual void render();
};
font menu_item_renderer::_title_font = NULL;
menu_item_renderer::menu_item_renderer(menu_item* item, unsigned order) {
_item = item;
_order = order;
if (!_title_font)
_title_font = ::load_font("maven_pro_regular.ttf",24);
}
menu_item_renderer::~menu_item_renderer() {
// TODO Auto-generated destructor stub
}
void menu_item_renderer::render() {
const char* title = _item->get_title().c_str();
int title_width = ::text_width(_title_font, title);
unsigned y = 44 * _order + 20;
::fill_rectangle(color_red, 20, y, title_width + 40, 34);
::draw_text(title, color_white, _title_font, 30, y + 5);
}
Your Model class menu needs a add_view(menuview *v) and update() method.
Then you can delegate the update of your widget to the derived View (menu_renderer or a cli_menu_renderer).
The Controller needs to know the Model (as member), when the Controller runs (or executes a Command) and has to update the Model with a Setter (like m_menu_model->set_selected(item, state)) and the Model calls update() on Setters.
Your Controller menu_controller has a update method, there you could also ask for Input, like if (menuview->toggle_select()) m_menu_model->toggle_selected(); (which all menuviews have to implement) and invoke the setter, but thats a inflexible coupling of View and Controller (you could check MVC with the Command Pattern for a more advanced combination).
For the Position you can set member vars like int m_x, m_y, m_w, m_h.
But these members are specific to a View with GUI, so only the derived View needs them.
Then you could use these values to compare against mouse Positions and use a MouseOver Detection Method like this:
// View menu_item
bool menu_item::over()
{
if (::mouse_x > m_x
&& ::mouse_x < m_x + m_w
&& ::mouse_y > m_y
&& ::mouse_y < m_y + m_h) {
return true;
}
return false;
}
// update on gui menu item
bool menu_item::update()
{
if (over()) {
m_over = true;
}
else {
m_over = false;
}
// onclick for the idea
if ((::mouse_b & 1) && m_over) {
// here you could invoke a callback or fire event
m_selected = 1;
} else {
m_selected = 0;
}
return m_selected;
}
// update the command line interface menu item
bool cli_menu_item::update()
{
if ((::enterKeyPressed & 1) && m_selected) {
// here you could invoke a callback or fire event
m_selected = 1;
} else {
m_selected = 0;
}
return m_selected;
}
void menu_item_renderer::render() {
// update widgets
_item->update();
// ...
}
// Model
void menu::add_view(menuview *v) {
m_view=v;
}
void menu::update() {
if (m_view) m_view->update();
}
bool menu::set_selected(int item, int state) {
m_item[index]=state;
update();
}