if I have a CustomContainer with 2 Text Areas, is it possible to update the text for those text areas at run-time? please note that the custom container is in a scroll list.
Yes it is possible. Maybe this late answer can help someone.
I have a screen with a list of custom widgets of actual failures.
The custom container has 4 text areas: date, time, error component, error text.
Here I set the texts in 3 ways, the text for date and time is generated from a numeric time value, the text for the caption is read from the resource, and the text for the error details text is read from the resource but values are filled in afterwards.
After the content on the screen is invalidated, either by scrolling or by calling invalidate, a callback function xxxUpdateItem is called automatically from touchgfx runtime. You have to override and implement it in your view class of the screen.
This function is called with a reference to your custom widget and the actual index of the current item, like this:
void MessageScreenView::scrollList1UpdateItem(CustomContainerFailureOrInfo& item, int16_t itemIndex)
From this you call a function of the custom widget which sets the new texts, e.g.:
void CustomContainerFailureOrInfo::setDetails(uint16_t itemIdx, uint32_t dateTime, uint16_t captionTextId, uint16_t detailTextId, const char16_t * templateF1, float f1, const char16_t * templateF2, float f2)
{
setDateTime(dateTime);
setCaption(captionTextId);
setDetailText(detailTextId, templateF1, f1, templateF2, f2);
}
Text for Date and time is generated from a time_t value.
The caption is read from the resource with the text widget's setTypedText function, e.g.:
void CustomContainerFailureOrInfo::setCaption(TypedTextId t)
{
caption.setTypedText(TypedText(t));
caption.setWideTextAction(WIDE_TEXT_WORDWRAP);
caption.invalidate();
}
I had the problem, that some error messages should show error related values, while others should only show plein text. I solved it by using value wildcards and passing a format string and a value:
void CustomContainerFailureOrInfo::setDetailText(TypedTextId t, const char16_t * templateF1, float f1, const char16_t * templateF2, float f2)
{
text.setTypedText(TypedText(t));
Unicode::snprintf(textBuffer1, TEXTBUFFER1_SIZE, "");
if (templateF1)
{
if (awiStrUtil::isPrintfFloatContained16(templateF1))
{
Unicode::snprintfFloat(textBuffer1, TEXTBUFFER1_SIZE, reinterpret_cast<const Unicode::UnicodeChar *> (templateF1), f1);
}
else
{
// attention: (const char16_t*)
Unicode::snprintf(textBuffer1, TEXTBUFFER1_SIZE, reinterpret_cast<const Unicode::UnicodeChar *> (templateF1));
}
}
// similar code removed: if (templateF2) ...
text.setWideTextAction(WIDE_TEXT_WORDWRAP);
text.invalidate();
}
Related
I'm writing an application that requires me to write information to a TFT display (kinda like a gameboy display).
I'm outputting the information to the display line by line. The way I'm doing it now requires me to have a function for each different screen.
Like:
void displayWelcomeMessage();
void displayInsertCoinMessage();
void displayGoodByeMessage();
Each function follows this logic:
void displaWelcomeMessage()
{
writeline(0, "Hi David");
writeline(1, "Welcome");
writeline(2, "Back!");
}
Problem: I hate to have a different function for each screen. It's not scalable at all, imagine if I had 500 different screens.
How do I abstract the process of writing to the display? So that I end up with a single generic function responsible for writing to the display.
Thank you
Update
Following "Useless's" and Michael.P's advice, what I will probably end up doing is storing the format of each message in a file:
DisplayMessages.cfg
WELCOME_MESSAGE_1 = "Hi %name"
WELCOME_MESSAGE_2 = "Welcome"
WELCOME_MESSAGE_3 = "back!"
And in the code I will do something like:
using Message = std::vector<QString>;
void login(){
//Do Stuff...
QString line
Message welcomeMessage;
line=getMessageStructureFromCfg("WELCOME_MESSAGE_1").arg(userName); // loads "Hi %name" and replaces %name with the content of userName
welcomeMessage.pushBack(line); // pushes the first line to welcomeMessage
line=getMessageStructureFromCfg("WELCOME_MESSAGE_2"); // loads "Welcome"
welcomeMessage.pushBack(line); // pushes the second line to welcomeMessage
line=getMessageStructureFromCfg("WELCOME_MESSAGE_3"); // loads "back!"
welcomeMessage.pushBack(line); // pushes the third line to welcomeMessage
displayMessage(welcomeMessage);
}
void displayMessage(Message const &msg) {
int i = 0;
for (auto &line : msg) {
writeline(i, line);
i++;
}
}
Thank you all for your help!
PS: Further improvements can be made if the file containing the messages structure used JSON instead of plain text. This way you could just iterate the child members(the lines) of each message and process accordingly
How do you abstract the information to be displayed in a screen?
The same way you abstract any information - you move the data out of the code and into a data structure.
Your displayXMessage() functions perform a fixed sequence of calls on a fixed sequence of hardcoded string literals. So, split the algorithm from the data the usual way, by passing the data as an argument:
using Message = std::vector<std::pair<int, std::string>>;
void displayMessage(Message const &msg) {
for (auto &line : msg) {
writeline(line.first, line.second);
}
}
and call it with the appropriate data:
Message welcomeMsg { {0, "Hi David"},
{1, "Welcome"},
{2, "Back!"}
};
displayMessage(welcomeMsg);
I think you will find a solution to all those kind of problems by learning design patterns. In this case in particular. Strucural patterns seem to be what you are looking for; you will then have to pick the pattern which fits the most what you are trying to do.
I'm trying to add a dynamic number of sliders to a GUI window, and update an std::vector<float> with the values according to how each slider changes. The GUI library uses callbacks and I can successfully do it for one slider (or when copy&pasting the slider code 20 times but that's not a solution), however I'm having problems factoring the slider creation into a function: There are issues with variable lifetime in how to tell each slider which element in the vector it should update.
I'm using nanogui (or libigl specifically, which uses nanogui) but I think the problem is actually quite generic and should apply to other GUI frameworks or situations as well.
Here's a short version of the code of how to add one slider and it updates my_values[0] (note I had to put "0" manually):
int main(int argc, char *argv[])
{
igl::viewer::Viewer viewer;
std::vector<float> my_values(50);
viewer.callback_init = [&](igl::viewer::Viewer& viewer)
{
nanogui::Slider* slider = new nanogui::Slider(viewer.ngui->window());
slider->setCallback([&](float value) {
my_values[0] = value; // this slider sets the value at element '0'
});
viewer.ngui->addWidget("0", slider);
viewer.screen->performLayout();
return false;
};
viewer.launch();
}
Now as mentioned before, in viewer.callback_init, I'd now like to add 50 of these sliders but obviously I don't want to copy&paste the slider and its callback code 50 times. But somehow I need to pass my_values[i], i.e. which element in the vector each slider is going to update.
I tried something like this which doesn't work, because at the time that the function/callback is actually called, the variable value_id is not defined:
int main(int argc, char *argv[])
{
igl::viewer::Viewer viewer;
std::vector<float> my_values(50);
auto add_slider = [&](igl::viewer::Viewer& viewer, std::vector<float>& my_values, int value_id, std::string value_name)
{
nanogui::Slider* slider = new nanogui::Slider(viewer.ngui->window());
slider->setCallback([&](float value) {
my_values[value_id] = value; // offending line, 'value_id' is not initialized
// also I'm using 'value_name' to give the slider a name - code omitted
});
return slider;
};
viewer.callback_init = [&](igl::viewer::Viewer& viewer) {
// Now add all 50 sliders (in a for-loop in real code obviously):
viewer.ngui->addWidget("0", add_slider(viewer, 0, "0"));
viewer.ngui->addWidget("1", add_slider(viewer, 1, "1"));
// etc.
viewer.screen->performLayout();
return false;
};
viewer.launch();
}
I tried various things, making value_id a (const) reference, even an std::shared_ptr<int> (I know, horrible), but still value_id is always not initialized on the line my_values[value_id] = value;.
How can I accomplish this? My guess is it must be a well-known pattern/solution in GUI/callback programming, I just haven't figured it out yet.
Instead of storing slider values in a std::vector<float>, use map<string, float> or map<Slider*, float>. Therefore, whenever your callback is called, you'll know which variable to update(by the address or name of the slider).
map<Slider*, float> my_values;
slider->setCallback([&](float value) {
my_values[slider] = value;
});
I am working with CDialogs and mfc. My dialog loads, and there are two input boxes and a button. When i click the button I want to have the values in the input boxes change. My current attempt is this
void
CInstanceNumberDlg::updateLeftRange(int i) {
GetDlgItem(IDC_2NDDERIV_WT)->SetWindowText((LPCTSTR)i);
UpdateData(false);
UpdateWindow();
}
I have also tried with other combinations of including UpdateData() and UpdateWindow(). This method is being reached with a valid integer, and that ID should be valid.
How can i get the value in the input box to be modified?
Thank you
Your problem is this line:
GetDlgItem(IDC_2NDDERIV_WT)->SetWindowText((LPCTSTR)i);
i is not a pointer to a string and so you should not be casting it to an LPCTSTR. What you need to do convert the value of i to a string and then pass a pointer to the resulting string to SetWindowText(). And I don't believe you need UpdateData() or UpdateWindow(). For example:
void CInstanceNumberDlg::updateLeftRange(int i)
{
TCHAR tszValue[32];
StringCchPrintf(tszValue, _countof(tszValue), TEXT("%d"), i);
GetDlgItem(IDC_2NDDERIV_WT)->SetWindowText(tszValue);
}
I have a QLineEdit that accepts a string that will be evaluated at a javascript expression like "[0,3]" and connected to fire changes using editingFinished(). I added a validator so the user couldn't leave the input with a bad expression, but apparently I'm misunderstanding how the validator works, because if I return QValidator::Invalid, when the expression wouldn't be valid, user's can't type any mistakes (character won't disappear on backspace). For example, temporarily changing "[0,3]" to "[0,]" to fill in another number.
I've tried changing the validator to return to QValidator::Intermediate on bad expressions thinking that would be a happy medium letter users alter text, but setting bad text back to its previous value on unfocused or return, but that seems to let user's put in anything that want. For example, they can type in "[0," and click on something else and the input still has "[0," in it as opposed to jumping back to the way it was. Am I misunderstanding how the intermediate type works?
QValidator::Invalid 0 The string is clearly invalid.
QValidator::Intermediate 1 The string is a plausible intermediate value.
QValidator::Acceptable 2 The string is acceptable as a final result;
i.e. it is valid.
Here is my current validator, which I just put on a QLineEdit:
class PointFValidator : public QValidator
{
QValidator::State validate(QString &input, int &position) const;
void fixup(QString &input) const;
};
QValidator::State PointFValidator::validate(QString &input, int &position) const
{
try {
evalPointF(input);
} catch (std::exception &e) {
return QValidator::Invalid;
}
return QValidator::Acceptable;
}
void PointFValidator::fixup(QString &input) const
{
}
And this is what actually tests the string to see if it's formatted correctly
QPointF evalPointF(QString s)
{
//initGuile();
QScriptEngine engine;
QString program = "function frame() { return 9; }\n\n" + s;
QScriptValue value = engine.evaluate(program);
QStringList pair = value.toString().split(",");
if (pair.length() < 2)
throw std::runtime_error("invalid pointf string");
return QPointF(pair[0].toFloat(), pair[1].toFloat());
}
Is the QValidator not what I need? Is it only for typing prevention? Do I need to listen to the change event, validate it myself, and set it back if it's not valid?
So trying to compile your code didn't work for me. The constness of the virtual functions in QValidator make it pretty much impossible to get your example code to compile.
So I would (like you mentioned at the end of your question) go and set up a signal to respond to the content changes of your QLineEdit, evaluate it, and then put the output somewhere helpful.
But based on the kinds of things you put in your evalPointF function, it looks like you are just writing an IDE for javascript.
Why not use the pattern that most IDE's already have of putting issues in another window, and using font's and formatting to modify the text, instead of actually changing the text?
Hope that helps.
I have a class Message that has a std::string as a data member, defined like this:
class Message
{
// Member Variables
private:
std::string text;
(...)
// Member Functions
public:
Message(const std::string& t)
: text(t) {}
std::string getText() const {return text;}
(...)
};
This class is used in a vector in another class, like this:
class Console
{
// Member Variables
private:
std::vector<Message> messageLog;
(...)
// Member Functions
public:
Console()
{
messageLog.push_back(Message("Hello World!"));
}
void draw() const;
};
In draw(), there's an iterator that calls getText(). When it does, the program segfaults. I've determined that text is valid inside the Message constructor. However, I can't tell if it's valid from inside Console. I'm assuming it is, but if I try to inspect indices of Console's messageLog, gdb tells me this:
(gdb) p messageLog[0]
One of the arguments you tried to pass to operator[] could not be converted to what
the function wants.
Anyone know what's going on?
EDIT: here's draw(). TCODConsole is part of a curses library I'm using, and so this function prints each message in Console to a part of the curses screen. TL and BR are Point member objects (two ints) that tell where on the screen to draw Console. I left out parts of Message and Console in the original question to hopefully make things clearer, but if you need me to post the entire classes then I can. They aren't too long.
void Console::draw() const
{
int x = TL.getX(), y = TL.getY();
int width = BR.getX() - TL.getX();
int height = BR.getY() - TL.getY();
// draw the Console frame
TCODConsole::root->printFrame(x, y, width, height, true);
// print the Console's messages
vector<Message>::const_iterator it;
for(it=messageLog.begin(); it<messageLog.begin()+height-1; ++it)
{
string message = "%c" + it->getText();
TCODConsole::setColorControl(TCOD_COLCTRL_1,
it->getForeColor(),
it->getBackColor());
y += TCODConsole::root->printRectEx(x, y, width, height,
TCOD_BKGND_NONE,
TCOD_LEFT,
message.c_str(),
TCOD_COLCTRL_1);
}
}
My guess is that by the point you use it->getText(), the iterator is NULL. Add a check it != messageLog.end() when you walk the array, and before calling it->getText().
Is it definitely std::vector messageLog and not std::vector<Message> messageLog? That seems a bit odd.
What does the height have to do with the vector's index? You have:
messageLog.begin()+height-1;
Why are you adding the screen coordinate to the iterator? That seems to be your problem and you're most likely overindexing and that's why you're getting a SIGSEGV.
What you probably want is to simply iterate over all the messages in the vector and display them at a particular location on the screen. I see what you're trying to do, but if you're trying to calculate the screen boundary using the iterator you're definitely going about it the wrong way. Try running a counter or get messageLog.size() and then recalculate the height with each iteration. As for the loop just do:
for(it=messageLog.begin(); it!=messageLog.end(); ++it)
It's probably because the scope of the Message object created in the Console method is just the Console method. So, if your program is trying to access this object in another method, like draw, you will get this segmentation fault, since this object is deleted after the execution.
Try this (just insert a new keyword):
Console()
{
messageLog.push_back(new Message("Hello World!"));
}
In this case, the object is not deleted after Console's end.
Just remember to delete the objects created when your program doesn't need them anymore.