Hand coding app and having a hard time understanding themes particularly with lists

189 views
Skip to first unread message

Dan

unread,
Nov 14, 2013, 7:21:04 AM11/14/13
to codenameone...@googlegroups.com

I've been hand coding my app and so far, it seems to work pretty well. It's only when I attempt to add subtle styles that I get in trouble (I want to remain as close to native as I can).

The things that I've wanted to change so far are font sizes on labels and creating a custom ListCellRenderer (font size and spacing). However, for the custom ListCellRenderer, I've lost some functionality by doing so such as pressing an item doesn't change the background.

I read that I could use the GenericListCellRender to keep all the same functionality. Should I simply extend GenericListCellRender or copy it in its entirety and customize that?

Here is what I have so far:

public class GameListRenderer extends Container implements ListCellRenderer {
   
    private Label gameDate = new Label();
    private Label gameDetails = new Label();
   
    public GameListRenderer(){
        setLayout(new BorderLayout());

        gameDate.getStyle().setFont(Font.createSystemFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_SMALL));
        gameDetails.getStyle().setFont(Font.createSystemFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_LARGE));

        addComponent(BorderLayout.NORTH, gameDate);
        addComponent(BorderLayout.SOUTH, gameDetails);
    }

    public Component getListCellRendererComponent(List list, Object value, int index, boolean isSelected) {
        Game game = (Game)value;
       
        gameDate.setText(game.getGameDateString());
        gameDetails.setText(game.toString());
       
        return this;
    }

    public Component getListFocusComponent(List list) {
        return null;
    }
}

I've looked at both How do I? videos in regards to lists, but those seem to deal with the GUI builder.

Shai Almog

unread,
Nov 14, 2013, 1:53:43 PM11/14/13
to codenameone...@googlegroups.com
Since you are using Containers I would go with generic list cell renderer which simplifies a lot of this process but you would need to work with Maps or Hashtables in the model side which might not be acceptable for you.

The reason selection doesn't work for you is that you just ignore it. You get an isSelected call in which you should return a selected/focused version of the component. You also return null for the focus component.
I suggest reading the code of DefaultListCellRenderer and ideally GenericListCellRenderer both of which will help you in "doing the right thing"(tm): http://code.google.com/p/codenameone/source/browse/trunk/CodenameOne/src/com/codename1/ui/list/

Dan

unread,
Nov 14, 2013, 2:07:24 PM11/14/13
to codenameone...@googlegroups.com
Thanks for confirming my suspicions.

At the moment, the model provided is a Vector of custom objects (Vector<Season>) that override the toString method. Once I click on the item, I retrieve the object, call getSeasonId and then launch a process to retrieve JSON data about this seasonId.

I'll read up and see what is required to convert to a Hashtables. I suspect that it wouldn't be too hard as I'm working with Hashtables when getting my JSON already.

CodenameOneMan

unread,
Nov 15, 2013, 5:58:41 AM11/15/13
to codenameone...@googlegroups.com
Hi Dan,

Just a quick top up. You can create a Custom List model which means implementing ListModel Interface and overriding the methods. Whatever dataset you send it to custom list model, getItemAt method needs to be changed. getItemAt method would return the int where we have to return the index. Use vector of Hashtables and return the vector index in getItemAt method.

Also you can use Multibutton and create themes for multiline1, 2 3 and 4. U can differ font sizes there. This will solve your issues i believe. Hope this would help you.

Dan

unread,
Nov 15, 2013, 8:51:53 AM11/15/13
to codenameone...@googlegroups.com
I opened up my theme, added Multiline1. In the Font tab, I derived a SYSTEM font, PLAIN and LARGE. I then clicked Save. My CVS showed that the file had changed.

I relaunched my application but the lists were all the same except the one I had over ridden with my custom ListCellRenderer mentionned above. How are the themes applied to hand coded apps then?

Shai Almog

unread,
Nov 15, 2013, 4:00:14 PM11/15/13
to codenameone...@googlegroups.com
Make sure you did this both for selected/unselected.
Also its possible you had a typo MultiLine1 upper case L.

Dan

unread,
Nov 15, 2013, 7:19:21 PM11/15/13
to codenameone...@googlegroups.com
I made sure that I had done the font change for both selected and unselected (using Theme builder). I saved the file and launched the simulator with the same results.

