Creating custom VFD components
While most of the times you will be using one of the pre-defined VFD components, chances are that one day you will need to implement some others of your own; luckily, you can do that with ease!
The first thing to know is that, in order to create a VFD component, you only have to take an existing class that discends from java.awt.Component (or create a new one) and implement on top of that an interface, named nrio.MBIOComponent.
There are three kinds of VFD components, as far as behaviour is concerned: field components, field validators, decorative components. Decorative components are not tied to a specific field, they just add some fancy graphics, labels etc to your forms; field components, instead, are designed to interact with the user and can therefore accept input and present an output of the related field. Validators are components that can decide about the correctness of the user input as a whole, and therefore they have access to all of the data entered; validators can be also either field components or decorative components. Note: The distinction between field components, validators, decorative components is based almost exclusively on your class behaviour; if you implement the setValue and getValue methods with empty methods, you get a decorative component, while you must indicate via the isValidator method if your component is a validator.
Simple VFD components will discend from the standard AWT components such as Canvas, Choice, Label, etc, while composite VFD components will likely inherit from java.awt.Panel.
Note: no experiment has been done with lightweight components (e.g. descending from Swing components), but they should work just fine.
Note2: source code for the default components is provided in this package.
Let's see all of the methods for nrio.MBIOComponent, divided into categories.
Methods that deal with the visual appearance of the components:
public void setSize(Dimension d) public Dimension getSize(void) public Dimension getPreferredSize(void) public void setVisible(boolean b) public boolean isVisible(void) public void setLocation(Point p) public Point getLocation(void)
These methods are the same as the corresponding java.awt.Component methods. They're needed, but usually you don't need to override them.
public Dimension getPreferredSize(Dimension d) This method allows you to specify an aspect ratio to be kept; when the user resizes a component in the VFD, this method is called to check whether the new size is correct or not.
public void setParentBackground(Color c) public void setParentForeground(Color c) public void setParentFont(Font f)
These methods are used by MaxBase, VFD and other classes to inform your components that the panel on which they're laid off has been customized with the specified font and colors; it is up to you to decide whether to comply with your parent colors/font or not (that's why some new methods have been created instead of reusing setFont, setBackground etc).
public void setMode(int mode) public int getMode(void) These methods are used to discern in which operating mode has our component been put. You may, or may not, want to specify different behavioursfor different modes.
The set/getMode methods can accept/return the following parameters/return values: MBIOComponent.LAYOUTEDIT MBIOComponent.PROPSEDIT MBIOComponent.DATAEDIT MBIOComponent.DATAINPUT MBIOComponent.VIEW
xyzEDIT and VIEW are used to discern whether we are in view mode (e.g. printing), data edit mode (user modifying data), data input mode (user adding a record to the archive), props edit mode (developer wanting to customize this component) or layout edit mode (visually building the report or add/modify panel).
public Image getIcon(int dimension, boolean color) public String getName(void) Although not used as of yet, these methods will be used by future releases of the VFD to present to the user an icon and a name for the component.
Other component-specific methods:
public void setCustomProps(s = String[][]) public String[][] getCustomProps(void) Custom properties are optional settings that don't fit into the standard VFD component properties; you can have as many as you need. Each property has a name and a value, both of them representable as string.
Letting the user customize your component is up to you: whenever the user requests to change the component custom properties in the VFD, the component setMode(PROPSEDIT) method will be called (it is up to you to restore it to its original state once the customization process is ended). Whenever the developer/user has finished creating a Form View Report, the visual builder asks for the custom props.
Elements of the array are as follows: [0] [0] = number of properties [n] [0] = name of property 'N'. [n] [1] = value (toString) of property 'N'. Note: components that don't have properties to be customized MUST return the null value (this way the visual builder won't let the user customize the component\ properties).
public void shutDown(int result) This method is issued upon every component right before the form is closed; if your components need to perform any finalization actions that are not done automatically when the components are destroyed, this is the right place where to execute them.
Note: the result parameter can be one of the following: MBIOComponent.OK, MBIOComponent.ABORT. If the OK value is passed, the corresponding input session brought to a record succesfully written to disk, while ABORT may mean that the record wasn't stored OR that the user cancelled the record input session.
public String getHelpText(void) An external help system (implemented by the VFD) is available, and if you want to let the user know about how to customize or use your component, you can implement this method. Else, return NULL. Hint: help is always nice, if anything for version and author information. Hint2: if you check the current mode (dataedit/propsedit/..) before spitting out a string at God speed, you can provide context-sensitive help (how to enter data vs. how to customize your component vs. ..).
Methods that let your components deal with external objects:
public void setLPInterface(LPInterface lpi) LPInterface is the object that poses as message dispatcher between MaxBase instances. Therefore, your component could be interested in getting some data from it (say a listbox of suppliers from which to choose from). Note: LPInterface is by default NULL: it is set to a meaningful value only if you called MaxBase passing a non-NULL LPInterface parameter. You can find more about LPInterface by reading the corresponding HTML docs.
Database-related methods:
public void setValue(Object s) public Object getValue(void) These two methods are very important: they are used to set/get the value for the field this component is linked to (if any). If your component is only a decorative one, you can avoid implementing these methods. Note: if the user is entering new data, you will be passed (with the setValue method) an empty string: "". If this is the case, you may want to present a default value. Note2: If you return NULL in getMethod, the whole record won't be stored in the database. Use the empty string ("") to return an empty but valid value. Note3: while the Object parameter/return type was really needed to keep the forms open to future enhancements, as of today they really are Strings. (You are passed a String with setValue, and you should return a String from getValue)
public void setFieldInfo(String[] s) You are passed information about the field that your component will have to represent. Feel free to use this information, or to completely ignore it. Elements of the array are as follows: [0] = field length [1] = field name [2] = field indexing (ISAM00, NONE1, B+TREE11, etc). Note: MaxBase knows which field to assign to your component, since this information has been given to it by the user that designed the panel/report.
public String getFieldName(void) This is used by the visual form builder.. Please return the name of the field this component is associated with.
public void setFieldName(String name) Likewise, the above method is used during LAYOUTEDIT mode for setting the field associated with this component.
public void setDbInfo(String name, boolean bApplet) The above method is used to pass the db name (as a string) and information about whether we're running as an applet or not. Note: if the database we're currently logged in is not local, the db name will be in the form of an URL (mbase://host.domain:port), else it is reported as a platform-dependent file name ending in ".dat".
The last part of the interface methods description is devoted to validator stuff.
What is a validator? The short answer is: a validator is a component that can decide about the correctness of user input (as a whole). If a component is a validator, it is passed all of the fields values both upon panel instantiation (during data modify stage) and panel dismissing (after the user has pressed "OK" during add or modify stages). The validator can therefore process this information (and take actions accordingly), and when asked if everything is ok by issuing on it the Validate method (done just before closing the panel), it can make the whole add/modify procedure abort.
Since the validator is a GUI component in itself, it can visually present the user reasons why the input wasn't accepted. Of course, if you don't need a GUI validator, you can always let it be invisible during add, modify and/or view (most notably printing) stages by setting its 'visible' property to false after setMode has been issued on it.
Note: a validator is by definition also a MaxBase I/O component, and therefore it can also represent a db field. Therefore you can have 'ID' fields that get automatically filled with parts of other fields (eg. an 'ID' field that is composed by the supplier's ID plus the article ID plus the current date and time to uniquely identify a sale). Note2: if you don't really need such a complex beast as a validator, you can always resort to returning NULL values in fields. E.g. if you don't want a field to be blank, but the user didn't enter any data, you can return NULL on that field and MaxBase won't store the record in the archive. Validators are optional, therefore you can have zero of them in your panels.
public boolean isValidator(void) public void setValidator(boolean b) Used by the visual builder for presentation purposes. Please implement them even if your component is not a validator!
public void setFieldInfo(String[][] s) Field information: [0, 0] is the number of fields. [n, 0] is field 'n' length. [n, 1] is field 'n' name. [n, 2] is field 'n' indexing type. This method is different from the setFieldInfo(String[]) one, because this gives the validator information about all of the fields in the database.
public void setRecordValues(String[] s) Record values for this form: [n] is current value of field 'n'.
public int ValidateThis(void) Use the above to validate currently entered data. Valid return code are: MBIOComponent.OK MBIOComponent.OKSTAY MBIOComponent.ABORT MBIOComponent.STAY OK = Data entered is ok: dismiss the panel, OKSTAY = Data entered is ok: stay and let the user make further changes, ABORT = Data is not ok: dismiss the panel, STAY = Data is not ok: stay and let the user correct errors.
Note: OKSTAY is only to be used when modifying records.
File format for .mfr files (the ones produced by the VFD)
While .mfr files have been invented with MaxBase in mind, it is certainly true that you can build your own VFD or an .mfr parser in order to use .mfr forms with non MaxBase databases. Why should you do that?
- The .mfr format is quite flexible, and is already battle-proven (its features have been suggested by developers and put to use in real world applications).
- Having a single, unified form file format is a benefit for the developer, who can reuse panels and components with different database programs.
- Even if you don't use MaxBase, by implementing your .mfr parser that acts as an interface between user input and the database engine you can then use the MaxBase form view printing beans, thus reducing development times.
You can contribute to the evolution of the .mfr file format by sending your suggestions to me, the MaxBase author -- I am interested in everybody's findings and am quite willing to keep the .mfr file format open!
The file format is quite simple: you have a number of characters (with no line feeds) representing the form, divided in pages. Each page string contains information about page settings and the objects contained in the page itself.
The simplest page is represented by this string (pipes, "|", are used to separate information bits):
Page 1|||640 400|255 255 0|192 192 192|0 0 0|Dialog|p 12| |1|
Page 1 is the caption of the page tab.
640 400 is the dimension of the form.
255 255 0 are the R G B components of the page tab background color.
192 192 0 are the R G B components of the default page background color.
0 0 0 are the R G B components of the default page foreground color.
Dialog is the default page font family name.
p 12 are the default page font attributes and size. 'p' means plain, 'b' means bold, 'i' means italic (you can have 'bi' to obtain bold and italics text at the same time).
the blank space means that no background image has to be used for the form -- otherwise, a platform specific file name must be specified.
1 means that an eventual background image would maintain its original aspect ratio (0 would mean don't maintain original aspect ratio).
After page information, you can have information for objects stored in that page (remember, there can be empty pages). Object information is structured this way:
nrio.FreeText|1|224 28|132 150|0|Genere||
nrio.FreeText is the fully classified class name of this object.
1 is the object name (VFD uses numbers as names for objects).
224 28 is the object size (width, height).
132 150 is the object position (x, y).
0 means that this object is not a validator (1 would mean that it is).
Genere is the name of the field this component is tied to. If the object wasn't tied to a field, we would have had a space instead.
Note: This object doesn't have any custom property. An object that has custom properties would have some more entries, using the |property name|property value|property name|property value|... format.
The object string ends with a double pipe (||) when another object follows it; it ends with nothing if there are not other objects following it (even if there are other pages after that object).
A triple pipe (|||) is placed between two pages.
If you want to see some form examples, all you have to do is fire off the VFD, create a form and save it, then explore the .mfr file contents with an ASCII editor. |