|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Habanero Tutorial
- for v2.0.0b Note: Most of this tutorial is still relevant for v2.1, which adds new code generation features and generates a dual release project using a ControlFactory structure. This tutorial demonstrates some of Habanero's features by creating a demo application in C#. You may want to build the application yourself as you read each section. It is usually best to start with the "Quick & Dirty" tutorial before doing this one. The source code for the full sample project is available for download at the bottom of this page. Included is the script for the MySQL database. Simply use the restore option in the MySQL administration panel to create the database on your system. Contents:
It's time for a customised system, and being an IT firm, we feel we have enough know-how to code our own system. So off we go, load up the coffee machine, crank up some rock music and pull out the SQL manual for the first time. We've decided to code the system in C# with object orientation, using the MySQL database to store the parts and sales data. MySQL is a relational database, which means it will be good for reporting. After getting "Hello, world" working and taking fifteen minutes to celebrate, it's heads-down for the real grind. And it all grinds to a halt. Having drawn up a plan of our object structure, we then sit down and map out the database table structure. It suddenly dawns on us: how do you reconcile the two? Surely there must be a simple way? And it sure is going to be a mission to write up all the database connection code and all the SQL statements to move the data back and forth between the database and the application. With a splatter of keyboard strokes and a tick-tick of mouse buttons, our browser calls up the Habanero architecture to solve all the world's problems ... well, except for global warming. The Problem The hunt is always on to find an easier, quicker, cleaner and more error-free way to achieve a programming task. Despite continued innovation, there are still some glaring efficiency gaps, such as the jagged interface between relational databases and object-oriented code. Habanero was developed by a group of software developers that ran into the continuous problem of having to provide the intermediate between object-oriented code and database tables. A pattern of usage developed and Habanero was packaged together with recurring functionality from the different applications. Furthermore, the architecture remained in use by the team for a long period before being released onto the market, considerably improving stability and ensuring that the software is relevant to real-world business needs. What sets Habanero apart from other flavours of persistence or object-relational mapping (ORM) solutions is the ability to generate user interfaces that are automatically mapped to the object as well. The implication is that you can build a functional database-dependent application with visual results in far less time, which is also great news for those interested in building prototype applications for their clients. Revisiting the Business Having decided on using Habanero, it's time to get this vehicle out of the garage and onto the information highway. We'll build the system incrementally, getting the basic structure up and running and then adding the bells and whistles as we go.
This is a more theoretical explanation of the Habanero framework. If you prefer a hands-on approach, you can skip this section and come back to it later. What Does Habanero Try to Fix? Habanero attempts to tackle two primary problems. Firstly, there are a number of repetitive steps required to copy values from relational databases, which are not object-oriented, to objects in code. Database connections need to be coded and managed, and the particular quirks of each database vendor need to be understood. Then Sql statements must be written for each type of action (create, read, update, delete) and for each property of each object. Concurrency checks need to be done, and some kind of mapping for inheritance and relationships must also be managed. In short, there's a vast amount of work covered in these actions, and what's more frustrating is that they are largely repetitive from project to project. Equally, making small changes to a database structure can result in a large amount of readjustment in the code as well. Secondly, there is considerable repetition in designing a user interface and mapping each control to a property, as well as ensuring that each property provides up-to-date values that reflect the database content. Of course, individual requirements for user interfaces can vary vastly, and it is unlikely that any one solution can automate most developers' requirements. However, Habanero does provide a large array of functionality to create full user interfaces or components of a user interface, and then map these to properties as the developer specifies. These properties are then kept up-to-date by the background engine as you use the components at run-time. Depending on your application, this can present a considerable time-saving. How Does Habanero Carry Out Mapping?
Habanero requires a set of class definitions that are contained in a single XML file. Here you specify:
Habanero comes with Firestarter, a GUI application that can be used to manage the class definitions for you. If you use Firestarter, much of the work in setting up and maintaining your mappings can be automated. You can simply start with an existing database structure, then use Firestarter to generate the class definitions from the database and then generate the code for these class definitions. You can of course work directly with the class definitions XML - the XML code used to achieve common tasks in this tutorial is included. Carrying out the process manually, you would need to handle three steps, in any order. 1) Design the database. 2) Create the classes. 3) Create the XML class definitions. With regards to creating the classes, all entity classes are required to inherit from Habanero's BusinessObject. For each property value that must be mapped, the property will need get and set methods that use a Habanero-provided method to carry out the persistence. The appendices contain the full API for the class definitions. What are Business Objects? Business objects are entities that in some way represent different components of business logic. For instance, in the manufacture of a car, the wheels, door, bonnet, seat and ignition all make up business objects. An email client provides more a more abstract example: an individual email, an address book, a blocked-senders list and a preference settings list could all be business objects. Essentially business objects are well used term in the IT industry to differentiate these kinds of components from other objects like widgets in a user interface or components of your database connection architecture. Habanero's BusinessObject provides a range of common functionality to its inheritors. Most importantly it hands down the tools to carry out persistence of the properties to and from the database. What About Sql? There is some debate at the moment on the wisdom of either hiding developers from the power of Sql or giving the developers open room to create errors through faulty Sql code. In Habanero, both sides are catered for to some extent. At a simple level, you will hardly write any Sql while using Habanero. Basic persistence to and from the database is handled by Habanero, which automatically constructs the Sql needed for each property value and adapts each statement to cater for the supported database vendor you specified. There are, however, times when you will need to use some level of Sql. At a simple level, you can place restraints on how many objects are loaded in a collection of business objects, and how these are ordered. These are provided using Sql criteria that will be appended to the Sql statements. Secondly, Habanero provides support for migrations, so that you can take an updated application to an outdated client installation, and the application will automatically update the client using the Sql statements you have provided. Finally, you can still execute a self-created Sql statement at any place in your code. You can construct the statement with a single string, or you can safely build a SqlStatement object out of components. Supporting Alternate Data Sources Habanero provides the structure to support data storage other than databases. For instance, testing can be done using an in-memory database that manages the data during the lifetime of the application. The current device is stored using the GlobalRegistry.DataAccessor property. Designing for Many UI Environments Habanero supports UI generation for forms and grids, so that developers can automate much of their user interface design. Even better, Habanero uses a control factory structure, so that each control is an instance of an interface and is created by a control factory. With this design, you need only change the control factory and you can convert your entire UI design to run in a different UI environment. Taking advantage Visual WebGUI by Gizmox, you can design one system to run either as a desktop application, or as an online web application. Visual WebGUI simulates Windows Forms design for the web environment, giving you the advantage of reduced development time and increased responsiveness.
System Requirements Habanero is a .Net product, so you will need to install the .Net 2.0 (or above) framework. You will also need to meet the .Net 2.0 minimum requirements. As far as application requirements go, this depends heavily on the type of application you will be developing. For client installations, you require Windows 2000 or above and will typically need a Pentium 4 1.6ghz, 256mb RAM for Windows 2000 or 512mb RAM for Windows XP. The minimum requirements for Windows Vista applications are not yet clear. Licensing & Installation You can obtain all editions of Habanero through the Download section. The download file is a zip that contains two MSI installation files, one for the Habanero framework with its DLL's, documentation and source code. The other installs Firestarter, the GUI application to manager the class definitions. The source comes with test classes for use with NUnit, which is a free testing framework - that way you will be able to check if any of your changes break other parts of the framework. Note that the source code is distributed under the GNU Lesser GPL, implying that you cannot sell the source code or any modifications to it. If you have fixed any bugs or made useful additions, it would be helpful to the community of Habanero developers if you posted the code on the Habanero forum. Some of this code may be incorporated into future versions of Habanero. More instructions on working with the source code can be found in the appendices. Files & Directories The Habanero installation will include the following folders:
If you will be using logging in your application, download and install the log4net DLL's - see Appendix II for more information. Once you have chosen the database vendor you will be using, follow the installation instructions for that database. Your Habanero-based application will need to reference the DLL's that come with that installation. Technical Support Chillisoft provides various types of support for Habanero. See the Support page for more information. There is also a brief Troubleshooting section at the end of this tutorial.
NOTE: You will find some sections with different markings: Auto - indicates that Firestarter is used to automate some tasks. This is the recommended route.Manual - indicates how you could achieve a task by working directly with the code or XML. This is for advanced use and you can usually skip these sections. Designing the Database At this stage you'll have chosen your database of choice and installed it. For this tutorial we'll be using the MySQL database. We open the database administrator and create a new schema called "replace_it". It's time for our first table, which will hold our computer parts. We'll call it ComputerPart - no surprises there. The first column is "ComputerPartID" which serves as the primary key. Code conventions for C# use PascalCase for naming, so it's easier if we use that same convention in the database too. Now there is some debate on primary keys and what format they take, but for our example, our primary key will be a Guid and will remain hidden from the user. In MySQL, a Guid can be represented as a CHAR(38) type.
Auto Auto-Generating the Class Definitions The class definitions are held in an XML file and are used to describe how the database maps to the objects in code. Habanero includes FireStarter, which is a GUI editor that helps you manage them visually. You can either set up the classes individually using FireStarter, or you can have them generated from the database for you. Before you open FireStarter, you will need to copy the DLL of your database vendor (eg. MySQL.Data.dll for MySQL) into the executable directory of FireStarter in your Chillisoft/Habanero installation. Once you've opened FireStarter, create a new project in FireStarter (through the File menu) called "Replace_it". Then go to the Generate menu and select "ClassDefs from Database". The following window should appear: ![]() First add a Connection string that specifies how to access your database. The Default Assembly indicates which project your class files will reside in. Pascal Case refers to the naming of the classes and Table Name Prefix can eliminate any prefix you have in front of your table (eg. if all your tables are tbsomething). Tick the two Populate options for now, so that we can generate user interfaces for the classes as well. Ignore the Show Primary Keys options and the Append options, which prevent a regeneration from adding columns or fields when you have previously chosen not to. Then Test the connection and Generate the definitions. ComputerPart should now be listed. If you have any error messages, check that your connection string is correct (port 3306 is often a good choice for a localhost server). Manual Writing the Class Definitions Manually The class definitions are held in an XML file and are used to describe how the database maps to the objects in code. A full list of the elements and options available are listed in Appendix I. Create an XML file called "ClassDefs.xml" and insert the following code.
Later on, once you've created your solution, you will need to add the XML file to your solution. This file must be copied to the output directory of your application. Right-click on the project containing your ClassDefs.xml file, go to Build Events and add the following Post-build command: copy "$(ProjectDir)ClassDefs.xml" "$(SolutionDir)Replace_it\bin\Debug\" By the way, you can use Firestarter to export just the XML class definitions file for you, rather than generating the whole project. Keep in mind that if you make local changes to your XML file and then export the class definitions from Firestarter, you may lose your changes. If you edit the XML directly, you may want to use Firestarter's import utility which will import an existing XML file. Auto Auto-Generating a New Solution When you are starting a new project from scratch, Firestarter can generate the new project for you as a solution (.sln) file that you can open in Microsoft Visual Studio. This will contain the assemblies, the business object classes, the launch classes and some additional utilities. If you already have an application and want to take advantage of the code generation, you can take one of two approaches. Either generate a new project and transfer your old code across to the new one, or generate a new project and transfer the classes from the new project across to your old one. The first option is better, because regenerating the code after making some changes will not require the manual transfer to be repeated. Code generation is done by Firestarter. Once you've set up your class definitions, simply go to the Generate menu and select "Generate Code". The following window should pop up: ![]() You might need to specify which Connection the new solution will be using and you'll need to set the Solution Directory. The Habanero DLL path only needs to be supplied if you want the application to reference a different set of DLLs to that which FireStarter is currently using. As you can see, four projects are created within your solution:
Once you've clicked Generate you can open the project by double-clicking on the .sln file (depending on your IDE). Add your database vendor's DLL to the references of the root project (eg. MySQL.Data.dll), and ensure that Replace_it is the default startup assembly. The project should compile and run successfully. Manual Setting up the Solution/Project Manually Fire up your standard IDE or text editor and set up a new solution. If you're using Visual Studio, add new projects as recommended above in the Auto solution. For this tutorial, there should be one Windows Application project and the rest would be libraries. First up we'll need to reference some DLL's that are important to our application. If you're using Visual Studio, right click on the References folder and "Add reference".
If you've created a new project in Visual Studio 2005, you'll see that two classes have been added already: "Form1.cs" and "Program.cs". If you're using a different IDE, go ahead and create two equivalent classes for consistency. We'll also integrate Habanero at this stage - notice the "using" references, the two lines at the beginning of the "main" method and the try/catch clause which provides a neat error message box.
Manual Configuring Database Settings The application needs to know how to connect to the database and this is done using the "app.config" file in Visual Studio (right-click on the project, "add new item" and choose "Application Configuration File"). Once the file has been added to the project, it should be amended to look something like that below. The options for vendor are "mysql", "access", "sqlserver", "oracle", "postgresql" and "sqlite". Additionally, you may want to add logging support to your application, which is a simple operation - see Appendix II on how to add Log4net to the configuration file.
Manual Adding Business Objects We'll need a new class for ComputerPart that will map to its database table. As with all such objects, ComputerPart inherits from BusinessObject. Here is the code for a simple version of the class that provides properties to manipulate the class's fields. Note that if you're wanting to vary this for your own use, you'll only need to change the class name where appropriate and add or remove the properties for each of the fields you want to display or amend in your database table. Just a small but important note here on properties. When you amend a property directly in the code, you will then need to call Save() on the object to have the changes persisted. Thus, you can make several changes or calculations and only save the changes when you are ready. Since we're working with database values which potentially have a null value, we use nullable types (with the ? on the end) - should you need to cast a nullable type to a non-nullable type, use ".Value".
Accessing Business Objects in Code Now that we've set the Habanero environment and have a working persistence framework, we have ready access to our business objects. Here is a sample set of code to load, edit and save a ComputerPart:
Note that the changes are not committed until you call Save on a BusinessObject or AcceptChanges on a collection. You can also group changes together under one Transaction - see the Wiki for more information.
We're about to explore a powerful feature of Habanero - generating user interfaces for the objects we are mapping. This works best when you have a class with several properties that behave fairly simply and just require information, such as contact details. When you have a complex user interface, Habanero can still generate most of the interface for you, saving you the time of mapping the controls to the properties and checking that the values supplied are valid according to the application's business rules. You can then add customisation on top of the generated controls. Multiple User Interface Environments A key feature of version 2.0 is the ability to design one application for multiple environments. This would have been unthinkable in the past, but with the launch of Visual WebGUI by Gizmox, Habanero now allows you to design your user interface independently of the environment, and you can switch between the environments simply by changing your configuration. The key to achieving this is the IControlFactory class. Whenever you want to create a control, like a TextBox, Grid or Form, simply call a method in the application's ControlFactory, which will create the right control and make sure it is customised for that environment. For instance, apart from the obvious namespace issues, one key difference between designing for the desktop and designing for the web is the amount of events that are assigned. If each event causes a call to the server in your web application, you can understand that Habanero ensures that controls for the web environment sacrifice some usability in order to reduce bandwidth and lag in your web application. The following diagram illustrates how controls are structured in Habanero, with "Win" indicating controls from System.Windows.Forms and "VWG" indicating controls from Gizmox.WebGUI.Forms. Most controls have a manager, which groups common logic used between all the variants of a control. The current ControlFactory is stored in GlobalUIRegistry.ControlFactory - there is a default control factory initialised for you when you run StartUp on HabaneroApp. In a moment we'll see how to create controls using your ControlFactory. Auto Managing UI Design in Firestarter Using FireStarter, you manage which properties to display under each class's "UIs" tab. You can generate grids and/or forms, and specify which control types to use to edit the data. When you generate the class definitions from a database, FireStarter can automatically create the UI definitions as well. The following screenshot demonstrates what would have been created for a grid display of computer parts: Manual Editing UI Class Definitions You might want to edit the UI class definitions directly in the XML. The result as viewed above can be added to your XML as follows for the ComputerPart class:
You can see from the UI section of this definition that a lot is assumed about the control types and the labels. See the Appendix on Class Definitions for more information on how you can control the layout and display of the different fields and columns. Managing Multiple Forms When you generate a new project with FireStarter, the main form is set as an MDI form, meaning that it can contain other windows within it. The one advantage of having several forms open at the same time is that when you switch away from a form, you should be able to return to it in its last state (unless you close it). Habanero facilitates this with the FormController class, from which we will inherit and adapt. First we'll create a form to show our computer parts, simply adding a new file in the .UI project. In order to merge this form into the form controller, it needs to inherit from UserControlWin and IFormControl. We'll add code to load the computer parts from the database into a collection of business objects and make them available to be read by the grid. At a later stage we'll want to use Sql criteria with the Load() method to limit just how many parts we display. We'll also instantiate a grid and add it to the provided form. There are some grid variants - this one is read-only and provides buttons to edit the selected row. Note that we use the BorderLayoutManager supplied with Habanero - this was created to cover for user interface shortfalls in .net.
You'll notice that when FireStarter generates code for a new project, a class is already created in the UI project to control the forms. You'll simply need to add each form you create to the structure already provided:
Finally, in ProgramForm (the master application form in the root project) we tie the form controller to the main menu so that the correct form opens and so that all currently open forms are listed under the Window menu. Note that in order to integrate with Habanero's UI structure, we change the form to inherit from FormWin rather than Form itself.
A small note here: if you prefer using the form designer in Visual Studio to handle your menus, simply remove the CreateMainMenu method listed above and use the designer to add the menu. When you double-click on a menu item, insert the "_formController.SetCurr..." code into the newly created method. Running the Application Everything is in place now and if we build and run the project, we'll have an editing window something like this:
You should be able to click on the "Add" button and the newly added object should appear in the grid immediately. If that doesn't happen then you will need to check your database setup, the app.config file, the class definitions or the ComputerPart.cs class. Check, for instance, that you've provided the correct field names. You may notice that the editing form doesn't have a title bar and can't be moved around. This can be amended in FireStarter by supplying a Title for the Form, under the "UIs" tab. Note that in this application we haven't included the ComputerPartID in the grid. This was partly a matter of choice and partly of principle. Regarding choice, a Guid is not as useful as a description when listing parts. Regarding principle, it's commonly held opinion that you shouldn't be changing your primary key, so we've designed the database to eliminate that possibility, keeping the primary key discrete. Of course, we will later add a unique reference number for each computer part, but that will be in a custom format rather than as a Guid, which is admittedly difficult to read and communicate. Our application is nicely set for expansion now, and you can see that it will be easy to scale it up as we add components. Let's try that in the next section.
The ComputerPart class is very limited at the moment - we don't even know how many parts we have in stock. We simply need to add a few more fields, but just how much work is this going to be? Change management is one area where object-relational mapping begins to shine. Rather than update a whole range of SQL statements, we can manage this process fairly smoothly. Updating the Database We'll need to begin the change process by updating the database. After adding some more important fields, our database structure now looks like this:
Auto Updating Code Automatically The process of updating your code is made easy with FireStarter. Simply carry out the generation steps again. Firstly, call up the menu option "Generate > ClassDefs from Database". Keep the same settings, except tick the two "Append" options, so that any new fields will also be assigned to your grid and form property lists. To update the code, regenerate the project using the menu option, "Generate > Generate Code", and keep the same settings as before. FireStarter will add the files that are missing and will update the business objects. Re-open the solution if you have added new classes and they don't appear. Note that each business object class is split into two partial files. The first file (eg. ComputerPart.cs) is where you can add your custom code, knowing that it won't be affected by a regeneration. The second file (eg. ComputerPart.Def.cs) holds the properties and relationships and will be replaced each time you regenerate. Manual Updating Class Definitions & Code Manually To update the class definitions, simply add the new properties and their types. "String" is the assumed default type and the other types can be named either by their wrapper class names (eg. Int32) or their standard-use names (eg. int). (see Appendix I for a list of data types in the class definitions). If you're generating user interfaces you will also add the new properties to your UI listings where appropriate.
The ComputerPart class also needs new properties to reflect the changes.
Check that the project compiles. If you get an error when you build at this stage, check for mistakes like wrong capitalisation, spaces between words and types that don't match up. Running the updated project should provide an editing form something like the following:
We're ready to introduce orders received by customers and orders sent to suppliers. It's easy to see that they have several things in common, making this a good opportunity to explore inheritance. Inheritance Strategies Inheritance is one distinct feature supported by object-orientation but not by relational databases. Habanero as an object relational mapping tool translates an inheritance structure to database tables in three different ways. The default "ClassTableInheritance" uses one database table per class in the inheritance structure. All fields that are inherited from the parent are actually stored in the database table of the parent, while the child table only stores its ID, the parent's ID and any fields that are unique to the child. "SingleTableInheritance" maps all fields of all classes of an inheritance structure into a single table. "ConcreteTableInheritance" uses a table for each concrete class in the inheritance hierarchy, so that the fields of an abstract parent are stored in the child rather. In our example, supplier orders and customer orders are very similar - they can both have an order number, name, price and delivery status. These properties can all be abstracted to a parent PartsOrder class that the user will never see. ("Order" is a sql keyword) Now to choose an inheritance strategy. Each one obviously has its pros and cons. The advantage of "ClassTableInheritance" is that we're not having to duplicate the fields of each inheritor in both the database tables and the class definitions - once they've been declared in the parent, they're inherited automatically. "SingleTableInheritance" places all the data into one table, which does reduce our control over the individual tables. "ConcreteTableInheritance" separates the data distinctly but does incur duplication. You can imagine that in a class with many fields, this duplication can add up to a lot of extra work. We'll go with the default for this example, although you may want to experiment with the other structures too. Note that the inheritance format discussed below is one of several types available. For more information on other ways of implementing inheritance, see the Wiki. Adding the Database Tables Here are the three database tables. At the moment we're keeping it simple and not giving the child classes any fields of their own. This is just temporary, and it's easy to imagine that differences will begin to creep in. Foreign keys are created here to prevent a parts order from being deleted while the child order still exists. Out of interest, there is no requirement for database foreign keys in Habanero, because Habanero can carry out deletion prevention itself. There are two advantages of adding foreign keys though - it helps FireStarter to detect relationships when generating the definitions and it prevents accidental deletions if someone is editing the database directly.
Auto Auto-Generating the Classes As you did in the "Managing Changes" section, use FireStarter to regenerate the class definitions. There are just some fiddly extra steps to take before you generate the code. FireStarter doesn't yet detect inheritance from a database schema, so go to the CustomerOrder and SupplierOrder classes, select Super Class and assign the PartsOrder class. Secondly, delete the PartsOrderID properties in the two child classes (since this is inherited). Finally, jump across to the UI definitions for each class and assign which properties you would like to display in those two classes. The multi-selector is an easy way of assigning the properties quickly. FireStarter correctly picks up that the Delivered property is a boolean, but will display a TextBox by default. We'd rather use a CheckBox, so switch over to the "UIs" tab and the "Form" tab, and change the Delivered control to CheckBox. Subsequently regenerate the code as you did before, which should create three new classes in your project. Re-open your solution if the new classes don't appear. Manual Adding the Class Definitions In the child class definitions, the "superClass" element is used to indicate inheritance and the PartsOrderID is left out because it's inherited. If you're generating user interfaces, you'll also need to include UI definitions for the child classes.
Manual Adding the Classes Manually
Thanks to inheritance, our two new child classes couldn't be simpler:
We could just create some user interfaces for these orders, but let's go a step further. Covering relationships will give us the tools needed to have a list of parts for each order...
Introducing Relationships Relationships have a range of uses, especially in providing content for combo-boxes and relating a group of items that belong to a parent. Order items are a perfect example - the interesting challenge is how to describe this relationship. In database design, you create the child class and include a field that refers back to the parent, whereas in code you would probably hold a collection in the parent class. Habanero effectively links both approaches. In addition to the relationship between a PartsOrder and OrderItems, we can also create a relationship between the OrderItem and the ComputerPart it represents. If we simply put a string for the part code field, we're opening the system up to errors. What if the part code changes format? It's far cleaner to change the part code field to an ID (in this case a Guid) that points back to the original ComputerPart table. The second advantage of this is that we can easily construct a ComboBox, so the user simply picks the part code from a list. The third advantage is that giving us the ID of the ComputerPart means we're one step away from all the other properties of that part, such as price and stock. Relationships are usually described as 1-to-1, 1-to-many or many-to-many. Many-to-many relationships require an intermediate table, which we won't discuss here (there's plenty of web material on this subject). What concerns Habanero is the immediate view from each class. In other words, does this relationship point to single or multiple objects? For a PartsOrder, a multiple relationship points to all the items in the order, and in an OrderItem, a single relationship points to the original order. Similarly, in an OrderItem, a single relationship exists to the ComputerPart and a multiple relationship points from the ComputerPart to all the OrderItems that use it. Just a note here on deletion prevention. Habanero provides three options for deletion prevention on multiple relationships only. Deletion prevention resolves the question of: what happens if someone accidentally deletes a ComputerPart that is being used in an OrderItem? The deletion check goes on the ComputerPart's relationship rather than on the OrderItem. Habanero thus poses the question to a multiple relationship: can I delete myself if someone is using me? This works the opposite way to foreign keys. The three options are: prevent deletion, delete related (eg. delete the order items if I get deleted) and dereference related (ie. in code, remove the reference so that the object will either continue to belong to something else that references it or be cleaned up by the garbage collector). Prevention is the default and that's our preference in this example. Adding an OrderItem Table Here is the table layout for the "OrderItem", including the two foreign keys:
Auto Regenerating the Code While you regenerate the class definitions, you need to watch that your previous changes are not overwritten. For instance, the CheckBox control for the Delivery boolean (discussed under Inheritance) may revert to a Textbox. Simply turn off the two "Append" options, to prevent previous UI definitions being appended to. FireStarter detects relationships from the foreign keys you supply in the database, so looking under the "Relationships" tab will show the relationships we have discussed. You can then regenerate the code as well, restarting Visual Studio if no OrderItem.cs class is visible. One last step is needed after generating the code - there is no way to know which PartsOrder an OrderItem is associated with, so we can specify that in the constructor. The constructor must not be added to the OrderItem.Def.cs stub, otherwise it will be overwritten in the next regeneration.
Manual Adding Class Definitions & Code Manually The new feature here is the "relationship" element. We'll add single-type relationships in the OrderItem class definition and multiple-type relationships on the other sides.
Also, add the multiple relationships for the definitions of PartsOrder and ComputerPart:
The OrderItem class will need an additional constructor to assign the ID of the parent PartsOrder and an additional method to return the parent order relating to an order item.
PartsOrder.cs will require an additional method to return the collection of its associated order items (between the quotes we specify the relationship name as defined in the class definitions):
Likewise for ComputerPart.cs:
At this stage the application should compile successfully. Developing the User Interface There are several ways to display the corresponding order items when you click on an order. One efficient way is to have two grids, so that clicking on the customer order grid will cause the order items grid below it to show the items for that order. The principle challenge here is to copy across the PartsOrderID when a new item is added, otherwise we have no way of knowing what the parent order is. The DefaultBOCreator creates a new object when we click "Add", so we'll simply replace it with our own, passing the parent object to the constructor. The overridden CreateBusinessObject will be called by the grid control just before the editing screen is opened.
We can now build one form to display all kinds of orders, by taking advantage of generics.
The form controller must be updated, and you can see here how to call the orders form.
Finally, update the ProgramForm with two new menu items to access customer and supplier orders. When run, the application should appear something like the following:
Note that typing a value into the part code field will crash the application, because it won't be a complete Guid. Just hang tight on that one - we'll resolve that in the next section by adding lookup lists and a combo box.
Habanero has extensive support for generating user interfaces and provides access to these controls so that additional behaviour can be assigned. In this section we'll look at some particular features available to the developer, along with techniques used to achieve common tasks. Adding Lookup-Lists in a ComboBox In the previous section we looked at how you can specify a lookup-list using relationships. A lookup-list is simply a set of options that the user can select from for a particular field, such as a list of countries or a list of address contacts. Habanero allows you to provide values for a lookup-list from a pipe-separated string (eg. gender options: "M|F") or from a database. These lookup-lists are defined under the property in the class definitions. Combo-boxes in a user interface are populated from three types of lookup-lists: BusinessObjectLookupList, which creates a list from a set of loaded business objects, DatabaseLookupList, which builds a list from a given sql statement, and SimpleLookupList, where a static collection can be specified in the class definitions. A lookup-list comprises a set of display-value pairs. The display part is shown to the user and the value part is the value actually stored. The BusinessObjectLookupList provides the display part from the class's ToString() method, while the DatabaseLookupList requires a value field and a display field in the select part of the Sql string. To implement a combo-box for the ComputerPart in OrderItem, we'll first amend the class definitions. In FireStarter, select OrderItem, change to the "Properties" tab, select ComputerPartID and go to the "Lookup List" tab, where you can select the list type as "Business Object":
You could also have chosen the "Database" type, in which case you would have provided a sql string as "select ComputerPartID, PartCode from computerpart order by PartCode". We also need to indicate that the control type to display the computer part will be a ComboBox. Set this under the "UIs" tab:
Finally, we'll need a revised ToString() method in the ComputerPart class which describes how to display an instance in the combo-box. Be sure to add this method in the ComputerPart.cs file and not the ComputerPart.Def.cs file.
This should now run successfully, with the string display showing correctly in the grids and the combo-box, but the Guid stored in the database. Fulfilling Orders Where the Delivered field is set to false, we can now dispatch or receive orders and automatically amend the stock quantities. One way of doing this is to add a button on each orders grid. Add the following code at the bottom of the OrdersForm constructor:
The DispatchClickedHandler and ReceiveClickedHandler are implemented further along. The parts stock is loaded and we check that all of the stock is available before changing stock levels. Note that we call Save() on the objects in order to persist the changes to the database, otherwise our stock will still be the same when we restart the application.
At this stage we have a basic stock management system. Using Habanero we've achieved this with very little contact with either the user interface designer or the database connections. Data Validation & Property Rules This is also a good time to introduce property rules (also known as validation). In our parts business, we obviously don't want a negative stock, so we can create a rule that prevents negative integers. A rule can be a combination of smaller rules, so that ranges can be created for instance. Using FireStarter, the rule can be assigned for the Stock property of ComputerPart. Similar rules could of course be designed for a wide range of numbers, such as the Cost and Price.
Better Number Control While our user interface is functional, it's not very robust, and an experimental user could quickly bring our application down to earth by entering strange characters into some of our controls. We can exercise some control over our numbers input by using NumericUpDown controls. The control types to be used in the user interface for the ComputerPart can be amended in FireStarter as before, except that the mapper type needs to be specified. The mapper is a Habanero component used to map data back and forth between the object and the UI control. When you specify the NumericUpDown control and leave the mapper as "Auto-Select", the mapper defaults to NumericUpDownIntegerMapper. This is fine for the Stock property, but for Cost and Price you should manually specify the mapper as NumericUpDownMoneyMapper, which assigns two decimal places. Filtering Grid Displays Quite clearly, our grid displays could grow rather large as we begin to add computer parts or customer orders. There are several ways to limit the number of objects being shown. We could specify load criteria as a parameter in each collection's Load() command. Another simple way is to add a filter control, which filters out the rows to display, so that only those rows that meet a requirement are shown. Looking first at the computer parts, how you filter your values will depend on how you distinguish your parts. For instance, Replace IT uses a part code made up of three letters which indicate the type of product and four numbers which serve as a sub-code for that type. For example, one of the optical mice might have a code of MOU1534. So our solution simply categorises items by their three first letters, doing a non-strict string comparison. The Habanero FilterControl comes attached with the GridControl, but can also be deployed independently. You simply need to assign a set of filter controls and specify which columns they filter out. The GridControl automatically detects when a filter control has been modified by the user and filters the grid immediately. Keep in mind that the this filter only works on a pre-loaded collection, so your grid will initially contain the full collection before filtering. In this example, the AddStringFilterComboBox method creates a combo-box that searches for all objects with that given string within the specified property. There is also a free-type TextBox filter to search on the Description field. Add the following code at the bottom of the ComputerPartsForm constructor:
This should create the following interface:
It would be nice to do something similar with orders, where we place a checkbox that allows us to show only those orders that are still to be delivered. However, the Habanero checkbox filter is a strict "true" or "false" check, and doesn't provide inclusivity. In other words, a ticked checkbox should show only "false" values, and an unticked checkbox should show both "true" and "false" values, not just "true" values. The better approach here is a custom implementation, to be added at the bottom of the OrdersForm constructor:
Updating Database Versions When you start producing applications for business and industry, you'll often find requests for new features and changes streaming in. Adding a new database field is a multi-step process. When it comes to changing the application, Habanero helps to simplify the process, so that you need only change the class definitions, the class implementation and the user interface (if not entirely defined in the class definitions). The result of those changes is a new set of EXE's or DLL's that can simply replace what the client has already. When it comes to database changes, however, you can't simply replace an entire working database. Usually, you'd have to manually run a set of Sql scripts to add or remove columns. Updating one database is manageable, but multiple installations can be both tedious and prone to error - if you forget the update, your application will crash. Habanero can automate this database update process for you using the IApplicationVersionUpgrader interface and the DBMigrator class, which will cause the application itself to update the database automatically, so you only need to replace the application. Database Versioning in Practice Back at Replace IT, we recently installed the latest version of the new system and it's been running smoothly. The databases have rapidly become populated with new entries. However, the accounts department have found the CustomerOrder form a little bit lacking - they're now asking for an address column. We have already covered the change management process earlier on, but we'll change the process a bit now to explain database version control. Instead of adding the database field and doing a class definition regeneration, simply add the new Address field straight into the class definitions for CustomerOrder (either manually or through FireStarter). If you're generating user interfaces, you'll also want to amend the UI definitions so that the Address is shown in the editing form for the CustomerOrder. Next we'll implement IApplicationVersionUpgrader in the root project, which will carry out a version check every time it runs. If the database version is out-of-date, it will carry out any migration instructions to get it up to speed. You'll probably need to add a reference to the Habanero.DB.dll in your project. Here is the new class:
To extend this for each version change, simply add more AddMigration() calls, and increment the version number each time. We'll need to point to this class right at the start of our application, by adding another line in Program.cs:
There's one final step: creating the database table that will store the version number. HabaneroApp supports both DatabaseSettings and ConfigFileSettings, but the latter can't be written to, so we'll need a database table called "settings", with the following design:
As soon as you've created the table, add a single row with the SettingName as "DATABASE_VERSION" and the SettingValue as "0". Our application should run now, and a quick check on the database should show a new field for the address. Running the application again should make no change.
In the previous section on User Interface Enhancements, we introduced the concept of a lookup-list. Basically, a lookup-list provides a set of options for a control like a combobox to display. Sometimes your lookup-list will be a fixed list that doesn't change, as in the months of the year. Other times you'll want the options to be dynamic so that some administrator can edit or add to them over time, such as the countries of the world. Then again, you might allow the case where the list grows as the final users themselves add items as they need them, such as a list of customers. Because of its emphasis on UI generation, Habanero is well-designed to cope with lookup-lists. We have already discussed how to implement the lists, but you may also want to be able to manage all the lookup-lists centrally. Adding the Computer Part Types Once our collection of computer parts starts to grow, we'll need a way of categorising them. To achieve this we'll simply create a new dynamic lookup-list that stores all the categories in a table. Here's the simple database table design:
In ComputerPart, we'll need a new column for ComputerPartTypeID that will hold the primary key of the computer part type. A foreign key is useful to check data integrity and for Firestarter to generate the relationships.
Adding the Class Definitions Go ahead and generate the class definitions for ComputerPartType and ComputerPart. The three properties in ComputerPartType will be a Guid and two strings. For the ComputerPartTypeID in ComputerPart, set its lookup-list property to BusinessObjectLookupList. There should be two relationships: a single relationship from ComputerPart to ComputerPartType and a multiple relationship from ComputerPartType to ComputerPart. Also remember to add a grid UI definition for ComputerPartType. In ComputerPart, add the ComputerPartType in the form and grid - Firestarter should automatically convert the type to a ComboBox. NOTE: Be aware that with Firestarter not detecting inheritance, when you regenerate, the existing inheritance structure might become a little corrupted. You can either choose to auto-generate the classes and repair the inheritance structure, or you can manually create the ComputerPartType class, properties, relationships and UI changes in FireStarter. Either way, it's best to use FireStarter to manage your definitions and code generation rather than editing the XML yourself, since that will save having to edit both the XML file and the classes in the code. Adding the Classes When you regenerate your project, the new ComputerPartType class should appear. If not, reload Visual Studio. Importantly, you need to add a ToString() method to the ComputerPart class, which determines what string will be shown to the user in the ComboBox dropdown. Displaying the Name property of the type is probably best. Implementing a Lookup-List Editor Our changes are almost ready to run, except for the fact that there are no computer part types available to choose from. We could easily build a new form with a grid to add some types to choose from, but you can imagine that we'd need a new form every time we add a new lookup-list. If you've had any experience with applications like these, you'll know that you use lookup-lists for a wide range of options, including customers, countries, cities, genders and many others. A cleaner way of managing all your lookup-lists centrally is to use Habanero's StaticDataEditor, which provides a treeview on the left with all the available lookup-lists, and an editable grid on the right which allows the users to add new items. The implementation is really simple:
As you can see, it's easy to add new types to this editor - simply use the AddItem method. As before you'll need to add this form to the Form Controller and include a main menu item to access it.
Adding Options on the Fly Habanero also provides a feature where users can right-click on a ComboBox to add options immediately. This service requires a UI form definition to have been created in the XML class definitions.
XML Class Definitions This section covers a list of explanations, possible options and limitations for all class definitions as listed in the class definitions XML file (usually called "classdefs.xml"). The class definitions are required in order for Habanero to run, and you need to include the definitions for at least one valid class. This is a sample structure for a class definition file. Note that the order of elements is enforced as per the Document Type Definition (DTD). See the specifications further along for specific options and limitations.
Here follows a list of all the class definition elements. For clarification, "attribs" means attributes and the default settings are in italics. Keep in mind that XML elements are case-sensitive.
Log4net is a system used by Habanero for logging output from your application. Log files give you assistance in several ways, including analysing system faults remotely, tracking system usage, tracing the process that led to a breakdown and understanding where the system failed when you don't have any other visual response. Habanero logs output to this logging system, and you can use logging calls in your code to add to this output. Log4net is highly flexible, allowing you to specify the log output medium (eg. text file), the output format and the output detail level (eg. errors only, or all debug information). Setting Up Logging Support The settings are laid out in the app.config file (or your equivalent config file) between the configuration tags. First declare the "log4net" settings type in "configSections" as follows:
Then specify the "log4net" settings - here is an example which you could use:
The settings above specify the log output file name, that the existing file will append new data, the output format and that all output of type "INFO" or more serious will be logged. "DEBUG" is the lowest level and will display all types of logging output. Using Logging in Your Own Code To use logging in your own code, you'll need to setup a logger in each class where you want to send logging output. Include "log4net" as follows:
Then add the logging object to your class fields like this:
Finally you can send log output anywhere in the code of that class like this:
For more information on log4net, visit the
homepage at: http://logging.apache.org/log4net User-Friendly Error Form The HabaneroAppWin class has an ExceptionNotifier property, which allows you to specify a standard form to display error messages (do this before calling Startup). This form must implement IExceptionNotifier. By default, Habanero supplies its own error display form, which hides the messy details from a final user, but still allows a developer to view the stack trace. While the form will automatically popup during the program launch, during normal run-time, you will need to enclose the code in a try-catch block. Here is a sample implementation in Program.cs, the root class.
And in the Main() method:
You can also mask the exception so that naive users aren't hit with the full details of a complicated error message:
Included with the Habanero download is the source code for the framework. Source is included for the Habanero components, but not for FireStarter. All the source code components can be reached from the "Habanero.sln" solution file. Licensing The source code is distributed under the GNU Lesser GPL. The full license is included with the installation, or you can access it at http://www.gnu.org/licenses/. Under this license, any changes you make to the source should be accessible to others and you may not sell the code you have modified. We recommend that you post changes or bug fixes on the Habanero Forum for the benefit of other users. The Habanero development team may in turn include those changes in future releases of Habanero. If you do make changes, you can include your name in the copyright listing at the top of the file where the changes were made. Running the Tests Habanero includes a set of unit tests that can be run to make sure your changes don't break existing code accidentally. Make sure the tests run successfully before you make changes, and then run them again after each change.
Troubleshooting FAQ
(this list is a work in progress)
Please send tutorial feedback to
habanero@chillisoft.co.za.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
![]() |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
![]() |
![]() |