Showing posts with label CodeProject. Show all posts
Showing posts with label CodeProject. Show all posts

Thursday, May 1, 2008

Working with Delegates in Visual Form Files

So you've got your interface put together via your Visual Form File resource. Now you want to hook up some event handlers or callbacks in your application code to the UI. No problem!

The first thing you need to to is write the code. Let's start off by using our application class as a place to put the callbacks. Later on we'll investigate using the main window class as well, but for now, lets look at our app class:


class LOLCatsApp : public Application {
public:

};


OK let's put in some code to handle a button click. The command button class has a delegate that takes a function with one parameter, a ButtonEvent* and returns nothing (void). Since ButtonEvent derives from Event, and in this case we really don't care much about the event per se, we can "cheat" a bit and simply write a function that takes a generic Event*.


class LOLCatsApp : public Application {
public:
void myCallBack( Event* ) {
Dialog::showMessage( "Hello there!" );
}
};


Now we've defined our callback. Let's add it to the application for later retrieval:


class LOLCatsApp : public Application {
public:
LOLCatsApp( int argc, char** argv ) :
Application(argc, argv) {
addCallback( new ClassProcedure1<Event*,LOLCatsApp>(this, &LOLCatsApp::myCallBack), "LOLCatsApp::myCallBack" );
}

void myCallBack( Event* ) {
Dialog::showMessage( "Hello there!" );
}
};


An application is component, and therefore can have 0 or more callbacks. So we call the app's addCallback() method and create a link to our callback function making sure to give a correct name.

At this point, we've defined/implemented a callback function, and added it to the list of callbacks the application maintains and that can be retreived by others via a call to the app's getCallback() function. All we have left is to hook the application's callback to the component in our VFF definition.

Like properties, a component can expose the delegates it has via the VCF's RTTI macros. This allows you to access them in the vff definition. The rule for this is that each object block, i.e.

object MyObj : MyClass

end


has an option delegates section, defined like so:

object MyObj : MyClass
delegates
end
end


Within this block, you can reference the delegates of the component and then assign an array of callbacks to the delegate. Each callback in the array will be added to the delegate. In other words:

object MyObj : MyClass
delegates
MyDelegate = [SomeComponentName@SomeComponentClass::SomeCallBackFunction]
end
end

Each item in the array of callbacks has the following format: the name of a valid component instance, the "@" character, and the name of the callback. Technically the name of the callback can be anything you want, however the convention is to use fully qualified C++ names, so that's usually the name of the component instance's class, the "::" qualifier, and the name of the method.

The component referenced can be any component in the VFF definition, or it can be the name of the app class. By default, the name of the app is it's class name *unless* you explicitly change it before you load up your form(s).

Armed with this information, lets add our callback to a button:

object myBtn : VCF::CommandButton
caption = 'Click Me'
delegates
ButtonClicked = [LOLCatsApp@LOLCatsApp::myCallBack]
end
end


When the form is loaded the framework will look at the myBtn instance, get access to it's ButtonClicked delegate and the callback named "LOLCatsApp::myCallBack" in the LOLCatsApp instance, and then add the callback to the delegate. At that point everything is "wired" together - when you click the button, the LOLCatsApp::myCallBack code will be invoked!

One question might be how to know what delegates are available. You can always browse the code, but that may nor may not immediately tell you that the delegate is exposed via the RTTI macros. Another way is use the new ClassRegistry Browser ( ClassRegistryBrowser.zip ). This is a graphical program that shows all the registered classes in the VCF's ClassRegistry. A class must be registered in the ClassRegistry for it to available for reference in a VFF definition. When you select a class, the program then iterates through all of the properties and delegates of the class. Something a bit like this:



Using this tool you can definitively determine what delegates are available and can be safely referenced in your VFF definition.

Now, let's make a change and instead of using our app class, lets use our window class to hold our callback. Let's add the code to our class first:


class LOLCatsWindow : public Window {
public:
LOLCatsWindow();

virtual ~LOLCatsWindow(){};

void myCallBack( Event* ) {
Dialog::showMessage( "Hello there!" );
}

};


Add our callback:

class LOLCatsWindow : public Window {
public:
LOLCatsWindow() {
addCallback( new ClassProcedure1<Event*,LOLCatsWindow>(this, &LOLCatsWindow::myCallBack), "LOLCatsWindow::myCallBack" );

}

virtual ~LOLCatsWindow(){};

void myCallBack( Event* ) {
Dialog::showMessage( "Hello there!" );
}

};


And then wire it up in the VFF:

