GUI behavior question

Started by
13 comments, last by Molle85 12 years, 1 month ago
I know this is a long post, but I hope that someone is willing to read it and give my some advice with my problem.

I have a quesion about how my GUI should respond in a certain situation.

Here is an example code on how you can currently use my GUI.

void init(tgui::Window& window)
{
tgui::Picture* picture = window.addPicture("pic1");
picture->load("filename.png");
picture->SetScale(3, 3);
}

int main()
{
tgui::Window window(...);
init(window);

window.getPicture("Pic1")->SetScale(2, 2); // <- this is the line I have a question about.

// Other things
}


When you create an object you create and add them to the window with the addObject functions (addPicture, addButton, addCheckbox, ...).
You will receive a pointer to the object so that you can load it, scale it, change it's position, ...
You probably don't store the pointer and let it go out of scope, which is no problem.

But when you need the pointer back (for e.g. changing the scale) then you will need to call a getObject function.
To this function you must pass the name of the object, which is the same as the one that you passed to addObject.
My question is, what do you think is best when you pass a wrong name (usually this can only occur when you made a typing mistake).

- Should the function return a NULL pointer? This could be a good solution, but if you would do this: window.getObject("name")->SetScale(2, 2) when "name" would be wrong then you will get a segmentation fault. But when you would get that segfault you will know that you made a typo and you will find the problem easily.

- The other option, the one that is currently used, isn't great either. On startup some empty objects will be created. When you pass a wrong name than the empty object is returned. You will not get a segfault, but the scaling won't have any effect. You will also get into problems when trying to call GetPosition(), because this will not return what you would expect. You might face some dificulties finding out why your object isn't scaled like you wanted.

- Another option would be to return a boolean and take a reference as parameter. Then you will have to check if the function returns true, and if it does then you are allowed to change the object. But just like the first solution you will no longer be able to do window.getObject("name")->SetScale(2, 2).


At first sight the third solutions look the best, but it will make the following code completely unusable:
if (window.getCheckbox("check")->isChecked())
window.getLoadingbar("load")->incrementValue();


If I would still want the code I would have to write:
tgui::Checkbox* checkbox;
if (window.getCheckbox("check", checkbox))
{
if (checkbox->isChecked())
{
tgui::LoadingBar* loadingBar;
if (window.getLoadingBar("load", loadingBar))
loadingBar->incrementValue();
}
}

I hope that it is clair that this isn't really what I want.

I think changing to the first solution is the best thing to do, despite the fact that you might get segfaults if you mistype the name.

Anyone has an opinion about it? Maybe a better solution?

Thanks in advance.
TGUI, a C++ GUI for SFML
texus.me
Advertisement
window.getObject("name")->SetScale(2, 2)

Just my two cents on this style...
The above should only be used if the function guarantees to return a valid object pointer no matter what. Otherwise, you should always check for validation first (object != nullptr). You are returning empty (dummy) objects, which is also an option, but a nullptr is IMHO much nicer.

Object *o = window.getObject("name");

if (o != nullptr)
o->SetScale(2, 2)
else
{
...
}
Ok, thanks for your reply.
If anyone else has an opinion then I would love to hear it.
TGUI, a C++ GUI for SFML
texus.me
An approach that comes to mind, but which I haven't explored beyond just dreaming it up :) is to return a smart pointer whose operator-> would throw an exception if the internal pointer was null.

This would allow people that were "100% sure" that the widget should be there to write code relatively naturally. If they happened to be wrong, they would be "punished" with an exception for forgoing a null-ness check. Alternatively, the checks might assert and/or return a "null object", though I personally prefer the exception in this case.


// Sketch:
template<typename T>
class checked_ptr
{
public:
checked_ptr() : p(0) { }
explicit checked_ptr(T *p) : p(p) { }

T *operator-> () const { return p ? p : throw null_ptr_exception(); }
T &operator* () const { return p ? *p : throw null_ptr_exception(); }
// ...

private:
T * p;
};
Thanks for your feedback.
My knowledge on exceptions and smart-pointers is a little bit small, but this might be interesting for later.

