Open Modules – version 0.1 – Demo Release
Copyright (C) 2003 Hotsprings Inc.
Distributed under the GPL
Location: www.HotspringsInc.com
Description: This demo is a presentation of the controls available at this time in the hotsprings framework. Many more will be written as this project advances. We intend to add a sample of use for any new control that we deliver to this file . If you develop a control of your own and wish to share it please add a generic use case to this file.
class MyDialogMessage demonstrates a dialog filled with a single label to be used as a message box. Within the dialog the DoLayout function (a virtual in DialogWindow) is used to position the components within the container. Most windows dialogs are not resizable. We believe that although the relative position of components within the dialog can be predefined the total size of the dialog needs to be adjusted by the end user according to his/her needs.
class LaunchADialog is used to launch a dialog whenever a button in this demo is pushed. It inherits from Button::ActionListener and uses the notification to create a new dialog. Take this class as a sample of how to attach some functionality to buttons.
class MySkinBuildListener listens to skin creation completion message and triggers the creation of the main window. See file "CreateSkin.cpp" for an explanation of how we create skins for this demo.
About Listeners: As a general rule a listener is instantiated and added to exactly one broadcaster/server. Sometimes you can get away with creating one listener and then adding it to more than one broadcaster but this only invites for trouble since when the broadcaster is destructed it cancels all its listeners. As a result listeners registered to more than one broadcaster end up being canceled by the first deleted broadcaster. It is a bit hard from a common C/C++ perspective to create so many little listener objects each with such a small purpose ... It feels like an awful waste of resources. Please resist the temptation to "optimize" and just create as many listeners as needed :-) The design of the listener objects closely resembles the one used in Java. Java has certain language specific features that come to help the creation of these objects. The ability to create inner classes that act as transparent proxies to the outer class is unfortunately not available in C++ so we have to write a bit more code to achieve the same result. The relationship between the objects involved in listening goes something like this: somebody (the controller) creates a server object, a listener and a client. The listener is added to the server and the server owns the listener. The listener has a pointer to the client, it does not own the client. The client may own the server. So while a request is being processed the client owns the engine doing the processing, the engine owns the listener object and the listener is connected back to the client. If the chain needs to be canceled, the client deletes the server, the server deletes the listener, the listener disconnects from the client and the chain is safely broken. Some servers may work for more than one client, so the client may not delete the server but then it needs to keep partial ownership on the listener, and directly cancel it when no longer interested in notifications. The server can still invoke the listener's callback but since it is canceled the listener will no longer proxy the notification back to the client. One might ask why can't we just make the client itself be the listener to the server. There are several reasons:
1) Encapsulation: we do not want the client to be derived out of the listener and therefore expose the listener interface to the outer world. This interface can be made private but derivates of the client can still override some virtual method in the client and do unexpected damage.
2) Safety: since the client needs to be aware of the server and the server needs to be aware of the object it sends notifications to , it is very easy to end up with an ownership loop that keeps both objects alive indefinitely.
3) Flexibility: a client may use more than one instance of a server type and have more than one way to implement the listener interface. A dedicated listener object can be made to service each specific server or even specific server request.
4) Identity: The listener object gives an identity and storage space to the transaction being performed. While a dedicated listener object is alive it can store within its instance additional state information specific to the transaction in question. The additional information would require a vector or some other container and all the management functions if the client or the server were to keep it. Chances are the server does indeed keep such a container but the information kept within is specific to the server's needs. It is not desirable for the server to manage/handle/care about the needs and requirements of a specific transaction initiated by some specific client.
class DemoApplication is the main application class, triggers the creation of the skins, the main window and attaches all the created components together. Once the main window has been created the app no longer has an active role in the program but is guaranteed to be available during the entire execution cycle. The constructor of the application class is the first thing in the framework that should be called, the destructor the last. This way we structure the scope of all objects within the scope of the application. As a side note please do not create static variables that last longer than the application. Their destructor might call functions within aggregate objects that might internally be using the application as an "always available" entity or might use system resources that are allocated and released by the application. It is ok to have static pointers to objects but if you do please initialize those pointers after the app constructor and make sure they get deleted in a controlled manner before or during the app destruction. For an example of how this can be done please look at our own static objects created within "void MainEntryPoint()". The objects themselves are bound to the stack and their scope is limited to the function so their creation and deletion is under tight control. During their existence however we have access to these objects through static pointers initialized during the object's constructor and cleared out during the object's destructor. This is a variant of the "singleton/monostate" design pattern that does more than just provide a shared state but also ensures tight control over the objects life span. Some of the controls have a "DemoPopulate()" method. This is for demonstrations and debugging, remember to remove these calls in your final projects.
Anatomy of "CreateMainWindow()": First you need a "WinMgr" object, there can be only one of these in your app. It manages all the windows you create and the interaction of those windows with the operating system. Also stores all the system color access functions. It is the embodyment of the system's desktop/GUI framework. One would normally create such an object in the constructor of the application. A window can be created as a child of "WinMgr" or as a child of another window.
Once a window has been created a set of properties need to be configured:
- the title of the window (SetTitle)
- the position of the window on screen (SetBounds, SetContentBoundsOnScreen)
- the baground color of the window (SetBgStyle)
- what to do when the window is closed (AddWindowListener, SetWindowClosesApp)
The "SetWindowClosesApp" function installs a predefined listener to the window, with the single purpose of translating the "window closed" notification into a "quit application" event. As a result the closing of that window will determine the shutdown of the entire app. This is very common behaviour for many apps but it is by no means the only way to shutodown applications. If you need custom behavior, just install your own window listener and proceed with some other logic to generate the shutdown for the application.
A window is also a view continer, so just like any other view can accept children views to be added to it. To populate a window call "AddView". By default if only one view is added to a window that view will fill the entire content of the window and you need not add any logic to resize the view during the resize of the window.
If your window is a dialog window you need to override "DoLayout" to reposition the children within the window's content. Otherwise you need to add a window listener and implement your own "BoundsChanged" handler. We plan to make this much better in the near future by adding layout managers and the ability to install them into any view container. In this example we populate the window with one large tab view. For the TabView there are few things to configure. Just set the font for the text in the tabs, the skin to render the tabs and the tab bar and add the tabs themselves with "AddPage". The "AddPage" function takes 2 parameters: the name of the page and the view associated with that page. Pages within the tabview are automatically resized to cover the entire content of the tab.
ImageView: use this control to show pictures, (only the PNG decoder is available at this time, more will be added shortly). If you already have an image in memory you can simply instruct the imageview to show it with "SetImage", if you only have a filename image view can be instructed to load the image from that file and show it as soon as it becomes available.
Buttons: create instances of class "ButtonView". Set the bounds for that specific view. Set the text to be shown on the face of the button, the font is has to be rendered with, teh alignement for the text within the button bounds. You can use GetPreferredSize on the button once you have configured its content and skin. Don;t forget to add the button to the parent container. To make the button have a "purpose" you should also add an action listener to the button and write a handler for the action. In this example we add an action listener that launches a dialog window with a message.
ComboListView: it is an edit line with a dropdown list box. Both the edit and the listbox can be configured independently. Make sure to configure the skin for the combo with both the skin for the text and the skin for the child list box. To see how you can do this check the "CreateSkin.cpp" demo file.
ScrollBar: the scrollbar can be configured with a parameter, parameter range, parameter page size. The last of them , "parameter page size" should be a value within the parameter range and it is used to calculate the size of the thumb in the scroll bar. You will rarely need to create scrollbars independently. Instead you can use the scrollpage/scrollview combination. The scroll view is a container that has the ability to scroll its children. The scroll page is a container that parents one such scrollview and 2 scroll bars. Certain views like the textview, already have the ability to scroll their content and therefore can be directly added to a ScrollPage.
|
|
|