Re: GUI Library Proposal for a Proposal

Message: 11 Date: Fri, 12 Nov 2004 22:18:53 +0200 From: John Torjo <john.lists@torjo.com> Subject: Re: [boost] Re: GUI Library Proposal for a Proposal To: boost@lists.boost.org Message-ID: <41951AAD.20208@torjo.com> Content-Type: text/plain; charset=us-ascii; format=flowed
You have made a good point. I still feel uncomfortable about parsing header files to determine type information. If header files were used, since run-time reflection does not exist, one would have to inject the correct code into source files based on design-time decisions. But it does seem as
I think reflection in general is very different from reflection for a GUI.
When you have a GUI control, you want to apply reflection for a few of its properties. You won't have #ifdefs (or at least I would consider it bad design). Case in point: its interface should be extremely simple and easy to parse.
You shouldn't need complex information like reflection for virtual functions, overloading etc.
Even reflected property types should be of trivial types: 1. build in types 2. std::string 3. types that can simply be read from /written to strings.
For case 3., you might also have a validation function which will be used in design-mode to validate user input.
At least that's what I want.
I *think* what you envisage is based around collecting data from the user via dialogs. So in your view of things, there would be a C++ object that holds the collected data, a set of validation routines, and a reflection map that binds these to the UI. But you don't do anything to the program state until the user clicks "OK" in your dialog, at which point you suck the data out of your simple C++ object and act on it. In contrast, other people (myself included) are considering binding properties and member functions to the UI for similar purposes to why you would expose them to script: to allow the user to drive them. So, for a simple example, let's suppose you have some object that corresponds to a file: class file_backed_t { public: std::string get_filename(); /// This actually renames the file on disk. void set_filename( std::string const& ); }; Now, when you bind the filename property to the UI, you'll automatically do the rename as soon as the user changes the text in the edit box. (Probably that isn't the desired behaviour in this case, but it serves as a demonstration.) This usage pattern corresponds better to modeless dialogs where you don't have an "OK" button. Of course if you are using a generalised reflection architecture like I mentioned yesterday, then you can do both approaches:
One of the things that I noticed about serialisation code is that there's generally a symmetry between the reading and writing routines. So rather than write "this is how I load an X, and this is how I save one" I'd want to write "these are the things in X that need to be saved" and let some templates figure out what the corresponding routines are. And then it was this desire to serialise things automatically by specifying properties that led me on to think about a common architecture for language binding and serialisation. Maybe in the end you'd get declarations like:
/// Properties for serialisation boost::properties< my_t, "my_t", boost::serialisation > .def( "my_int", &my_t::my_int, &my_t::set_my_int ) // After serialisation format version 4,a // my_float no longer needed to be serialised .def( "my_float", &my_t::my_float, &my_t::set_my_float, boost::serialisation::in_format_version_range<>( 0, 4 ) ) ;
/// Properties for scripting boost::properties< my_t, "my_t", boost::langbinding > // Use serialisation properties to implement pickling. .def_pickle( default_pickle_suite< my_t >() ) .def( "my_int", &my_t::my_int, &my_t::set_my_int, boost::langbinding::return_internal_reference<>() ) // We don't want to bind my_float, for whatever reason. ;
boost::properties< file_backed_t, "file_backed_t", boost::gui > // This is a heavyweight property for a modeless dialog .def( "filename", &file_backed_t::get_filename, &file_backed_t::set_filename ) ; // dialog_data_t is just a struct to store data for a modal dialog till // the user clicks "OK", which is when we process it. struct dialog_data_t { std::string filename; // Says whether to back up the specified file before // processing it. bool make_backup; }; // Helper function for dialog_data_t reflection. bool is_good_filename( std::string const& ); boost::properties< dialog_data_t, "dialog_data_t", boost::gui > .def( "filename", &dialog_data_t::filename, boost::gui::validator( &is_good_filename ) ) // No validation required for this simple bool. .def( "make_backup", &dialog_data_t::make_backup ) ; George

I *think* what you envisage is based around collecting data from the user via dialogs. So in your view of things, there would be a C++ object that holds the collected data, a set of validation routines, and a reflection map that binds these to the UI. But you don't do anything to the program state until the user clicks "OK" in your dialog, at which point you suck the data out of your simple C++ object and act on it.
In contrast, other people (myself included) are considering binding properties and member functions to the UI for similar purposes to why you would expose them to script: to allow the user to drive them. So, for a simple example, let's suppose you have some object that corresponds to a file:
class file_backed_t { public: std::string get_filename(); /// This actually renames the file on disk. void set_filename( std::string const& ); };
Now, when you bind the filename property to the UI, you'll automatically do the rename as soon as the user changes the text in the edit box. (Probably that isn't the desired behaviour in this case, but it serves as a demonstration.) This usage pattern corresponds better to modeless dialogs where you don't have an "OK" button.
Note: I support both modeless and modal dialogs (more to the point: saving data can happen automatically - click of "Ok", or programmatically). Usually, you need more validation than a simple filename. The user could enter data into more controls, and once the user is done, you run the validation routine(s). This is the transaction commit/fallback pattern, which is much better the direct binding IMO. Also, notice that when doing validation with save_dlg, when a validation routine is called, you have acess to more states of your data: - the original value - the old value - the last known good value (the last value for which all validation code succeeded) - the new value When a validation routine is called, you can set an error, or for instance, simply set the value back to the - old value - last known good value - or whatever you wish. I would say this is very flexible. Hopefully in the near future I'll have the time to update the docs so that people will understand the save_dlg's full power. Best, John -- John Torjo, Contributing editor, C/C++ Users Journal -- "Win32 GUI Generics" -- generics & GUI do mix, after all -- http://www.torjo.com/win32gui/ -- v1.5 - tooltips at your fingertips (work for menus too!) + bitmap buttons (work for MessageBox too!) + tab dialogs, hyper links, lite html
participants (2)
-
George van den Driessche
-
John Torjo