object LOLCats : VCF::Window
object myBtn : VCF::CommandButton
caption = 'Click Me'
delegates
ButtonClicked = [LOLCats@LOLCatsWindow::myCallBack]
end
end
end


That's it! You've now got your window class handling a button click.

This same technique can be applied to any component that exposes delegates through the VCF's RTTI. For more examples you might want to look at the following:
vcf/examples/LOLCats
vcf/examples/ClassRegistryBrowser
vcf/examples/ListViews

Wednesday, May 2, 2007

Visual Form File format

XAML this, XAML that, blah, blah, blah. I get so sick of hearing about how great XAML is, as if this were the most mind altering, earth shaking technology to hit the streets in years. Bah Humbug!

We've had something like this for years in the VCF! And the predecessor to XAML dates back even further than that, to Delphi's form files, NeXT's NIB files, and so on. So, I thought I'd post what the format for a Visual Form File (VFF) looks like, in Backus-Naur format:


component-decl ::= "object" component-id component-properties
*(component-decl) [component-delegates] "end"
component-id ::= component-name component-class ["," component-classid]

component-name ::= id
id ::= ALPHA *(ALPHA | DIGIT | '_' )
component-class ::= ALPHA *(ALPHA | DIGIT | '_' | "::" )
component-classid ::= ''' 1*(ALPHA | DIGIT | '-' ) '''

component-properties ::= 1*component-property
component-property ::= property-name '=' property-value
property-name ::= id | property-object-name | property-collection-name
property-object-name ::= id '.' property-name
property-collection-name ::= id '[' property-collection-key ']'
property-collection-key ::= property-number | property-string
property-value ::= property-string | property-number |
property-component-ref | property-enum |
property-enum-mask | property-binary | property-bool


property-string ::= ''' *(ALPHA | DIGIT | 'any printable unicode char' ) '''
property-number ::= 1*DIGIT ['.' 1*(DIGIT) ]
property-component-ref ::= ('@' component-name) | "null"
property-enum ::= id
property-enum-mask ::= '[' property-enum *[',' property-enum] ']'
property-binary ::= '{' binary-data '}'
binary-data ::= 1*(binary-byte)
binary-byte ::= (DIGIT | 'A' | 'B' | 'C' | 'D' | 'E' | 'F' )
(DIGIT | 'A' | 'B' | 'C' | 'D' | 'E' | 'F' )
property-bool ::= "true" | "false"

component-delegates ::= "delegates" *(component-delegate-decl) "end"
component-delegate-decl ::= delegate-name '=' delegate-array
delegate-name ::= id
delegate-array ::= '[' 1*(event-handler-ref) ']'
event-handler-ref ::= component-instance '@' component-callback-method
component-instance ::= id
component-callback-method ::= component-classname "::" component-methodname
component-classname ::= id
component-methodname ::= id


A simple example:

object window1 : VCF::Window
top = 500
left = 500
width = 400
height = 300
end

Note that the class name must be a fully qualified C++ class name, including the namespace the class belongs to.

A more complex example might look like this:

object Form1 : Window, 'ED88C0A1-26AB-11d4-B539-00C04F0196DA'
alignment = AlignNone
anchor = 0
bottom = 555.00000
color.blue = 0.78431
color.green = 0.81569
color.red = 0.83137
height = 537.00000
left = 234.00000
name = 'Form1'
top = 70.00000
visible = true
width = 565.00000

object label1 : VCF::Label, 'ED88C09F-26AB-11d4-B539-00C04F0196DA'
anchor = [AnchorLeft,AnchorRight]
bottom = 100.00000
caption = 'This is a label!'
color.blue = 0.50000
color.green = 1.00000
color.red = 0.00000
height = 45.00000
left = 20.00000
name = 'label1'
textAlignment = taTextLeft
top = 55.00000
verticalAlignment = tvaTextCenter
visible = true
width = 300.00000
wordWrap = false

delegates
end
end

delegates
end

end

This is what we use to load up components dynamically at runtime. So a form can be stored in format, and then loaded at runtime, dynamically no less (!), like so:


Window* window = Frame::createWindow( classid(VisualFormFilesWindow) );


So no one thinks we're using some new fangled version of C++, "classid" is simply a macro that gets the VCF Class instance from a given C++ class type.

When you call Frame::createWindow(), you get a new instance of your window class (called VisualFormFilesWindow), with all the controls and components as you've defined them in your VFF file! Voila!

There's more information on the VFF format here:
Visual Form File format

Something to note - in the latest version, we now support collection properties that can be specified via the RTTI macros, and can also be modified via the VFF format, for example:

object myObj : MyObjectWithItems
items[0] = 10
items[1] = 123
end