Menu

#38 Discuss and implement a better separation between user code and wxGlade code

to discuss
closed
None
2017-12-06
2016-05-31
AnotherUser
No

When developing an application of even a modest size, the wxglade model can quickly become unwieldy, as I find myself writing code in the stubs, which need to be merged whenever a UI change is performed. I currently use vimdiff for this.

This is a further problem when upgrading between different wxGlade versions, as the generated code is not as easily diffed.

Perhaps there can be some ideas generated around how this could be improved. Ultimately, it would be ideal if wxglade could do its best to only modify its own output code, given a mix of wxglade generated code, and some user modifications. I.e. any user changes to the source would be left alone.

This however, seems difficult, and possibly requires some serious time to be put into such a module.

Maybe there is some middle ground. An example could be automating some simple patch mechanics, such as generating slightly clever patches from two wxglade files, which can be applied ot the user source.

I am uncertain what is the best way to proceed, but would certainly be keen to see what ideas are out there.

Discussion

  • Carsten Grohmann

    • summary: en e --> Discuss and implement a better separation between user code and wxGlade code
     
  • Carsten Grohmann

    • Group: Next Release --> to discuss
     
  • Dietmar Schwertberger

    The handling of automatic and user generated code needs to be improved.
    But honestly I don't understand the problem that is reported here.
    Could you please provide an example?

     
  • AnotherUser

    AnotherUser - 2017-07-13

    Hi Dietmar, thanks for taking the time to look at this. My project is online at http://threedepict.sourceforge.net the UI is wxglade based. You can look at the UI code here:
    https://sourceforge.net/p/threedepict/code/ci/default/tree/src/gui/

    It is hard to come up with a small example, as for small stuff there is not really any issue. It is more the cumulative effort of keeping things in sync.

    What I do is layout my UI using wxglade, then tweak it from there. As a very synthetic example: perhaps I want a button to activate only on a given criterion.

    So I write this:

    #include "wxglade_out.h"
    
    // begin wxGlade: ::extracode
    // end wxGlade
    
    enum
    {
        ID_CHECK_BUTTON=wxID_HIGHEST +1
    };
    
    MyFrame::MyFrame(wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style):
        wxFrame(parent, id, title, pos, size, style)
    {
        // begin wxGlade: MyFrame::MyFrame
        panel_1 = new wxPanel(this, wxID_ANY);
        checkbox_1 = new wxCheckBox(panel_1, ID_CHECK_BUTTON, _("checkbox_1"));
        button_1 = new wxButton(panel_1, wxID_ANY, _("button_1"));
    
        set_properties();
        do_layout();
        // end wxGlade
    }
    
    void MyFrame::set_properties()
    {
        // begin wxGlade: MyFrame::set_properties
        SetTitle(_("frame_1"));
        // end wxGlade
    }
    
    void MyFrame::do_layout()
    {
        // begin wxGlade: MyFrame::do_layout
        wxBoxSizer* sizer_1 = new wxBoxSizer(wxVERTICAL);
        wxBoxSizer* sizer_2 = new wxBoxSizer(wxHORIZONTAL);
        wxBoxSizer* sizer_3 = new wxBoxSizer(wxHORIZONTAL);
        sizer_3->Add(checkbox_1, 0, 0, 0);
        sizer_3->Add(button_1, 0, 0, 0);
        panel_1->SetSizer(sizer_3);
        sizer_2->Add(panel_1, 1, 0, 0);
        sizer_1->Add(sizer_2, 1, 0, 0);
        SetSizer(sizer_1);
        sizer_1->Fit(this);
        Layout();
        // end wxGlade
    }
    
    BEGIN_EVENT_TABLE(MyFrame, wxFrame)
        // begin wxGlade: MyFrame::event_table
        EVT_CHECKBOX(wxID_ANY, MyFrame::OnCheck)
        // end wxGlade
    END_EVENT_TABLE();
    
    void MyFrame::OnCheck(wxCommandEvent &event)
    {
        button_1->Enable(!button_1->IsEnabled());
    }
    
    // wxGlade: add MyFrame event handlers
    

    Note I have modified MyFrame::OnCheck. If I want to change the user interface now, I have to go back to the .wxg file, generate a new file, and then merge the changes across. If I do not merge, I will loose the content of OnCheck, as I wrote this by hand

    Currently I do this via diffing. My application is modestly large, so there can be lots of changes, including in the layout functions, where I overrode wxglade. Some I want to keep, others I have to change

    So every time I make changes to the UI I use vim's diff mode to compare the two sets, then very carefully go through all of the differences to merge the actual change across. This is highly error prone. Sometimes I miss changes I have made in the source, and have not made in the wxg file. Sometimes updates between wxglade versions re-order code, or emit slightly different code so the diffs no longer match.

    If wxGlade could scan a source file, or some manually prepared subset of this, and see what it can build out of it, that would be amazing. Then I could try to pick between the changes, and move them back into the wxglade, with greater confidence of not failing along the way.

    I admit this is quite hard, and might not be a priority.

     
  • Dietmar Schwertberger

    First: It's a very long time since I did anything with C++.

    In Python files generated by wxGlade, an event handler looks like this:

        def on_checkbox_event(self, event):  # wxGlade: MyFrame.<event_handler>
            print("Event handler 'on_checkbox_event' not implemented!")
            event.Skip()
    

    The user may edit the event handler and in that case the user content is preserved over the next code generation. Technically, the user content is detected by the "wxGlade: MyFrame.<event_handler>" comment.
    This is not very robust against things like renaming of classes etc., so it's always good to keep backups.
    I was not aware that this is not implemented in C++. This would have solved your problem already. I'm sure that at least part of the implementation is there. I will check whether there's a bug preventing the use of it...

    I would normally only add small GUI glue code into these event handlers. Your checkbox example would be such glue code.

    For the more complex use cases, especially any business logic, I would recommend to derive from the generated class and then override the event handler methods there.
    Doesn't this work in your case?

    An example in Python:

    Set output file name to e.g. ActivatorGUI.py; window name e.g. ActivatorFrame

    The generated code, stripped down a bit:

    class ActivatorFrame(wx.Frame):
        def __init__(self, *args, **kwds):
            wx.Frame.__init__(self, *args, **kwds)
            self.panel_1 = wx.Panel(self, wx.ID_ANY)
            self.checkbox_1 = wx.CheckBox(self.panel_1, wx.ID_ANY, _("Activate"))
            self.button_1 = wx.Button(self.panel_1, wx.ID_ANY, _("button_1"))
    
            self.__set_properties()
            self.__do_layout()
    
            self.Bind(wx.EVT_CHECKBOX, self.on_checkbox_event, self.checkbox_1)
            self.Bind(wx.EVT_BUTTON, self.on_button_event, self.button_1)
    
        def __set_properties(self):
            ...
        def __do_layout(self):
            ...
    
        def on_checkbox_event(self, event):  # wxGlade: MyFrame.<event_handler>
            print("Event handler 'on_checkbox_event' not implemented!")
            event.Skip()
    
        def on_button_event(self, event):  # wxGlade: MyFrame.<event_handler>
            print("Event handler 'on_button_event' not implemented!")
            event.Skip()
    

    File Activator.py or main.py:

    import wx
    import ActivatorGUI
    
    class ActivatorFrame(ActivatorGUI.ActivatorFrame):
        def __init__(self, app):
            self.app = app
            ActivatorFrame.ActivatorFrame.__init__(self, None, wx.ID_ANY, "")
    
        def on_checkbox_event(self, event):
            self.button_1.Enable( self.checkbox_1.IsChecked() )
            event.Skip()
        def on_button_event(self, event):
            self.app.do_something()
    
    class ActivatorApp(wx.App):
        # application / logic
        def OnInit(self):
            self.init_data()
            self.frame = ActivatorFrame(self)
            self.SetTopWindow(self.frame)
            self.frame.Show()
            return True
        def do_something(self):
            print("Button pressed")
    
    Activator = ActivatorApp(0)
    Activator.MainLoop()
    
     

    Last edit: Dietmar Schwertberger 2017-07-15
  • Dietmar Schwertberger

    OK, I've had a look: the code for preserving C++ event handlers was almost there.
    Would you please use the latest repository version and modify one file? I will update the repository only in the next days after some more testing of several other things.

    Go to codegen/cpp_codegen.py, around line 1064
    This should look like this:
    void %(klass)s::%(handler)s(%(evt_type)s &event)
    Change it to this:
    void %(klass)s::%(handler)s(%(evt_type)s &event) // wxGlade: %(klass)s.<event_handler>

    With this modification, event handlers should look like this (note the comment at the end which is the marker):

    void ActivatorFrame::on_checkbox_event(wxCommandEvent &event)  // wxGlade: ActivatorFrame.<event_handler>
    {
        event.Skip();
        // notify the user that he hasn't implemented the event handler yet
        wxLogDebug(wxT("Event handler (ActivatorFrame::on_checkbox_event) not implemented yet"));
    }
    

    Deactivate "Overwrite existing sources" in Application Properties. This way you can edit the handler in the generated file and it will be preserved.

    Regards,
    Dietmar

     

    Last edit: Dietmar Schwertberger 2017-07-14
  • Dietmar Schwertberger

    One additional note: Carsten has marked the "Overwrite existing sources" as deprecated.
    I will for sure keep it. It's open how to do this exactly. The current approach is name based and not robust enough. I'm thinking about a unique ID per widget which will not change on moves or renames.
    I would also like to implement an editor for the handlers in wxGlade.

    But anyway that does not change my recommendation: place only glue code in the GUI classes, keep business logic in the application or in a derived class.

    Regards,
    Dietmar

     
  • Dietmar Schwertberger

    • status: open --> closed
    • assigned_to: Dietmar Schwertberger
     
  • Dietmar Schwertberger

    "Overwrite existing sources" is now renamed to (not) "Keep user code", i.e. different name and the meaning is reversed.
    Also, the documentation has been updated with an example how to organize code. The example is in Python, though.
    http://wxglade.sourceforge.net/docs/source_code.html
    User contribution for a C++ example welcome...

     

    Last edit: Dietmar Schwertberger 2017-12-06

Log in to post a comment.