Development: XTI

From WxWiki
Jump to: navigation, search

Overview

The need for an extended type system was driven from the need of making a designer application (cbx for Borland) for wx that would be able to work with plug-ins of new components, retrieving their information at load-time.

So we have exposed properties that could be changed when designing, exposed event sources and exposed event sinks (these got created in the designer itself, both with source and their entries in the handler, you were free to move code around, but the handler table was in the hand of the designer).

Another feature is the dynamic class feature. The idea there was to be able to create new classes at runtime, eg have a proper class for a db-table, using kind of code injection, so kind of similar things that Ruby does with ActiveRecords etc now. This feature would be especially useful from dynamic languages like Python etc.

Class Metainformation

Flags

The flags have to be inserted into the metadata system. So that they can be streamed out as strings, allowing independence from changes in values.

WX_DEFINE_FLAGS( wxButtonStyle )

wxBEGIN_FLAGS( wxButtonStyle )
    // new style border flags, we put them first to
    // use them for streaming out
    wxFLAGS_MEMBER( wxBORDER_SIMPLE )
    wxFLAGS_MEMBER( wxBORDER_SUNKEN )
    wxFLAGS_MEMBER( wxBORDER_DOUBLE )
    wxFLAGS_MEMBER( wxBORDER_RAISED )
    ...
wxEND_FLAGS( wxButtonStyle )

Properties

Properties are on the one hand true properties in the sense that they are values that would be edited in a designer, and on the other hand also event sources (eg 'Click') against which handlers (event sinks) can be connected :

wxBEGIN_PROPERTIES_TABLE(wxButton)

    wxEVENT_PROPERTY( Click , wxEVT_COMMAND_BUTTON_CLICKED , wxCommandEvent )

    wxPROPERTY( Font, wxFont, SetFont, GetFont, EMPTY_MACROVALUE, 0 /*flags*/,   \\
        wxT("Helpstring"), wxT("group") )
    wxPROPERTY( Label, wxString , SetLabel, GetLabel, wxString(), 0 /*flags*/,   \\
        wxT("Helpstring"), wxT("group") )

    wxPROPERTY_FLAGS( WindowStyle, wxButtonStyle, long, SetWindowStyleFlag,      \\
        GetWindowStyleFlag, EMPTY_MACROVALUE, 0 /*flags*/, wxT("Helpstring"),    \\
        wxT("group") ) // style

wxEND_PROPERTIES_TABLE()

Normal Properties

A normal property is declared by indicating its

  • name,
  • type,
  • setter and getter functions,
  • default value,
  • flags,
    • wxPROP_DEPRECATED means property will be removed in future releases
    • wxPROP_OBJECT_GRAPH for a object graph property, will be streamed with priority (after constructor properties)
    • wxPROP_ENUM_STORE_LONG this property will only be streamed out and in as enum/set, the internal representation is still a long
    • wxPROP_DONT_STREAM don't stream out this property, needed eg to avoid streaming out children that are always created by their parents
  • help string
  • group (allowing grouping for related properties in a editor)
wxPROPERTY( Label, wxString, SetLabel, GetLabel, wxString(), 0 /*flags*/,    \\
    wxT("Helpstring"), wxT("group") )

A property that is a flag has its flag type indicated before the numeric type used

wxPROPERTY_FLAGS( WindowStyle, wxButtonStyle, long, SetWindowStyleFlag,      \\
    GetWindowStyleFlag, EMPTY_MACROVALUE, 0 /*flags*/, wxT("Helpstring"),    \\
    wxT("group") ) // style

Event Properties

An event exposes its

  • name
  • wx event type
  • event class
wxEVENT_PROPERTY( Click , wxEVT_COMMAND_BUTTON_CLICKED , wxCommandEvent )

Handler

Handlers are event sinks that are connected against event properties, they must be exposed like this with their method name and its event parameter

WX_BEGIN_HANDLERS_TABLE(MyXTIFrame)
    WX_HANDLER( ButtonClickHandler , wxCommandEvent )
WX_END_HANDLERS_TABLE()

for a method in a class that would be defined e.g. as:

void ButtonClickHandler( wxCommandEvent & WXUNUSED(event) )
{
    wxMessageBox( "Button Clicked ", "Hi!", wxOK );
}

Archiving / Instantiating object trees

The term used at the time was persisting / depersisting (wx/xtistrm.h).

wxPersister