Now, if I look at the code for the visual Hello World, there are actual commands loading the theme. In my hand coded app, I don't have that. I went searching and found some code and here's what I have so far:

   public void start() throws IOException {
        if(current != null){
            current.show();
            return;
        }
        //for manaul coding app
        Resources res = Resources.openLayered("/theme");
        String[] themes = res.getThemeResourceNames();
        System.out.println(themes[0]);//outputs "Theme"
        int THEME_INDEX = 0;
        UIManager.getInstance().setThemeProps(res.getTheme(themes[THEME_INDEX]));
        Display.getInstance().getCurrent().refreshTheme();//this is line 36
        WelcomeForm welcomeForm = new WelcomeForm();
        welcomeForm.show();
       
    }

However, I get the following error:

java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at com.codename1.impl.javase.Executor$1.run(Executor.java:95)
    at com.codename1.ui.Display.processSerialCalls(Display.java:1047)
    at com.codename1.ui.Display.mainEDTLoop(Display.java:867)
    at com.codename1.ui.RunnableWrapper.run(RunnableWrapper.java:119)
    at com.codename1.impl.CodenameOneThread.run(CodenameOneThread.java:176)
Caused by: java.lang.NullPointerException
    at com.myapp.start(MyApp.java:36)
    ... 9 more

Getting closer at understanding the process.

Shai Almog

unread,
Nov 16, 2013, 3:39:59 AM11/16/13
to codenameone...@googlegroups.com
You made up the refresh theme call, it was never in our code and is completely unnecessary for your use case. Its there to change an existing theme not for startup.
We initialize the theme in the init() method usually by convention since it makes more sense.

Display.getInstance().getCurrent() will return the current form, since you haven't shown a form you can probably guess why that method returned null ;-)

Dan

unread,
Nov 16, 2013, 5:41:27 AM11/16/13
to codenameone...@googlegroups.com
I failed to notice similar calls in the init method. My apologies.

However, I ran my app after having removed the lines and noticed that clicking a list item, the text got larger. So, I double checked my theme and both selected/unselected were set.

Screenshots:
Theme Builder Selected
http://i.imgur.com/G9AntbD.png
Theme Builder Unselected
http://i.imgur.com/099WOL4.png

Not quite sure why the unselected state is being set...

Screenshots
Unselected List
http://i.imgur.com/F6skH8M.png
Selected List
http://i.imgur.com/ocf6pYp.png

Shai Almog

unread,
Nov 16, 2013, 11:13:34 AM11/16/13
to codenameone...@googlegroups.com
Is it possible you are creating the ui before setting the theme?

Dan

unread,
Nov 16, 2013, 11:37:12 AM11/16/13
to codenameone...@googlegroups.com
I'm running with the default generated hand coding hello world...

public class MyApp{

    private Form current;

    public void init(Object context) {
        try{
            Resources theme = Resources.openLayered("/theme");
            UIManager.getInstance().setThemeProps(theme.getTheme(theme.getThemeResourceNames()[0]));
       }catch(IOException e){
            e.printStackTrace();

        }
    }
   
    public void start() throws IOException {
        if(current != null){
            current.show();
            return;
        }
        WelcomeForm welcomeForm = new WelcomeForm();
        welcomeForm.show();
       
    }

    public void stop() {
        current = Display.getInstance().getCurrent();
    }
   
    public void destroy() {
    }

}

I do not touch anything in the UI after this form.

Shai Almog

unread,
Nov 16, 2013, 12:01:51 PM11/16/13
to codenameone...@googlegroups.com
I just tested this and it worked for me. Is it possible you are using a String vector/array rather than a vector of hashtables or maps?
This might lead to some odd behaviors such as this one (related to how the renderer is initialized).

Dan

unread,
Nov 17, 2013, 4:23:00 PM11/17/13
to codenameone...@googlegroups.com
Here's what I have so far:

public class Season{
   
    private int seasonId;
    private int userId;
    private String seasonName;
   
    public Season(){
        super();
    }
   
    public Season(int seasonId, String seasonName){
        super();
        this.setSeasonId(seasonId);
        this.setSeasonName(seasonName);
    }
   
    public int getSeasonId() {
        return seasonId;
    }
    public void setSeasonId(int seasonId) {
        this.seasonId = seasonId;
    }
    public String getSeasonName() {
        return seasonName;
    }
    public void setSeasonName(String seasonName) {
        this.seasonName = seasonName;
    }
   
