|
introduction get started articles manual tutorials reference docs case studies knowledge base goodies screenshots demos echoes Carousel zone home forums downloads bugs mailing list |
XUI Zone - articles
Sudoku with XUI
Introduction In this article we are going to use the XUI toolkit to create a demo application which is a little more entertaining than the usual business type applications in the XUI documentation to date. While the end result is a pretty frivilous game, what is important is that it illustrates many of the techniques which are available in XUI and that we have a fully functional and portable application which can take care of creating, saving and opening its own projects. The application described in this article makes use of several aspects of the XUI framework and only snippets of code which serve to illustrate those aspects are listed. The application can be run as an applet embedded within an HTML page or from the same codebase as a Java webstarted application. The webstarted application provides more functionality from it's menus but these could be built into the applet if necessary. This application uses the AWT widget set so that it can be easily run as an applet with the plugin or the microsoft JVM but could it just as easily use Swing. The full source code can be downloaded at the end of the article. Introduction to Sudoku Sudoku is a simple game of logic in which the player is presented with a partially filled 9 X 9 grid of numbers. The main grid is again split into 9 sub grids, each of which contains 9 cells which must contain each number from 1 to 9. In addition, each row and column must also contain all of the numbers from 1 to 9 and no number can be repeated in a row or column. Figure 1 shows a screenshot of the application which has loaded a predefined game. You can get more information on Sudoku at http://en.wikipedia.org/wiki/Sudoku
Figure 1 - A Sudoku puzzle
Create the grid components With the component registration mechanism in XUI it is possible to declare named custom components which can be used in page XML declarations or directly from Java with component factories. The grid page conains a panel which in turn contains nine distinct grid components. If we look a the components.xml file we can see the declaration of the Grid, GridLabel and PopupGrid components as shown in listing 1.
Listing 1 - components.xml
The Grid component has some function declarations set such as ID, GridStyle, GridEntryStyle etc. If we look at a snippet of the Grid class in listing 2 we can see some of the function calls.
Listing 2 - Grid.java
package net.xoetrope.sudoku.comp; public class Grid extends XPanel { String id; XStyle gridStyle; public Grid() { rootMdl = ( XBaseModel ) XProjectManager.getModel(); fact = new XStyleFactory( XProjectManager.getCurrentProject(), "net.xoetrope.awt" ); labels = new GridLabel[9]; } public void setID( String gridID ) { id = gridID; } public String getID() { return id; } public void setGridStyle( String style ) { gridStyle = XProjectManager.getStyleManager().getStyle( style ); } ... } This component can now be constructed from the gridpage.xml file as shown in listing 3.
Listing 3 - gridpage.xml
The nine grid components are laid out within their parent panel and each is given an ID from 1 to 9. The component declarations contain the attributes which were set in the components.xml file. That takes care of building the main grid declaratively. Now the nine cells within each of the grids will be constructed directly from the Grid class by using an instance of the XStyleFactory as shown in listing 4.
Listing 4 - Grid.java
XStyleFactory fact; public Grid() { rootMdl = ( XBaseModel ) XProjectManager.getModel(); fact = new XStyleFactory( XProjectManager.getCurrentProject(), "net.xoetrope.awt" ); labels = new GridLabel[9]; } public void addLabels( XPage page ) { fact.setParentComponent( this ); int x = 0; int y = 0; int count = 0; for ( int i = 0; i < 3; i++ ) { for ( int j = 0; j < 3; j++ ) { labels[ count ] = ( GridLabel )fact.addComponent( "GridLabel", x + 1, y + 1, squareSize - 1, squareSize - 1, "", null ); labels[ count ].setID( count + 1 ); labels[ count ].setGridID( id ); labels[ count ].setStyleFactory( ( XStyleFactory ) page.getComponentFactory() ); labels[ count ].setGridEntryStyle( gridEntry ); labels[ count ].setGridFixedStyle( gridFixed ); labels[ count ].setGridPossibleStyle( gridPossible ); x += squareSize; count++; } x = 0; y += squareSize; } } The XStyleFactory.addComponent call can be made with the name of the component which is declared in the components.xml file. In this code the Grid passes the id and the styles to each of the GridLabels. That takes care of constructing the custom components declaratively and from Java code using the XStyleFactory. Binding events to components In this application the events are added from the Java code as the components are created. This is just one way of adding events. They can be declared in the page XML along with the component declarations removing more code from your application in the process. Each of the labels in the grid needs to have a MouseHandler event attached so that the PopupGrid component which was shown declared in the components.xml file earlier will be shown as in figure 2.
Figure 2 - The popup grid
In order to do this we pass the page to the grid and it can bind the events to the labels as they are added as shown in listing 5.
Listing 5 - Grid.java
public void addLabels( XPage page ) { ... setBindings( page ); } public void setBindings( XPage page ) { for ( int i = 0; i < 9; i++) { page.addMouseHandler( labels[ i ], "labelClicked" ); } } The addLabels function is called from the GridPage XPage which needs to have the labelClicked method declared as shown in listing 6.
Listing 6 - The labelClicked method of the GridPage class
public void labelClicked() { MouseEvent me = ( MouseEvent ) getCurrentEvent(); if( wasMouseClicked() ) { activeLabel = ( GridLabel ) me.getSource(); } } The getCurrentEvent of the XPage returns a reference to the event which triggered the method call and it can be cast to the appropriate event type in order to check it's state. This is just one example of the events which need to be created. The events are created directly from your Java code as shown or they can be added as declarations in your XML pages. Binding the labels to the model Again the binding are added dynamically from the Java code as the components are added. These bindings can be added in the page XML declaration if the components are on a fixed page. Each of the numbers in the grid which are shown in the screenshots so far have been bound to their own specific XModel node. In order to illustrate that structre quickly have a look at listing 7.
Listing 7 - The model structure
This XML shows that the labels are bound to nodes in the format grids/(gridid)/(labelid). These bindings are setup in the Grid class as shown in listing 8.
Listing 8 - Setting the model bindings
public void setBindings( XPage page ) { for ( int i = 0; i < 9; i++) { String path = "grids/" + id + "/" + (i + 1); XTextBinding binding = new XTextBinding( labels[ i ], path ); page.addBinding( binding ); page.addMouseHandler( labels[ i ], "labelClicked" ); } } This code shows how each of the 81 labels are bound to the XModel using the simple XTextBinding class. Now in the numSelected method of the GridPage class the activelabel's XModel needs to be updated with the value of the selected number as shown in listing 9.
Listing 9 - Updating the model with the selected value
public void numSelected() { if ( wasMouseClicked() ) { MouseEvent me = ( MouseEvent ) getCurrentEvent(); String num = ( ( XLabel ) me.getSource() ).getText(); setModelValue( num, true ); repaint(); } } private void setModelValue( String value, boolean clear ) { XBaseModel mdl = ( XBaseModel )rootModel.get( "grids/" + activeLabel.getGridID() + "/" + activeLabel.getID() ); popupGrid.setVisible( false ); mdl.set( value ); XDataBinding binding = getBinding( activeLabel ); binding.get(); activeLabel.repaint(); } The setModelValue retrieves the appropriate node from the XModel and sets it value. Next the binding for the component is retrieved and is updated and the component is repainted completing the process. Now what is of most importance here is that the application code isn't updating the component directly, but is working directly on the model making sure that it is always in a consistent state. This is one of the most important aspects of XUI – it's ability to separate the view from the model. Once a game has been setup it is possible to save it for use later or to proceed into the game and play it. Once the user proceeds the numbers which were entered in the setup stage are fixed and cannot be changed. These are represented by the grey numbers and the coloured numbers are the ones which have been entered when the game is in play mode as shown in figure 3.
Figure 3 - The fixed an changable numbers
The fixed and non-fixed XModel nodes can be seen in the game XML in listing 10, the fixed numbers having a type attribute of fixed.
Listing 10 - Setting the fixed and non-fixed numbers
The setModelValue function takes care of setting the fixed attribute as shown in listing 11.
Listing 11 - The setModelValue method
private void setModelValue( String value, boolean clear ) { XBaseModel mdl = ( XBaseModel )rootModel.get( "grids/" + activeLabel.getGridID() + "/" + activeLabel.getID() ); mdl.set( value ); XBaseModel modeMdl = ( XBaseModel )rootModel.get( "temp/gridmode" ); if ( modeMdl.get().equals( "setup" ) ) XModelHelper.setAttrib( mdl, "type", "fixed" ); } The game of Sudoku is as much about establishing where the numbers cannot go as where they can and this is helped by being able to mark the grids with possibilities as shown in figure 4.
Figure 4 - Showing possible numbers
These numbers are selected by clicking the grid and then right-clicking the PopupGrid. These numbers are added as child nodes to the XModel nodes as can be seen for grid 2 in listing 12.
Listing 12 - The model with possibilities listed
Saving and opening games The persistence of the model in this section can be done just as easily over a http connection using Carousel. The most important thing is that once the model is updated and not the components the model can be saved and restored as needed. This mechanism can also be applied to subsections of the model. The MVC support which is provided by XUI is very useful in allowing developers to create well structured and maintainable applications and this provides the developer with a very useful and easy to use side benefit. That is the ability for projects to save and restore their model state with relative ease. The welcome page contains menu items for saving and opening files. The save code is shown in listing 13.
Listing 13 - Saving the game model
public void saveGame() { try { JFileChooser chooser = new JFileChooser( System.getProperty( "user.dir" ) ); int ret = chooser.showSaveDialog( this ); if ( ret == JFileChooser.APPROVE_OPTION ) { String path = chooser.getSelectedFile().getAbsolutePath(); FileOutputStream fos = new FileOutputStream( path ); OutputStreamWriter osw = new OutputStreamWriter( fos, "UTF8" ); BufferedWriter bw = new BufferedWriter( osw ); XDataSource.outputModel( bw, ( ( XModel ) rootModel.get( "grids" ) ) ); bw.flush(); bw.close(); } } catch (Exception e) { System.out.println( "error" ); } } This code is all that is needed to output the model to the file selected by the user and is in the format which has been seen in the listings throughout this article. The format is simple XML and as such is portable meaning that it can be used for exchanging information easily between applications. In this application, for example, the games could be setup by the game writer and published for players of the game to use. In order to reopen a saved game the code shown in listing 14 is used.
Listing 14 - Opening a saved game
public void openGame() { ... Select the file and load its contents into the contents StringBuffer StringReader sr = new StringReader( " Styling the components XUI Stylesheets allow you to create different skins for your applications allowing them to be rebranded easily without having to touch the Java code. The screenshots of the applications throughout this article have show the application using various colour schemes and this is achieved easily through the use of styles. Listing 15 shows a snippet of the stylesred.xml file.
Listing 15 - The styles.xml file
If we look first at the grid style we can see how it is used in the grid component in listing 16.
Listing 16 - Paint the grid
XStyle gridStyle; public void setGridStyle( String style ) { gridStyle = XProjectManager.getStyleManager().getStyle( style ); } public void paint( Graphics g ) { super.paint( g ); int y = 0; if ( gridStyle != null ) g.setColor( gridStyle.getStyleAsColor( XStyle.COLOR_BACK ) ); for ( int i = 0; i < 4; i++ ) { g.drawLine( 0, y, getSize().width, y ); y += squareSize; } int x = 0; for ( int i = 0; i < 4; i++ ) { g.drawLine( x, 0, x, getSize().height ); x += squareSize; } } As we saw earlier the style name is passed into the component from its XML declaration and the XStyle is created from it. The paint method then retrieves the back colour of the style and uses it to paint the grid. It's as simple as that. Now we can change the style in the styles.xml file or swap the styles file depending on what styling we want. While we're at it we can look at how the individual cell labels use the styles to paint the numbers. The GridLabel paint method is shown in listing 17.
Listing 17 - The GridLabel paint method
public void paint( Graphics g ) { String type = XModelHelper.getAttrib( mdl, "type" ); XStyle style; if ( allowEntry() ) style = gridEntry; else style = gridFixed; if ( highlighted ) g.setColor( new Color( 240, 240, 240 ) ); else g.setColor( style.getStyleAsColor( XStyle.COLOR_BACK ) ); g.fillRect( 0, 0, getSize().width, getSize().height ); g.setColor( style.getStyleAsColor( XStyle.COLOR_FORE ) ); g.setFont( XProjectManager.getStyleManager().getFont( style ) ); g.drawString( getText(), 12, 28 ); g.setColor( gridPossible.getStyleAsColor( XStyle.COLOR_FORE ) ); g.setFont( XProjectManager.getStyleManager().getFont( gridPossible ) ); int x = 2; for ( int i = 0; i < mdl.getNumChildren(); i++ ) { XBaseModel child = ( XBaseModel )mdl.get( i ); System.out.println( child.get() ); g.drawString( ( String )child.get(), x, 10 ); x += 8; } } This method needs to check the type attribute of the XModel node in order to find out if the number is fixed or not. It then draws the number in the appropriate colour. At the end of the function the possibilities are iterated and painted. The styles can be swapped at runtime from the style menu as shown in figure 5.
Figure 5 - The colours menu
Each of the submenus are setup to call the loadStyle method in the Welcome class as shown in listing 18.
Listing 18 - Resetting the stylesheet
public void loadStyle() { ActionEvent evt = ( ActionEvent )getCurrentEvent(); loadStyleSheet( "styles" + evt.getActionCommand().toLowerCase() + ".xml" ); } public void loadStyleSheet( String name ) { XPage page = ( XPage )pageMgr.getCurrentPage( null ); String pageName = page.getName(); pageMgr.reset(); project.getStyleManager().reset(); project.getStyleManager().load( name ); pageMgr.showPage( pageName ); } The command is retrieved from the ActionEvent and is passed to the loadStyleSheet function. The current page name is retrieved from the XPageManager and the XPageManager is reset unloading all of the cached pages. Then the projects XStyleManager is reset and is told to load the selected stylesheet. And finally the current page is displayed. This constructs the pages and creates the model bindings once again. But because the model has not been affected by the pages reloading the components will reflect the current state of the model. Summary I hope that this simple demo goes some way towards illustrating how XUI can be used to take some of the pain out of building Java client applications. But XUI isn't just about making it easy, it is more about creating a well defined and easy to follow structure for your projects. This leads to very significant code reductions and a more maintainable codebase. By the same token XUI never imposes a coding style upon the developer. If you want to do something outside the XUI framework Java is the language so it can be used whereever necessary. Using XUI to build this application not only provides a nice MVC separation but it also makes it very easy to save and restore the application state at any time. Not only this but the format is consistent and can be used seamlessly between XUI applications. Styling the application using an external stylesheet makes it possible to skin the application very easily by swapping the stylesheet files. These styles can not only be used in component declarations in XML but can also be used from your Java classes to style custom components. Binding events to callback functions makes for and easy and predictable way to setup event handlers. Declared custom components can be used very easily from XML page declarations as well as directly from your Java code. Run and download Please note that the application is provided AS IS and without warranty and is only intended as technology demonstration. The software remains copyright of Xoetrope Ltd. Launch as an applet by clicking here (opens in a new window) You can download three games for sudoku from here. Once downloaded select Open from the application menu and locate the game you want to play. The source code and netBeans project associated with this article is available to registered users... Click here to logon or register. comments
the sources of this project exist error, file : GridPage.java line : 24 -->"continueButton = ( XButton )findComponent( "continueButton" );" running in XUI2.0.7 will throw exception, it will be resolved after changing import class "import net.xoetrope.awt.XButton" into "import net.xoetrope.swing.XButton"
If you were logged in you could rate this article or add a comment. |