Monday, December 27, 2010
LWUIT Resource Editor Tutorial Part 8: Splash Screen Revisited
In this part of the LWUIT resource editor tutorial I go in depth into details of how to create a photoshop like layered UI and make it animate for complex UI constructs that can adapt to different resolutions more effectively.
Thursday, December 23, 2010
LWUIT mailing list down
As part of the changes to the java.net site the users mailing list echo emails are bouncing since they aren't recognized by the spam filter. I hope this problem gets resolved soon, in the meantime please use the LWUIT forum instead of the mailing list.
Wednesday, December 22, 2010
LWUIT Resource Editor Tutorial Part 7: Localization And Internationalization
In this part of the resource editor tutorial I cover how a user can localize a LWUIT application especially one built with the UI builder but generally this logic can be applied to any application. I also show off some new UI builder features for localization. Specifically the ability to fetch all the keys from the UI and edit localization data directly in the builder tool.
Thursday, December 9, 2010
A Better Way To Blackberry And Misc Announcements
In the past I posted some instructions on how to get LWUIT working on the blackbberry with the Netbeans IDE's using bbant. In my usual fashion these were somewhat complex instructions that caused quite a bit of grief.
There is a better way! Chen came up with it for the 1.4 release, I originally discounted it since I tried something similar which didn't work. I now tried it again and understood that the methodology allows for something much simpler:
Besides this I was asked to post about a couple of upcoming events specifically JavaOne Beijing & Brazil both of which seem to contain LWUIT related talks although I'm not sure who will be giving them. In Beijing specifically there are:
Rich Applications and Services for the Mobile Masses (1265)
There is a better way! Chen came up with it for the 1.4 release, I originally discounted it since I tried something similar which didn't work. I now tried it again and understood that the methodology allows for something much simpler:
- Install the desired JDE versions (we recommend 5.0 for touch and 4.2 if you need legacy support, notice that you should install both if you want both!).
- Install the Netbeans blackberry plugin from here.
- Select the RIM platform using the standard add platform button (the manage emulators button in the platform tree node in the properties dialog). That's it if you just want to write MIDlets!
- To write applications (which is where I failed last time) you need to fix the build-impl.xml which incorrectly generates a MIDlet. To prevent your changes from being overwritten you can just paste them into your standard build.xml.
Open your build-impl.xml (within the nb-project dir) search for the target "create-cod" and copy the entire target to your build.xml.
Replace the <arg value="-midlet"> argument with <arg value="-cldc">
Besides this I was asked to post about a couple of upcoming events specifically JavaOne Beijing & Brazil both of which seem to contain LWUIT related talks although I'm not sure who will be giving them. In Beijing specifically there are:
Rich Applications and Services for the Mobile Masses (1265)
Introducing Java TV Widget Development (2685)
Writing Stunning Cross-Platform Applications Using LWUIT (1245)
Which are all LWUIT related (the last of which seems to be Chen's J1 session).
If you are a developer in China we would appreciate some application information/screenshots (sent to out lwuit at sun dot com address), there is some push within Oracle for LWUIT in China and our managers have specifically asked for such applications.
Wednesday, December 8, 2010
LWUIT Resource Editor Tutorial Part 6: Lists & Renderers
In this part of the tutorial I go over the basic ideas behind lists & cell renderers. Lists in the GUI builder have most of the power that they have within LWUIT proper including renderers and models, with some simplifications in place.
Monday, December 6, 2010
LWUIT Resource Editor Developers Tutorial Part 1
This is a different kind of tutorial for the resource editor designed for developers who want to integrate their code with the resource editor's output. There will be additional parts for this tutorial as well as additional parts to the standard resource editor tutorial.
Wednesday, December 1, 2010
LWUIT Resource Editor Tutorial Part 5: Ratios 3
In this third part of the tutorial I essentially complete the demo (minor tweaks can be made but they should be pretty obvious). I also show off a somewhat modified UI for theme creation in the resource editor that makes it somewhat easier to navigate elaborate themes.
I also demonstrate some of the latest features to drop into the GUI builder such as multi-selection and in place editing.
While this concludes the initial main demo tutorial I still intend to keep up with producing further tutorials for the more advanced functionality as well as for the other parts of the tool such as localization.
Sunday, November 21, 2010
LWUIT Resource Editor Tutorial Part 4: Ratios 2
In this second part of the ratios tutorial I go on with customizing the result to produce something that's closer in resemblance to the final result and pretty much finish the first two forms. The last remaining form is the tabs form in the demo.
Notice that the ratio application UI is designed for the iPhone hence it has no focus behavior or anything of that type. A real world application targeting devices that support none-touch input as well would also add selected styles to the mix.
In part 3 I hope to conclude the demo although I might use the ratio demo some more to demonstrate some features of the resource editor such as localization and some more advanced themeing/animations.
Wednesday, November 17, 2010
LWUIT Resource Editor Tutorial Part 3: Ratios 1
In this part of the LWUIT resource editor tutorial I demonstrate how I created the Ratio's demo based on Michael Ruhlman's excellent Ratio iPhone/Android applications (more info at http://ruhlman.com/).
This demo shows how one can take a real world application and replicate its UI/functionality rapidly in LWUIT to get it running on all devices.
This tutorial is divided into several parts due to the video length limitation of Youtube and to make it easier to watch in parts.
I'm working on transcribing the videos since my accent is problematic for many people, it takes me a very long time to transcribe though so if anyone can do this fast it would be helpful (the automatic tools don't understand me either...).
Wednesday, November 10, 2010
Second Part of The New Resource Editor Tutorial
So far so good, another part of the resource editor tutorial has made its way up to youtube.... In this part I try to take a step back and cover some basics of what is a theme and how to apply it, I go over some of the basics of what is a selector and how to determine a value for it.
Thursday, November 4, 2010
New URL for the LWUIT Forums
The LWUIT forum was down for a while this week and went up with past links being broken.
All the messages are still there under the newer version of the forums, you can find the new forum here.
All the messages are still there under the newer version of the forums, you can find the new forum here.
Wednesday, November 3, 2010
New Resource Editor Tutorial Part 1
I hope to make this a weekly tradition for the next couple of months until we have a respectable set of videos covering all the features of the new resource editor in detail. The first video is just a warmup showing off how quickly one can create a hello world UI with the resource editor.
Saturday, October 23, 2010
Tips About Compatibility Issues With My Latest Commits
I just committed some major refactoring to the LWUIT code base the other day, I changed the way Dialogs work so the style of the Dialog UIID actually surrounds the "dialog" as one would expect... This works really well but is highly incompatible since the change required quite a bit of hacks. I also moved the alignment attribute into the Style class which doesn't seem like something that would break compatibility but it does.
If you are running into problems with your existing code try the following:
You can use the old Dialog logic by invoking Dialog.setDialogTitleCompatibilityMode(true);
This is mostly intended as an intermediate solution until you can fix your theme/code.
If you used Dialog style manipulations by changing the Dialog's content pane this will no longer work as expected! You will need to use the Dialog.setDialogStyle/UIID() methods instead.
If you find that alignment is broken for some use cases its usually triggered by changing the style after setting alignment. The solutions for this are to either define the alignment in the theme or just move the alignment setting code to a point after you change the style e.g.:
cmp.setAlignment(LEFT);
cmp.setUIID("SomethingElse");
Will replace the style component in the component and break its alignment, you need to make sure this doesn't happen.
If you are running into problems with your existing code try the following:
You can use the old Dialog logic by invoking Dialog.setDialogTitleCompatibilityMode(true);
This is mostly intended as an intermediate solution until you can fix your theme/code.
If you used Dialog style manipulations by changing the Dialog's content pane this will no longer work as expected! You will need to use the Dialog.setDialogStyle/UIID() methods instead.
If you find that alignment is broken for some use cases its usually triggered by changing the style after setting alignment. The solutions for this are to either define the alignment in the theme or just move the alignment setting code to a point after you change the style e.g.:
cmp.setAlignment(LEFT);
cmp.setUIID("SomethingElse");
Will replace the style component in the component and break its alignment, you need to make sure this doesn't happen.
Thursday, October 21, 2010
The UI Builder Class: How to actually use the LWUIT GUI builder
In my previous GUI Builder post I was a bit vague mostly because I was doing it in a rush in a day of some serious major commits to LWUIT's SVN, I didn't have that much time to post and when I do get a chance to write something there is so much new stuff I'm just overwhelmed with what to write about... (FYI allot more is coming really soon!).
Please notice that when you use the LWUIT GUI builder and its related API's that its alpha level software likely to break and fail in interesting ways. I did my best to prevent a resource file corruption but I strongly suggest to constantly backup the resource file while working (and please file issues if you run into them!).
The UIBuilder API is also in alpha form and might change significantly although I tend to believe we have the right direction there...
The premise is this: the designer creates a UI version and names GUI components, he can create commands as well including navigation commands, exit, minimize. He can also define a long running command which will by default trigger a thread to execute...
All UI elements can be loaded using the UIBuilder class. Why not just use the Resources API?
Since Resources is essential for using LWUIT, adding the UIBuilder as an import would cause any application (even those that don't use the UIBuilder) to increase in size! We don't want people who aren't using the feature to pay the penalty for its existence!
The UI Builder is designed for use as a state machine carrying out the current state of the application so when any event occurs a subclass of the UIBuilder can just process it. The simplest way and most robust way for changes is to use the resource editor to generate some code for you (yes I know its not a code generation tool but there is a hack...). The code you need to write to process the UI includes some boilerplate code which is pretty mundane for processing commands based on id's or processing an event from a component, to simplify this process I added to the resource editor two menu items, the firs is simpler: "Help->Show Source For Using". The second is far more elaborate and I will try to go over it and its output bellow: "MIDlet->Generate UI State Machine".
When running the Generate UI state machine menn option in a project with UI builder content it prompts you for a directory/file name for the output. Just give it a new file name in your project directory and it will create a UIBuilder subclass containing most of what you need...
The trick is not to touch that code! DO NOT CHANGE THAT CODE!
Sure you can change it and everything will be just fine, however it you will make changes to the GUI regenerating that file will obviously loose all those changes which is not something you want!
To solve it you need to subclass the generated class rather than subclass UIBuilder and just override the appropriate methods (some might even be abstract for your convenience), then if a UI changes you can safely overwrite the base class since you didn't change anything there...
Just so we are clear on the subject, after generating the code I can run the code by just using this and nothing else:
Obviously for a real world example that actually does something I would want to subclass and override methods in StateMachine.java. Lets look at the generated code which will help us understand both how to use the UIBuilder ourselves and how to subclass the code:
The code starts with several constructors to create the builder they all lead to one central constructor which I would like to focus on:
Notice the first line: registerCustomComponent(). Its crucial.
We need this line to let the GUI builder "know" how to locate a component by mapping the UIID to the component class. A Java SE developer might think "why not use Class.forName()". The problem is that Class.forName() will not work with obfuscated code, so we have to explicitly declare which classes we are using beyond the "core" LWUIT classes (even if they are within the LWUIT sub packages!).
This is a good thing since it means that you won't be importing Table, Tree & HTMLComponent unless you explicitly want them in your code. (BTW You will get an exception from the UIBuilder if you miss such an register call).
Notice that this also allows you to import your own arbitrary components and even use them in the resource editor's GUI builder (just by using pickMIDlet, but more on that at a later date).
To create the first form (which I picked from a combo box in the UI builder when generating the code) the UI builder needs the resource file and just invokes createContainer (which in this case returns a Form). Notice the call to setResourceFilePath, its important.
Once a form was created the UI builder tries to discard the resource file from RAM, however the resulting UI might include a Command for navigation to another form (declared within the resource file). How would the resource file be opened?
The solution is quite simple, if we have the name of the resource file we just open it (so creating the UI using a path name from the JAR is the simplest way possible to work with the UI builder). However, for more elaborate use cases such as storing downloaded resources in the RMS etc. one can just override the fetchResources() method and do pretty much anything to load a resources file on demand.
Next in the class we have constants for the command id's declared in the GUI builder, the GUI builder tries to push for them to be unique but doesn't force it (since you might want two commands with the same id). The generated code automatically adds a switch case and methods to process the commands which return true if the triggering event should be consumed.
And finally we have the exact same thing for every component that triggers an actionEvent. You can override handleComponentAction directly or let it do its magic and override your component name method in the code.
There are quite allot of additional methods in UIBuilder designed to enable you to use it in many varied ways (e.g. without subclassing and by binding listeners), I would like to mention a few methods of interest:
protected void processBackground(Form f)
This method is designed for the Splash Screen/wait dialog use case and is invoked on a separate thread to allow a long running task to take place. The default implementation is a sleep of 3000 ms allowing for easy debugging when running an application (e.g. I used this for the splash screen of the Ratio demo and it waited slightly even though the application doesn't load anything). To enable this in the GUI define the next form property which will trigger this method upon showing the form and seamlessly move to the next form on completion.
Similarly we have asyncCommandProcess(Command cmd, ActionEvent sourceEvent) and postAsyncCommand(Command cmd, ActionEvent sourceEvent).
A navigation command in the builder can be defined as asynchronous which will trigger these methods allowing the user to write background logic in the asyncCommandProcess() method and update the UI with the changes in the postAsyncCommand() method. Currently the UI builder doesn't generate code for async commands using these methods but it probably should do that for the next iteration.
I have allot more to write about and this was supposed to be a really short post so I'll try to keep some energy for these posts.
Please notice that when you use the LWUIT GUI builder and its related API's that its alpha level software likely to break and fail in interesting ways. I did my best to prevent a resource file corruption but I strongly suggest to constantly backup the resource file while working (and please file issues if you run into them!).
The UIBuilder API is also in alpha form and might change significantly although I tend to believe we have the right direction there...
The premise is this: the designer creates a UI version and names GUI components, he can create commands as well including navigation commands, exit, minimize. He can also define a long running command which will by default trigger a thread to execute...
All UI elements can be loaded using the UIBuilder class. Why not just use the Resources API?
Since Resources is essential for using LWUIT, adding the UIBuilder as an import would cause any application (even those that don't use the UIBuilder) to increase in size! We don't want people who aren't using the feature to pay the penalty for its existence!
The UI Builder is designed for use as a state machine carrying out the current state of the application so when any event occurs a subclass of the UIBuilder can just process it. The simplest way and most robust way for changes is to use the resource editor to generate some code for you (yes I know its not a code generation tool but there is a hack...). The code you need to write to process the UI includes some boilerplate code which is pretty mundane for processing commands based on id's or processing an event from a component, to simplify this process I added to the resource editor two menu items, the firs is simpler: "Help->Show Source For Using". The second is far more elaborate and I will try to go over it and its output bellow: "MIDlet->Generate UI State Machine".
When running the Generate UI state machine menn option in a project with UI builder content it prompts you for a directory/file name for the output. Just give it a new file name in your project directory and it will create a UIBuilder subclass containing most of what you need...
The trick is not to touch that code! DO NOT CHANGE THAT CODE!
Sure you can change it and everything will be just fine, however it you will make changes to the GUI regenerating that file will obviously loose all those changes which is not something you want!
To solve it you need to subclass the generated class rather than subclass UIBuilder and just override the appropriate methods (some might even be abstract for your convenience), then if a UI changes you can safely overwrite the base class since you didn't change anything there...
Just so we are clear on the subject, after generating the code I can run the code by just using this and nothing else:
import javax.microedition.midlet.*;
import com.sun.lwuit.*;
public class RatioMIDlet extends MIDlet {
public void startApp() {
Display.init(this);
new StateMachine("/ratios.res");
}
public void pauseApp() {
}
public void destroyApp(boolean unconditional) {
}
}
Obviously for a real world example that actually does something I would want to subclass and override methods in StateMachine.java. Lets look at the generated code which will help us understand both how to use the UIBuilder ourselves and how to subclass the code:
import com.sun.lwuit.*;
import com.sun.lwuit.util.*;
import com.sun.lwuit.events.*;
import com.sun.lwuit.plaf.*;
public class StateMachine extends UIBuilder {
public StateMachine(Resources res, String resPath, boolean loadTheme) {
UIBuilder.registerCustomComponent("HTMLComponent", com.sun.lwuit.html.HTMLComponent.class);
if(res != null) {
setResourceFilePath(resPath);
Form f = (Form)createContainer(res, "Splash");
f.show();
} else {
Form f = (Form)createContainer(resPath, "Splash");
f.show();
}
if(loadTheme) {
if(res == null) {
try {
res = Resources.open(resPath);
} catch(java.io.IOException err) { err.printStackTrace(); }
}
UIManager.getInstance().setThemeProps(res.getTheme(res.getThemeResourceNames()[0]));
}
}
public StateMachine(String resPath) {
this(null, resPath, true);
}
public StateMachine(Resources res) {
this(res, null, true);
}
public StateMachine(String resPath, boolean loadTheme) {
this(null, resPath, loadTheme);
}
public StateMachine(Resources res, boolean loadTheme) {
this(res, null, loadTheme);
}
public static final int COMMAND_BREAD_BACK = 5;
public static final int COMMAND_DOUGHS_BREAD = 4;
public static final int COMMAND_DOUGHS_BACK = 3;
public static final int COMMAND_MAINSCREEN_DOUGHS = 2;
protected boolean onBreadBack() {
return false;
}
protected boolean onDoughsBread() {
return false;
}
protected boolean onDoughsBack() {
return false;
}
protected boolean onMainscreenDoughs() {
return false;
}
protected void processCommand(ActionEvent ev, Command cmd) {
switch(cmd.getId()) {
case COMMAND_BREAD_BACK:
if(onBreadBack()) {
ev.consume();
}
return;
case COMMAND_DOUGHS_BREAD:
if(onDoughsBread()) {
ev.consume();
}
return;
case COMMAND_DOUGHS_BACK:
if(onDoughsBack()) {
ev.consume();
}
return;
case COMMAND_MAINSCREEN_DOUGHS:
if(onMainscreenDoughs()) {
ev.consume();
}
return;
}
}
protected void handleComponentAction(Component c, ActionEvent event) {
if(c.getComponentForm().getName().equals("Bread")) {
if("TextField1".equals(c.getName())) {
onTextfield1Action(c, event);
return;
}
if("ComboBox1".equals(c.getName())) {
onCombobox1Action(c, event);
return;
}
if("TextField11".equals(c.getName())) {
onTextfield11Action(c, event);
return;
}
if("ComboBox11".equals(c.getName())) {
onCombobox11Action(c, event);
return;
}
if("TextField111".equals(c.getName())) {
onTextfield111Action(c, event);
return;
}
if("ComboBox111".equals(c.getName())) {
onCombobox111Action(c, event);
return;
}
if("TextField12".equals(c.getName())) {
onTextfield12Action(c, event);
return;
}
if("ComboBox12".equals(c.getName())) {
onCombobox12Action(c, event);
return;
}
}
if(c.getComponentForm().getName().equals("MainScreen")) {
if("Doughs".equals(c.getName())) {
onDoughsAction(c, event);
return;
}
if("Batters".equals(c.getName())) {
onBattersAction(c, event);
return;
}
if("Custards".equals(c.getName())) {
onCustardsAction(c, event);
return;
}
if("Fat-Based Sauces".equals(c.getName())) {
onFatBasedSaucesAction(c, event);
return;
}
if("Stocks & Thickeners".equals(c.getName())) {
onStocksThickenersAction(c, event);
return;
}
if("Meat Related Ratios".equals(c.getName())) {
onMeatRelatedRatiosAction(c, event);
return;
}
if("Desert Sauces".equals(c.getName())) {
onDesertSaucesAction(c, event);
return;
}
}
if(c.getComponentForm().getName().equals("Doughs")) {
if("Bread".equals(c.getName())) {
onBreadAction(c, event);
return;
}
if("Pasta Dough".equals(c.getName())) {
onPastaDoughAction(c, event);
return;
}
if("Pie Dough".equals(c.getName())) {
onPieDoughAction(c, event);
return;
}
if("Biscuit".equals(c.getName())) {
onBiscuitAction(c, event);
return;
}
if("Cookie Dough".equals(c.getName())) {
onCookieDoughAction(c, event);
return;
}
if("Pâte à Choux".equals(c.getName())) {
onPTeChouxAction(c, event);
return;
}
}
}
protected void onTextfield1Action(Component c, ActionEvent event) {
}
protected void onCombobox1Action(Component c, ActionEvent event) {
}
protected void onTextfield11Action(Component c, ActionEvent event) {
}
protected void onCombobox11Action(Component c, ActionEvent event) {
}
protected void onTextfield111Action(Component c, ActionEvent event) {
}
protected void onCombobox111Action(Component c, ActionEvent event) {
}
protected void onTextfield12Action(Component c, ActionEvent event) {
}
protected void onCombobox12Action(Component c, ActionEvent event) {
}
protected void onDoughsAction(Component c, ActionEvent event) {
}
protected void onBattersAction(Component c, ActionEvent event) {
}
protected void onCustardsAction(Component c, ActionEvent event) {
}
protected void onFatBasedSaucesAction(Component c, ActionEvent event) {
}
protected void onStocksThickenersAction(Component c, ActionEvent event) {
}
protected void onMeatRelatedRatiosAction(Component c, ActionEvent event) {
}
protected void onDesertSaucesAction(Component c, ActionEvent event) {
}
protected void onBreadAction(Component c, ActionEvent event) {
}
protected void onPastaDoughAction(Component c, ActionEvent event) {
}
protected void onPieDoughAction(Component c, ActionEvent event) {
}
protected void onBiscuitAction(Component c, ActionEvent event) {
}
protected void onCookieDoughAction(Component c, ActionEvent event) {
}
protected void onPTeChouxAction(Component c, ActionEvent event) {
}
}
Like most generated code it is rather on the verbose side and methods/variables don't have the best naming logic, this is to be expected since the tool generates methods whether needed or not and provides names based on the names given by the designer (who in this case often chose not to change the default names given by the tool...).
The code starts with several constructors to create the builder they all lead to one central constructor which I would like to focus on:
public StateMachine(Resources res, String resPath, boolean loadTheme) {
UIBuilder.registerCustomComponent("HTMLComponent", com.sun.lwuit.html.HTMLComponent.class);
if(res != null) {
setResourceFilePath(resPath);
Form f = (Form)createContainer(res, "Splash");
f.show();
} else {
Form f = (Form)createContainer(resPath, "Splash");
f.show();
}
if(loadTheme) {
if(res == null) {
try {
res = Resources.open(resPath);
} catch(java.io.IOException err) { err.printStackTrace(); }
}
UIManager.getInstance().setThemeProps(res.getTheme(res.getThemeResourceNames()[0]));
}
}
Notice the first line: registerCustomComponent(). Its crucial.
We need this line to let the GUI builder "know" how to locate a component by mapping the UIID to the component class. A Java SE developer might think "why not use Class.forName()". The problem is that Class.forName() will not work with obfuscated code, so we have to explicitly declare which classes we are using beyond the "core" LWUIT classes (even if they are within the LWUIT sub packages!).
This is a good thing since it means that you won't be importing Table, Tree & HTMLComponent unless you explicitly want them in your code. (BTW You will get an exception from the UIBuilder if you miss such an register call).
Notice that this also allows you to import your own arbitrary components and even use them in the resource editor's GUI builder (just by using pickMIDlet, but more on that at a later date).
To create the first form (which I picked from a combo box in the UI builder when generating the code) the UI builder needs the resource file and just invokes createContainer (which in this case returns a Form). Notice the call to setResourceFilePath, its important.
Once a form was created the UI builder tries to discard the resource file from RAM, however the resulting UI might include a Command for navigation to another form (declared within the resource file). How would the resource file be opened?
The solution is quite simple, if we have the name of the resource file we just open it (so creating the UI using a path name from the JAR is the simplest way possible to work with the UI builder). However, for more elaborate use cases such as storing downloaded resources in the RMS etc. one can just override the fetchResources() method and do pretty much anything to load a resources file on demand.
Next in the class we have constants for the command id's declared in the GUI builder, the GUI builder tries to push for them to be unique but doesn't force it (since you might want two commands with the same id). The generated code automatically adds a switch case and methods to process the commands which return true if the triggering event should be consumed.
And finally we have the exact same thing for every component that triggers an actionEvent. You can override handleComponentAction directly or let it do its magic and override your component name method in the code.
There are quite allot of additional methods in UIBuilder designed to enable you to use it in many varied ways (e.g. without subclassing and by binding listeners), I would like to mention a few methods of interest:
protected void processBackground(Form f)
This method is designed for the Splash Screen/wait dialog use case and is invoked on a separate thread to allow a long running task to take place. The default implementation is a sleep of 3000 ms allowing for easy debugging when running an application (e.g. I used this for the splash screen of the Ratio demo and it waited slightly even though the application doesn't load anything). To enable this in the GUI define the next form property which will trigger this method upon showing the form and seamlessly move to the next form on completion.
Similarly we have asyncCommandProcess(Command cmd, ActionEvent sourceEvent) and postAsyncCommand(Command cmd, ActionEvent sourceEvent).
A navigation command in the builder can be defined as asynchronous which will trigger these methods allowing the user to write background logic in the asyncCommandProcess() method and update the UI with the changes in the postAsyncCommand() method. Currently the UI builder doesn't generate code for async commands using these methods but it probably should do that for the next iteration.
I have allot more to write about and this was supposed to be a really short post so I'll try to keep some energy for these posts.
Monday, October 18, 2010
New LWUIT GUI Builder Now Live!
Its been a while since we last discussed GUI builders and LWUIT, we spent some time thinking of the whole "gui builder issue". The biggest issue with GUI builders IMO is the code generation aspect, so when creating the new LWUIT GUI builder we just removed that aspect completely opting to make use of our existing resource file and resource editor architecture to allow a GUI builder that doesn't need to generate any code.
You can see a quick demo video on the right and you can try the webstart version right now to see how this works for you.
There are allot of things in the new GUI builder and I will try to keep you posted on how to use them in the best ways possible. The demo I'm showing off is a clone of Ruhlman's Ratio application which is based on his great cook book.
You can see a quick demo video on the right and you can try the webstart version right now to see how this works for you.
There are allot of things in the new GUI builder and I will try to keep you posted on how to use them in the best ways possible. The demo I'm showing off is a clone of Ruhlman's Ratio application which is based on his great cook book.
Monday, October 4, 2010
JavaOne 2010 - by Chen
It was a very interesting J1 this year (1st time under Oracle).
I felt it was better in previous years, but I believe Oracle will improve next year.
Anyway, our traditional LWUIT session went well and I had the opportunity to meet some lwuit developers.
Our session slides can be downloaded from here:
and the demo sources can be accessed from here (svn link):
(if you don't have a java.net user simply type in 'guest' user with an empty password)
Below I added a short video of the demo - a simple client that connects to Yahoo's Answers web service by using LWUIT4IO for the network communication
Monday, September 27, 2010
Tuesday, September 14, 2010
LWUIT At JavaOne 2010
If you are going to JavaOne this year there are quite a few LWUIT related sessions/BoF's you should probably add to your schedule.
The first is Chen and my traditional JavaOne session, unfortunately I won't be able to attend this year due to family issues and Ariel Levin will replace me in this session (for those who don't know Ariel, he has a long and glorious history with Java ME and is a pretty entertaining/knowledgeable speaker). Chen who is "the father of LWUIT" needs no further introduction... The session ID is S313185 "Writing Stunning Cross-Platform Applications Using LWUIT".
Terrence also needs no introduction either to readers of this blog, his session S314178 "Beyond Smartphones: Rich Applications and Services for the Mobile Masses" promises to be very interesting and packed with visual delight. From the demo I saw recently it seems he has the code nailed down and its sure to be an interesting session!
Alon will give a BoF on more practical day to day LWUIT development from the trenches in S314033 "LWUIT Cheat Sheet: How to Optimize Your LWUIT-Based Java ME Applications". LWUIT essentially started as a library for a Java application built within Sun's operator group, this application has passed many incarnations and the team moved back and forth in different directions. However, all things considered this is the longest running LWUIT application project in existence simply because our guys were there on the ground floor before LWUIT existed. Alon's team is building one of the best looking LWUIT applications we've seen and unlike most 3rd party applications, since they have full time access to our team they are using the latest and greatest cutting edge stuff in LWUIT.
Those of you looking to expand your horizons into LWUIT on TV would find a couple of sessions on the matter very interesting, the first S313442 " Developing Java TV Applications with LWUIT for DTVi-J (Ginga-J) Brazil" is lead by Tamir a core member of the LWUIT team who spent most of his time running the TV effort in our group. Ofir sadly couldn't come either due to scheduling conflicts, regardless Tamir is a great and thorough speaker. Understanding exactly the differences between TV and current LWUIT development is very important if you have thoughts on entering this space.
Last but not least is another TV related session with Ariel, Ziv and Bill all of whom very seasoned speakers who in session S313627 "Introducing Java TV Widget Development " would cover LWUIT as well. It should be very interesting as well.
The first is Chen and my traditional JavaOne session, unfortunately I won't be able to attend this year due to family issues and Ariel Levin will replace me in this session (for those who don't know Ariel, he has a long and glorious history with Java ME and is a pretty entertaining/knowledgeable speaker). Chen who is "the father of LWUIT" needs no further introduction... The session ID is S313185 "Writing Stunning Cross-Platform Applications Using LWUIT".
Terrence also needs no introduction either to readers of this blog, his session S314178 "Beyond Smartphones: Rich Applications and Services for the Mobile Masses" promises to be very interesting and packed with visual delight. From the demo I saw recently it seems he has the code nailed down and its sure to be an interesting session!
Alon will give a BoF on more practical day to day LWUIT development from the trenches in S314033 "LWUIT Cheat Sheet: How to Optimize Your LWUIT-Based Java ME Applications". LWUIT essentially started as a library for a Java application built within Sun's operator group, this application has passed many incarnations and the team moved back and forth in different directions. However, all things considered this is the longest running LWUIT application project in existence simply because our guys were there on the ground floor before LWUIT existed. Alon's team is building one of the best looking LWUIT applications we've seen and unlike most 3rd party applications, since they have full time access to our team they are using the latest and greatest cutting edge stuff in LWUIT.
Those of you looking to expand your horizons into LWUIT on TV would find a couple of sessions on the matter very interesting, the first S313442 " Developing Java TV Applications with LWUIT for DTVi-J (Ginga-J) Brazil" is lead by Tamir a core member of the LWUIT team who spent most of his time running the TV effort in our group. Ofir sadly couldn't come either due to scheduling conflicts, regardless Tamir is a great and thorough speaker. Understanding exactly the differences between TV and current LWUIT development is very important if you have thoughts on entering this space.
Last but not least is another TV related session with Ariel, Ziv and Bill all of whom very seasoned speakers who in session S313627 "Introducing Java TV Widget Development " would cover LWUIT as well. It should be very interesting as well.
Thursday, September 9, 2010
Peer Into The Future Of LWUIT
In my latest commit to LWUIT's trunk I added one of the major features intended for the next version of LWUIT: PeerComponent support. Peer components are essentially native components, e.g. a "fake" LWUIT component which represents the true underlying phone platform native component. This functionality is implementation specific and only works on RIM right now (we have a POC on AWT as well), it will not work for MIDP 2.x which doesn't allow arbitrary component placement.
Generally when we call LWUIT a lightweight framework we are borrowing the concept from Swing which treats all components implemented in pure java as lightweight (or peerless components) and all native OS components (such as AWT components) as heavyweight (or peer components).
In LWUIT we could not integrate with heavyweight/native components so we took advantage of that and generally took over the entire screen. Which is normally a huge advantage for LWUIT since it allowed us to implement lots of cool special effects very easily and streamline our implementation/porting efforts.
However as we ventured to platforms other than MIDP and even within some MIDP special cases (such as the MediaComponent) we wanted to allow implementation specific enhancements that provide some advantages only the native platform can offer. E.g. text input usually works best on the native front since it has access to localization/dictionary information that we are not privilege to.
In my latest commit I opened up a feature allowing implementation authors to expose access to the native component model and allow embedding such components in LWUIT. E.g. within the new blackberry implementation you can now do something like this:
ButtonField nativeRIMButton = new ButtonField("Hello");
PeerComponent peer = PeerComponent.create(nativeRIMButton);
lwuitContainer.add(peer);
While the example above might not be so useful for a button it would definitely ease integration with platform specific features and 3rd party widgets. However, this feature comes with MANY limitations and issues...
Peer components are rendered by the native platform hence the LWUIT drawing might have some issues with them. Currently under blackberry all peers are always rendered on top of everything hence no z-ordering or glasspane effects.
In the case of dialogs/menus etc. the peer components would disappear in the current implementation. I hope to improve this logic but its quite probable that glass panes/z-ordering will never work for peer components on RIM since the RIM API is very thread sensitive and won't allow us to perform the drawing on the LWUIT thread without risking major deadlocks.
As part of this change I deprecated the MediaComponent completely and replaced it with the more elaborate VideoComponent which in itself is a subclass of PeerComponent. The video component tries to hide the MMAPI implementation details further to allow different media API's in underlying native platforms e.g. JMF on TV.
I'll try to give more examples and details in my next blog post.
Generally when we call LWUIT a lightweight framework we are borrowing the concept from Swing which treats all components implemented in pure java as lightweight (or peerless components) and all native OS components (such as AWT components) as heavyweight (or peer components).
In LWUIT we could not integrate with heavyweight/native components so we took advantage of that and generally took over the entire screen. Which is normally a huge advantage for LWUIT since it allowed us to implement lots of cool special effects very easily and streamline our implementation/porting efforts.
However as we ventured to platforms other than MIDP and even within some MIDP special cases (such as the MediaComponent) we wanted to allow implementation specific enhancements that provide some advantages only the native platform can offer. E.g. text input usually works best on the native front since it has access to localization/dictionary information that we are not privilege to.
In my latest commit I opened up a feature allowing implementation authors to expose access to the native component model and allow embedding such components in LWUIT. E.g. within the new blackberry implementation you can now do something like this:
ButtonField nativeRIMButton = new ButtonField("Hello");
PeerComponent peer = PeerComponent.create(nativeRIMButton);
lwuitContainer.add(peer);
While the example above might not be so useful for a button it would definitely ease integration with platform specific features and 3rd party widgets. However, this feature comes with MANY limitations and issues...
Peer components are rendered by the native platform hence the LWUIT drawing might have some issues with them. Currently under blackberry all peers are always rendered on top of everything hence no z-ordering or glasspane effects.
In the case of dialogs/menus etc. the peer components would disappear in the current implementation. I hope to improve this logic but its quite probable that glass panes/z-ordering will never work for peer components on RIM since the RIM API is very thread sensitive and won't allow us to perform the drawing on the LWUIT thread without risking major deadlocks.
As part of this change I deprecated the MediaComponent completely and replaced it with the more elaborate VideoComponent which in itself is a subclass of PeerComponent. The video component tries to hide the MMAPI implementation details further to allow different media API's in underlying native platforms e.g. JMF on TV.
I'll try to give more examples and details in my next blog post.
Tuesday, September 7, 2010
Terrence Conducting a LWUIT Webinar
Terrence will conduct a LWUIT webinar on September 16th, for more details go to Terrence's blog.
Wednesday, September 1, 2010
Feedback On The Release/Features Of LWUIT 1.4
We are looking for feedback on release 1.4 of LWUIT for internal promotion within Oracle, we might try to form a more organized survey for a later date but right now we want to get general abstract impressions.
Have you updated to 1.4, if not when do you plan to do this? What features of 1.4 do you like? Which features are you using? Where do you see LWUIT going for the next version? What would you like to see in LWUIT?
Feel free to rant and provide abstract thoughts/context.
Thanks.
Have you updated to 1.4, if not when do you plan to do this? What features of 1.4 do you like? Which features are you using? Where do you see LWUIT going for the next version? What would you like to see in LWUIT?
Feel free to rant and provide abstract thoughts/context.
Thanks.
Sunday, August 22, 2010
Opening A New Tab In LWUIT
Late last week Chen finally committed one of our long outstanding promises to the LWUIT community, a rewrite of the aging TabbedPane. When we initially released LWUIT 1.0, the tabbed pane was one of the more complex components since it depended on several different LWUIT components to provide its functionality and was limited by missing features in LWUIT.
When the tabbed pane was written there was only one style per component, there was no Border object, no z-ordering. Eventually we chose an architecture for the TabbedPane that revolved around a list component coupled with a container and rather elaborate border drawing. The biggest drawback was the tedious customization process requiring manually deriving the look and feel to customize the renderer logic of the tabs.
The final push we needed to "just do it" was from Thorsten who took my swipe demo code and just turned it into a more real world demo using actual components... Very cool stuff. This inspired Chen to truly revolutionize the component and create a completely new touch optimized tabbed pane implementation that still works with the regular keyboard although differently (you now have to press fire to actually switch a tab).
Since the changes are so huge we decided to create a whole new drop in component called Tabs which is a drop in replacement for TabbedPane (just change all references to Tabs). For the next version of LWUIT we will probably remove TabbedPane entirely and have now deprecated its usage.
In the video you can see a very simple demo, the component supports allot of functionality that isn't demoed such as hidden tabs (for just a gesture based UI), full customization of the tabs etc. To make this demo I just changed the tabbed pane references in the LWUIT demo to Tabs.
When the tabbed pane was written there was only one style per component, there was no Border object, no z-ordering. Eventually we chose an architecture for the TabbedPane that revolved around a list component coupled with a container and rather elaborate border drawing. The biggest drawback was the tedious customization process requiring manually deriving the look and feel to customize the renderer logic of the tabs.
The final push we needed to "just do it" was from Thorsten who took my swipe demo code and just turned it into a more real world demo using actual components... Very cool stuff. This inspired Chen to truly revolutionize the component and create a completely new touch optimized tabbed pane implementation that still works with the regular keyboard although differently (you now have to press fire to actually switch a tab).
Since the changes are so huge we decided to create a whole new drop in component called Tabs which is a drop in replacement for TabbedPane (just change all references to Tabs). For the next version of LWUIT we will probably remove TabbedPane entirely and have now deprecated its usage.
In the video you can see a very simple demo, the component supports allot of functionality that isn't demoed such as hidden tabs (for just a gesture based UI), full customization of the tabs etc. To make this demo I just changed the tabbed pane references in the LWUIT demo to Tabs.
Wednesday, August 11, 2010
Basic Usage of LWUIT4IO
Last week I wrote about LWUIT4IO but didn't really explain how to utilize it properly and what makes it completely different from just using the GCF (Generic Connection Framework: Connector.open etc.).
Normally in MIDP you would just connect to the internet using Connector.open(String, ?) and get a connection object or a stream. The process seems simple enough but there are lots of hidden caveats in this process. For instance you would need to handle errors in case they happen (and they often do on a mobile device), in which case you need to show an error dialog (which is usually very uniform for all the connections you have in your application.
You also need to conduct the networking in a separate thread that is neither the LWUIT nor the default MIDP thread since these threads will block the UI from updating. After finishing every operation or in case of an exception it is critical in MIDP to cleanup all the connections/streams appropriately. Developers often forget to do this because the GC often removes that need, but with the GCF it is often critical to invoke close() otherwise native resources won't be freed and might cause issues on some devices (very common issue in feature phones).
LWUIT4IO tries to solve all of these by hiding the entire process of networking behind a component API, e.g. to connect to a URL and fetch its content in LWUIT4IO one could do just:
NetworkManager.getInstance().addToQueue(myRequest);
You do first need to initialize LWUIT4IO by invoking (similar to the need of invoking Display.init once):
NetworkManager.getInstance().start();
The request object is a callback class that includes the URL/arguments and request method (get/post). The network thread will perform the connection and invoke the proper callback methods within the request object e.g.:
ConnectionRequest myRequest = new ConnectionRequest();
myRequest.setUrl("http://mysite.com/");
myRequest.addArg("arg", "value");
addResponseListener(new ActionListener() {
public void actionEvent(ActionEvent ev) {
NetworkEvent n = (NetworkEvent)ev;
// gets the data from the server as a byte array...
byte[] data = (byte[])n.getMetaData();
}
});
Alternatively one can override the code to read/write the request values e.g.:
ConnectionRequest myRequest = new ConnectionRequest() {
protected void readResponse(InputStream input) throws IOException {
// read from the input stream...
}
};
Besides the advantages of the more generic code, the true advantages you gain in using this approach is quite remarkable:
Normally in MIDP you would just connect to the internet using Connector.open(String, ?) and get a connection object or a stream. The process seems simple enough but there are lots of hidden caveats in this process. For instance you would need to handle errors in case they happen (and they often do on a mobile device), in which case you need to show an error dialog (which is usually very uniform for all the connections you have in your application.
You also need to conduct the networking in a separate thread that is neither the LWUIT nor the default MIDP thread since these threads will block the UI from updating. After finishing every operation or in case of an exception it is critical in MIDP to cleanup all the connections/streams appropriately. Developers often forget to do this because the GC often removes that need, but with the GCF it is often critical to invoke close() otherwise native resources won't be freed and might cause issues on some devices (very common issue in feature phones).
LWUIT4IO tries to solve all of these by hiding the entire process of networking behind a component API, e.g. to connect to a URL and fetch its content in LWUIT4IO one could do just:
NetworkManager.getInstance().addToQueue(myRequest);
You do first need to initialize LWUIT4IO by invoking (similar to the need of invoking Display.init once):
NetworkManager.getInstance().start();
The request object is a callback class that includes the URL/arguments and request method (get/post). The network thread will perform the connection and invoke the proper callback methods within the request object e.g.:
ConnectionRequest myRequest = new ConnectionRequest();
myRequest.setUrl("http://mysite.com/");
myRequest.addArg("arg", "value");
addResponseListener(new ActionListener() {
public void actionEvent(ActionEvent ev) {
NetworkEvent n = (NetworkEvent)ev;
// gets the data from the server as a byte array...
byte[] data = (byte[])n.getMetaData();
}
});
Alternatively one can override the code to read/write the request values e.g.:
ConnectionRequest myRequest = new ConnectionRequest() {
protected void readResponse(InputStream input) throws IOException {
// read from the input stream...
}
};
Besides the advantages of the more generic code, the true advantages you gain in using this approach is quite remarkable:
- You don't need to write threading code - LWUIT callbacks (e.g. actionPerformed) are on the LWUIT EDT seamlessly. Everything else automatically goes to the network thread...
- Exceptions are caught and handled without any need to do anything.
- You don't need to close streams or connections ever!
- All streams are buffered so you don't need to worry abound efficiency of reading from a data input stream.
- You can utilize inheritance and reuse code far more extensively
- Cookies, redirects work pretty much like you would expect without any additional code.
Thursday, August 5, 2010
LWUIT 1.4 Finally Released
Its taken some time since our intended release date and by not allot has changed in the SVN but the 1.4 binary release is finally official! Get it here.
This release is very important since it marks our first release under Oracle which will hopefully make the next LWUIT release smoother now that we understand the process.
This is mostly a stability and bug fix release with the major feature being the new XHTML component which includes CSS support. Besides that we finally have a multi-line text field, allowing lightweight customizable input to span more than one line. We also added tools to customize the LWUIT VKB for some advanced use cases. Last but not least we introduced some more advanced animations than the previous static animation support.
This release is very important since it marks our first release under Oracle which will hopefully make the next LWUIT release smoother now that we understand the process.
This is mostly a stability and bug fix release with the major feature being the new XHTML component which includes CSS support. Besides that we finally have a multi-line text field, allowing lightweight customizable input to span more than one line. We also added tools to customize the LWUIT VKB for some advanced use cases. Last but not least we introduced some more advanced animations than the previous static animation support.
Monday, August 2, 2010
Introducing LWUIT For IO (LWUIT4IO)
When Chen started LWUIT he limited his ambitions to solving the UI portability issues and with those limited ambitions we were able to over deliver by providing a framework that delivered an easier, faster & better UI while still maintaining the portability advantage.
The limited scope of LWUIT is one of its strengths allowing us to focus on the issues at hand and not stray too much.
IO (storage, filesystem & network) is probably the biggest and most common portability stumbling block after UI & with LWUIT4IO we aim to simplify the process of working with these API's across platforms/devices without sacrificing portability or vendor specific features. The underlying API's are given the "LWUIT treatment" where we abstract lots of the thread related heavy lifting behind and component like unified API and try to make the whole process reasonably fool proof.
LWUIT4IO is fully optional, at it depends on LWUIT but the reverse isn't true. LWUIT has no dependency on LWUIT4IO and we intend to keep it this way since we assume many of you have written elaborate IO frameworks of your own and might not be ready to switch.
If you do decide to switch here are some of the things we worked on for LWUIT4IO and some of the features we have in our pipeline (notice that LWUIT4IO being brand new is still labeled as alpha quality software):
Default Connectors - e.g. File download connector
The limited scope of LWUIT is one of its strengths allowing us to focus on the issues at hand and not stray too much.
IO (storage, filesystem & network) is probably the biggest and most common portability stumbling block after UI & with LWUIT4IO we aim to simplify the process of working with these API's across platforms/devices without sacrificing portability or vendor specific features. The underlying API's are given the "LWUIT treatment" where we abstract lots of the thread related heavy lifting behind and component like unified API and try to make the whole process reasonably fool proof.
LWUIT4IO is fully optional, at it depends on LWUIT but the reverse isn't true. LWUIT has no dependency on LWUIT4IO and we intend to keep it this way since we assume many of you have written elaborate IO frameworks of your own and might not be ready to switch.
If you do decide to switch here are some of the things we worked on for LWUIT4IO and some of the features we have in our pipeline (notice that LWUIT4IO being brand new is still labeled as alpha quality software):
- Component based connections - Network connections are made with objects and callback events. When a response finishes its processing on the network thread it seamlessly transitions to an event on the LWUIT thread.
- LWUIT Component integration - currently the new slider component seamlessly shows progress indication in the IO framework. We plan to seamlessly integrate the HTML component as well making it both more efficient and easier to use.
- Buffering - all network connections in LWUIT4IO are seamlessly buffered providing easier programming (no need to manually buffer) and faster downloads.
- Timeout support - GCF (MIDP's Generic Connection Framework) doesn't support thread timeouts. LWUIT4IO compensates for that by providing timeouts on platforms that support them and "faking" timeouts on platforms that don't (such as MIDP) by abandoning the network thread and creating a new one seamlessly.
- Cookies - server cookies are managed seamlessly by LWUIT4IO.
- Filesystem - the LWUIT4IO portable filesystem API is simpler and more portable than JSR75.
- Storage - LWUIT4IO supports named storage streams to place data within application specific named streams. This is based on RMS when running on MIDP but provides an easier to use stream based API.
- Serialization - LWUIT4IO supports a simple object serialization API for application state saving.
- Access Point - LWUIT4IO provides an API for detecting the available internet access points for the device and picking the right one. This is device specific and works seamlessly on devices that support it such as newer Symbian devices and RIM devices.
- Unified Error Handling - LWUIT4IO supports error handling as LWUIT events as well as a single point error handler.
- JSON Parser - LWUIT4IO ships with a simple JSON parser. We plan to expose the existing LWUIT HTML XML parser in future versions of LWUIT to make REST programming easier in LWUIT.
- Caching Framework - LWUIT4IO contains a smart caching system that keeps frequently used elements in RAM and optionally reverts to using storage cache.
- Mutli-threaded networking (experimental) - LWUIT4IO supports seamless integration with many network threads while assigning specific duties to specific threads if so desired.
- Default connectors - simple web services are already implemented for convenience and reference in LWUIT4IO such as Google REST search, Twitter & image download service.
Default Connectors - e.g. File download connector
Thursday, July 29, 2010
Sliding It Back To My First Post
My very first post in this blog (May 2008) was about creating a progress indicator component. At the time LWUIT only had one style per component and the post was mostly about threading in LWUIT. Over that time we considered adding a progress indicator component frequently but had a very difficult issue with its customization. How can we create a component which is both powerful enough for general usage and not too restricted for the various use cases.
Enter the new Slider component which thanks to some advantages such as the multiple styles is far more powerful than the original and it is now a part of LWUIT. The slider can be manipulated like a gauge to control elements such as volume, it can be used to indicate position (with a text overlay inherited from Label) and it can be customized in pretty much any way imaginable.
The demo you see to your right is ridiculously simple containing 3 such sliders, the top and middle one are identical with the middle one adding a status overlay. The bottom slider is set for infinite progress support which is useful to indicate a "please wait" status.
To create such a theme I just customized the "Slider" and "SliderFull" styles with a border for the full state and the empty state (essentially the squares in the center are just the style of SliderFull painted on top of a style of Slider).
Enter the new Slider component which thanks to some advantages such as the multiple styles is far more powerful than the original and it is now a part of LWUIT. The slider can be manipulated like a gauge to control elements such as volume, it can be used to indicate position (with a text overlay inherited from Label) and it can be customized in pretty much any way imaginable.
The demo you see to your right is ridiculously simple containing 3 such sliders, the top and middle one are identical with the middle one adding a status overlay. The bottom slider is set for infinite progress support which is useful to indicate a "please wait" status.
To create such a theme I just customized the "Slider" and "SliderFull" styles with a border for the full state and the empty state (essentially the squares in the center are just the style of SliderFull painted on top of a style of Slider).
final Slider s1 = new Slider();
f.addComponent(s1);
final Slider s2 = new Slider();
s2.setRenderPercentageOnTop(true);
f.addComponent(s2);
Slider s3 = new Slider();
f.addComponent(s3);
s3.setInfinite(true);
new Thread() {
private boolean dir = true;
private int val = 0;
public void run() {
try {
sleep(1000);
while (Display.getInstance().getCurrent() == f) {
sleep(100);
s1.setProgress(val);
s2.setProgress(val);
if (dir) {
if (val > 100) {
dir = false;
}
val++;
} else {
if (val < 1) {
dir = true;
}
val--;
}
}
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}.start();
Subscribe to:
Posts (Atom)