As far as I know, when doing this the user can no longer use a normal pointer: he has to use my smart pointer. I think that this makes it a little more complicated.
I'd like to keep things easy so that people with almost no programming experience can still use my gui.
TGUI, a C++ GUI for SFML
texus.me

As far as I know, when doing this the user can no longer use a normal pointer: he has to use my smart pointer.

And for good reason!


I think that this makes it a little more complicated.
[/quote]
How so? Sure they have to write "checked_ptr<Picture>" or "PicturePtr" (with a typedef), rather than "Picture *", but is that really that bad?

(Actually, I think the "too complicated" argument falls rather flat purely by virtue of the fact we're using C++ in the first place...)

Don't be intimidated by the name. Smart pointers, when employed correctly, are no harder to use than regular pointers. In fact the whole point of them is that they're often easier/safer than regular pointers.

That's not to say that the approach I suggested is necessarily the best one as a whole, but you really shouldn't be automatically put-off by the use of a smart pointer. I posted a near-complete implementation in about 10 lines of code. What about it scares you?

If you really, really only want to return a raw pointer then you only have one option: force the user to check that the pointer isn't null. Well, you could use what's sometimes called the "null object pattern", but that doesn't sit well with me, as it's harder for the user to spot when they're using the API incorrectly.
I don't know why, but I just don't like the look of

checked_ptr<Picture>, but this is indeed solved with a typedef.


[color=#282828][font=helvetica, arial, verdana, tahoma, sans-serif]The more I think about this solution the better I find it. In most situations you don't need to store the pointer anyway so it doesn't really matter how it looks.[/font]




[font=helvetica, arial, verdana, tahoma, sans-serif][color=#282828]I'll think about it for a while (I wasn't planning to change it during the first few weeks anyway).[/font]


[color=#282828][font=helvetica, arial, verdana, tahoma, sans-serif]When I get enough time for it I will read some more information about smart pointers and find out what exception I could trow.[/font]



[color=#282828][font=helvetica, arial, verdana, tahoma, sans-serif]Thanks again for your help.[/font]

TGUI, a C++ GUI for SFML
texus.me
Unless you are planning on actually using the functionality that calling getObject("unknown_name") returns NULL, like , say, to dynamically generate a dialog or something, then I don't see why you don't just make getObject throw an exception on an unknown name.
Unless you are planning on actually using the functionality that calling getObject("unknown_name") returns NULL[/quote]
The GUI can be used by anybody, I don't know what other people plan to do. Maybe they will generate a dialog.

I just don't like the try and catch thing.
Why would someone have to write the following while on the other hand I could just work with boolean or NULL values?
try
{
window.getObject("name");
}
catch (exception& e)
{
// The function failed
}


I would have to write in my documentation that the function trows exceptions, while otherwise a person can look e.g. at the bool return value and immediately understand how the function works without checking the documentation.

With a NULL pointer the person who uses my gui would have a choice: ignore that the function might return NULL and don't do the check, or check if the function failed or not.
I know that not checking the return is a very bad way, but in this particular situation (with this function), you are usually 100% sure that the function will succeed.

I know that this might sound like I am not listening to what you guys are saying and just do what I planned, but I just didn't look into it very closely. In a month or so I will investigate the options that you mentioned before I make any changes.
TGUI, a C++ GUI for SFML
texus.me

I just don't like the try and catch thing.
Why would someone have to write the following while on the other hand I could just work with boolean or NULL values?
try
{
window.getObject("name");
}
catch (exception& e)
{
// The function failed
}



You pretty much answered it yourself. If it's a "soft" error like this, don't throw. Limit exceptions to errors that actually are "exceptional" and not more or less expected behavior.

Though in those situations they are a LOT nicer than manually checking return values on every function call across several layers and then manually cascading the error back up the call stack. Especially since programmers can be notoriously lazy/sloppy when it comes to error checking/handling and immediately getting an unhandled exception with a nice text is better than seg faulting or getting weird behavior that only happens sometimes and at seemingly random times.
f@dzhttp://festini.device-zero.de

This topic is closed to new replies.

Advertisement