a persister instance is called during writing and object, for every attribute, getting the chance for skipping attributes which should not be written out, or changing what gets written. You can subclass wxPersister if you want to change the built-in behaviour :

class MyDesignerPersister : public wxPersister
{
public:

    MyDesignerPersister( wxDynamicObject * frame)
    {
        m_frame = frame;
    }

    virtual bool BeforeWriteDelegate( wxWriter *WXUNUSED(writer),
                                      const wxObject *object,
                                      const wxClassInfo* WXUNUSED(classInfo),
                                      const wxPropertyInfo *propInfo,
                                      const wxObject *&eventSink,
                                      const wxHandlerInfo* &handlerInfo )
    {
        // this approach would be used if the handler would not be connected really
        // in the designer, so we have to supply the information
        if ( object == m_frame->GetProperty(wxT("Button")).GetAsObject() &&
             propInfo == CLASSINFO( wxButton )->FindPropertyInfo("OnClick") )
        {
            eventSink = m_frame;
            handlerInfo = m_frame->GetClassInfo()->FindHandlerInfo("ButtonClickHandler");
            return true;
        }
        return false;
    }

private:

    wxDynamicObject *m_frame;
};

an existing object tree (frame) is streamed out like this :

wxXmlDocument xml;
wxXmlNode *root;
root = new wxXmlNode(wxXML_ELEMENT_NODE, "TestXTI", "This is the content");
xml.SetRoot(root);
wxXmlWriter writer(root);
MyDesignerPersister persister(frame);
wxxVariantArray metadata;
writer.WriteObject( frame , frame->GetClassInfo() , &persister , wxString("myTestFrame"), metadata );
xml.Save("testxti.xml");

wxDepersister

A depersister reads in the xml file and creates a representation in a different format. It has callbacks for every element read back. wx has eg a wxRuntimeDepersister :

// is loading the streamed out component from xml and instantiating it using the
// wxRuntimeDepersister

wxObject* TryLoad()
{
    wxXmlDocument xml;
    wxXmlNode *root;
    wxRuntimeDepersister Callbacks;

    xml.Load("testxti.xml");
    root = xml.GetRoot();
    if (root->GetName() != "TestXTI")
    {
        assert(!"Bad XML file");
    }
    wxXmlReader Reader(root);
    int obj = Reader.ReadObject( wxString("myTestFrame") , &Callbacks );
    return Callbacks.GetObject(obj);
}

or a wxCodeDepersister that creates the corresponding source code :

// is loading the streamed out component from xml and writing code that  
// will create the same component 

void TryCode()
{
    wxXmlDocument xml;
    wxXmlNode *root;
    wxFFileOutputStream fos( "testxti.cpp" ) ;
    wxTextOutputStream tos( fos ) ;
    wxCodeDepersister Callbacks(&tos);

    xml.Load("testxti.xml");
    root = xml.GetRoot();
    if (root->GetName() != "TestXTI")
    {
        assert(!"Bad XML file");
    }

    wxXmlReader Reader(root);
    Reader.ReadObject( wxString("myTestFrame") , &Callbacks ) ;
}

Dynamic Class creation

the following code shows how you'd setup a class that does not exist as compiled code, this can especially be useful when using dynamic languages, implementing features like ActiveRecord (Ruby on Rails)

// Setting up the RTTI info for a not-yet-compiled class MyXTIFrame
wxDynamicClassInfo *dyninfo = new wxDynamicClassInfo(wxT("myxtiframe.h"),
                                                     wxT("MyXTIFrame"),
                                                     CLASSINFO( wxFrame ) );
dyninfo->AddProperty("Button", wxGetTypeInfo((wxButton**) NULL));
dyninfo->AddHandler("ButtonClickHandler",
                    NULL /* no instance of the handler method */,
                    CLASSINFO( wxEvent ) );

Wish list

  • Have some basic meta information about the class, e.g. name, description, probably also author/copyright. Maybe version (but how would this be used?).
  • List of supported interfaces (by name) and some way to get a pointer to a C++ object of given type for the supported interfaces. E.g. an object could implement "image handler" interface which would mean that it could be used as wxImageHandler, i.e. this is not only for GUI classes. But we should have a standard "GUI control" interface which would add additional stuff used for dialog editors and similar, e.g. adding a method returning an icon and/or method which could be called to draw the class to a given DC. CS : wouldn't using GUIDs be more appropriate here ?

See also

Object serialization using XTI and XML