    public int getUserId() {
        return userId;
    }

    public void setUserId(int userId) {
        this.userId = userId;
    }

    @Override
    public String toString(){
        return this.getSeasonName();
    }
}

public class ListSeasonsForm extends Form {

    private Form previousForm;
    private List<Season> seasonList;
   
    public ListSeasonsForm(Form previousForm){
        this.previousForm = previousForm;
        init();
        loadData();
    }
   
    private void init(){
       
        seasonList = new List<Season>();
       
        setTitle("Season List");
        setLayout(new BorderLayout());
        setScrollable(false);
        setBackCommand(new BackCommand(this.previousForm));
       
        seasonList.addActionListener(new SeasonListActionListener(this));
    }
   
    private void loadData(){
        ListSeasonsConnectionRequest r = new ListSeasonsConnectionRequest(seasonList);
       
        r.setUrl("http://mygoaliestats.com/api/");
        r.setPost(true);
        r.addArgument("action", "seasonByUserId");
        r.addArgument("userId", "59");
       
        InfiniteProgress progress = new InfiniteProgress();
        Dialog dialog = progress.showInifiniteBlocking();
        r.setDisposeOnCompletion(dialog);
       
        NetworkManager.getInstance().addToQueueAndWait(r);
       
        if(r.numberOfItems() > 0)
            addComponent(BorderLayout.CENTER, seasonList);
        else
            addComponent(BorderLayout.CENTER, new Label("No seasons available."));
       
    }
}

public class ListSeasonsConnectionRequest extends ConnectionRequest{
   
    List<Season> seasonListComponent;
    Season s;
    Vector<Season> seasonList = new Vector<Season>();
    Vector<Hashtable<String, Object>> todo;
       
    public ListSeasonsConnectionRequest(List<Season> list){
        this.seasonListComponent = list;
    }
   
    public int numberOfItems(){
        return todo.size();
    }
   
    @Override
    protected void readResponse(InputStream input) throws IOException {
        JSONParser p = new JSONParser();

        Hashtable h = p.parse(new InputStreamReader(input));
        todo = (Vector<Hashtable<String, Object>>)h.get("root");
      
        for(Hashtable<String, Object> entry : todo) {
            s = new Season();
            Double s1 = (Double)entry.get("seasonId");
            Double s2 = (Double)entry.get("userId");
            String s3 = (String)entry.get("seasonName");
            s.setSeasonId(s1.intValue());
            s.setUserId(s2.intValue());
            s.setSeasonName(s3);
            seasonList.add(s);
        }
    }

    @Override
    protected void postResponse() {
        seasonListComponent.setModel(new DefaultListModel(seasonList));
    }
}

You mentioned something about using a Vector of Hashtables, so I tried this:

public class ListSeasonsConnectionRequest extends ConnectionRequest{
   
    List seasonListComponent;
    Vector v = new Vector();
       
    public ListSeasonsConnectionRequest(List<Season> list){
        this.seasonListComponent = list;
    }
   
    public int numberOfItems(){
        return todo.size();
    }
   
    @Override
    protected void readResponse(InputStream input) throws IOException {
        JSONParser p = new JSONParser();

        Hashtable h = p.parse(new InputStreamReader(input));
        System.out.println(h);//prints JSON root=[{...}{...}]
        todo = (Vector<Hashtable<String, Object>>) h.get("root");
         
        Hashtable t;
       
        for(Hashtable<String, Object> entry : todo) {
            t = new Hashtable();
            System.out.println(entry.get("seasonName")); //outputs "2005-2006"
            t.put("Line1", "" + entry.get("seasonName"));                    
            v.add(t);
        }
    }

    @Override
    protected void postResponse() {
        seasonListComponent.setModel(new DefaultListModel(v));
    }
}

But here, the actual JSON string is being shown in the list (brackets and all). I thought that if I put in "Line1", that it would be grabbed by the app and displayed properly?

I think I'm missing something in my understanding on how this is supposed to work.

Shai Almog

unread,
Nov 18, 2013, 1:33:18 AM11/18/13
to codenameone...@googlegroups.com
You are adding the Season object rather than the Hashtable so what you are getting the invocation of toString() as a sort of fallback. You need the list to contain Hashtables not Season objects.
If you want to access the season object just do hash.put("Season", seasonInstance); and store it there for later retrieval.

Dan

unread,
Nov 18, 2013, 7:27:51 AM11/18/13
to codenameone...@googlegroups.com
What is the key that I should use for it to work properly? In my second example, using Hashtables, I had used the key "Line1" but the list wasn't displaying the appropriate content (entire JSON with brackets instead of just a string).

Shai Almog

unread,
Nov 18, 2013, 1:36:58 PM11/18/13
to codenameone...@googlegroups.com
Sorry I misread your code as one blob. The second section is the right one overall.
Can you see if instead of this:

            t.put("Line1", "" + entry.get("seasonName"));                    

Try doing this:
            t.put("Line1", (String)entry.get("seasonName"));                    

I'm assuming you will get a class cast exception since you have a vector there but that's just a guess.
I suggest saving the value into a variable and stopping there with a debugger breakpoint.

Dan

unread,
Nov 18, 2013, 4:11:18 PM11/18/13
to codenameone...@googlegroups.com
Here's what I have so far:

public class ListSeasonsConnectionRequest extends ConnectionRequest{
      
    List seasonListComponent;
    Vector v = new Vector();
    Vector<Hashtable<String, Object>> todo;
      
    public ListSeasonsConnectionRequest(List<Season> list){
        this.seasonListComponent = list;
    }
  
    public int numberOfItems(){
        return todo.size();
    }
  
    @Override
    protected void readResponse(InputStream input) throws IOException {
        JSONParser p = new JSONParser();

        Hashtable h = p.parse(new InputStreamReader(input));
        System.out.println(h);//prints JSON root=[{...}{...}]
        todo = (Vector<Hashtable<String, Object>>) h.get("root");
        
        Hashtable t;
      
        for(Hashtable<String, Object> entry : todo) {
            t = new Hashtable();
            System.out.println(entry.get("seasonName")); //outputs "2005-2006"
            t.put("Line1", (String)entry.get("seasonName"));//outputs {Line1=2005-2006} in the list                   
            v.add(t);
        }
    }

    @Override
    protected void postResponse() {
        seasonListComponent.setModel(new DefaultListModel(v));
    }
}

Here's what is happening for me:

1. I get JSON in the label now... {Line1=WhateverStringIPut}
2. the styling is neither applied on unselected/selected actions, the text remains the same even though I told it to go Large.

I reverted to code that used the Season objects again, and the styles don't get affected on neither states there too.

The MultiList1 is styled as indicated previously (Large font for selected/unselected states). I'm on Eclipse Indigo 3.8 (and just updated the plugin and re-tested).

Shai Almog

unread,
Nov 19, 2013, 12:40:41 AM11/19/13
to codenameone...@googlegroups.com
You need to use the Hashtable version.
What you are getting probably isn't JSON since you place a parsed object there. I'm betting its an instance of a Hashtable.
You can verify this by hardcoding the values in the Hashtable to "XXX" and see that this appears correctly then step over the code in the debugger.

Dan

unread,
Nov 19, 2013, 7:19:52 PM11/19/13
to codenameone...@googlegroups.com
Here's my code:

    Vector<Hashtable<>> v = new Vector<Hashtable>();

       
    public ListSeasonsConnectionRequest(List<Season> list){
        this.seasonListComponent = list;
    }
   
    public int numberOfItems(){
        return todo.size();
    }
   
    @Override
    protected void readResponse(InputStream input) throws IOException {
        JSONParser p = new JSONParser();

        Hashtable h = p.parse(new InputStreamReader(input));
        todo = (Vector<Hashtable<String, Object>>)h.get("root");

                  
        Hashtable t;
      
        for(Hashtable<String, Object> entry : todo) {
            t = new Hashtable();
            t.put("Line1", "XXX");

            v.add(t);
        }
    }

    @Override
    protected void postResponse() {
        seasonListComponent.setModel(new DefaultListModel(v));
    }

Shai Almog

unread,
Nov 20, 2013, 1:07:09 AM11/20/13
to codenameone...@googlegroups.com
FYI the diamond operator <> is part of Java 7 and isn't supported by Codename One. Make sure your source level is set to 5 otherwise the server build will fail.

This looks to me like you have some conflict with some variable you have lying around try this:
    protected void postResponse() {
        Vector<Hashtable> vec = new Vector<Hashtable>();
        for(int iter = 0 ; iter < v.size() ; iter++) {
            Hashtable a = new Hashtable();
            a.put("Line1", "XXXX);
            vec.add(a);
        }
        seasonListComponent.setModel(new DefaultListModel(vec));
    }

Dan

unread,
Nov 20, 2013, 11:55:26 AM11/20/13
to codenameone...@googlegroups.com
I'm at a loss. Here is my current code:


public class ListSeasonsConnectionRequest extends ConnectionRequest{
   
    private List seasonListComponent;
       
    public ListSeasonsConnectionRequest(List seasonListComponent){
        this.seasonListComponent = seasonListComponent;
    }

    @Override
    protected void postResponse() {
        Vector<Hashtable> vec = new Vector<Hashtable>();
        for(int iter = 0 ; iter < 10 ; iter++) {

            Hashtable a = new Hashtable();
            a.put("Line1", "XXXX");
            vec.add(a);
        }
        seasonListComponent.setModel(new DefaultListModel(vec));
    }
}

I get the same output. I run through the debugger (great tip BTW!) and see the values get added and everything seems to be running properly.

Shai Almog

unread,
Nov 21, 2013, 12:55:26 AM11/21/13
to codenameone...@googlegroups.com

I suggest you use a debugger and check your code since you are overriding your model somewhere. This is a simple hello world hand coded application:

package com.mycompany.myapp;


import com.codename1.ui.Display;
import com.codename1.ui.Form;
import com.codename1.ui.Label;
import com.codename1.ui.layouts.BorderLayout;
import com.codename1.ui.list.DefaultListModel;
import com.codename1.ui.list.MultiList;
import com.codename1.ui.plaf.UIManager;
import com.codename1.ui.util.Resources;
import java.io.IOException;
import java.util.Hashtable;
import java.util.Vector;

public class MyApplication {


   
private Form current;

   
public void init(Object context) {
       
try{
           
Resources theme = Resources.openLayered("/theme");
           
UIManager.getInstance().setThemeProps(theme.getTheme(theme.getThemeResourceNames()[0]));
       
}catch(IOException e){
            e
.printStackTrace();
       
}
   
}

   
   
public void start() {

       
if(current != null){
            current
.show();
           
return;
       
}

       
Form hi = new Form("Hi World");
        hi
.setLayout(new BorderLayout());
       
MultiList ml = new MultiList();

       
Vector<Hashtable> vec = new Vector<Hashtable>();
       
for(int iter = 0 ; iter < 10 ; iter++) {

           
Hashtable a = new Hashtable();
            a
.put("Line1", "XXXX");
            vec
.add(a);
       
}

        ml
.setModel(new DefaultListModel(vec));
       
        hi
.addComponent(BorderLayout.CENTER, ml);
        hi
.show();

   
}

   
public void stop() {
        current
= Display.getInstance().getCurrent();
   
}
   
   
public void destroy() {
   
}

}




Danny et Diane

unread,
Nov 21, 2013, 12:54:21 PM11/21/13
to codenameone...@googlegroups.com
I think I may have found the issue. In your code sample, you're using MultiList whereas I'm using simple List in mine.

I went and retrofitted my code with MultiList and voilà, all good.

Would like to note that some labels, in Large font, are being cut though. For instance, the letter "g". Furthermore, when I click on it, I see a box at the left, and the item's text moves to the left. Is this normal? It exhibits the same traits as your sample.

Regards,

Dan


--
You received this message because you are subscribed to a topic in the Google Groups "CodenameOne Discussions" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/codenameone-discussions/_jP7jWInXl4/unsubscribe.
To unsubscribe from this group and all its topics, send an email to codenameone-discu...@googlegroups.com.
Visit this group at http://groups.google.com/group/codenameone-discussions.
To view this discussion on the web visit https://groups.google.com/d/msgid/codenameone-discussions/9c2edf55-3d4c-4606-b112-beaf358444a6%40googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

Shai Almog

unread,
Nov 21, 2013, 2:28:44 PM11/21/13
to codenameone...@googlegroups.com
Sorry about that, I was pretty sure you mentioned MultiList along the way but too many concurrent discussion threads. Can't keep continuity (feels like George R.R Martin).
The g's decent might be a Java2D issue (desktop only) moving a bitto the right might be related to selected stying. Hard to tell from the description.
You need to style these elements in the theme in a similar way.
Reply all
Reply to author
Forward
0 new messages