пятница, 27 августа 2010 г.

ListView Multiple Selection 2

After reading the post about ListView Multiple Selection I started integrating a new ListView it into my project, but in few minutes I figured out that there were no normal key multiple selection support. So, I decided to fix this problem.

The problem

At first I was thinking about overriding callActionForEvent(e:KeyEvent):Void method in com.sun.javafx.scene.control.behavior.ListViewBehavior, but I've found out that it's not so simple. After applying a new behavior for a ListView in ListViewSkin like this:
class ListSkin extends ListViewSkin {
     override var behavior = ListBehavior{}
     ...
}
The ListView stops scrolling up/down if I use keys for navigation. I was thinking about putting some parameters in ListBehavior, but still no luck.

Workaround

As for workaround you'll need to put some extra code in MultiSelectListView class
override var onKeyPressed = function(e:KeyEvent):Void {
if ((e.code == KeyCode.VK_UP) or (e.code == KeyCode.VK_DOWN)
        or (e.code == KeyCode.VK_PAGE_DOWN) or (e.code == KeyCode.VK_PAGE_UP)
        or (e.code == KeyCode.VK_HOME) or (e.code == KeyCode.VK_END)) {
        if (not e.controlDown and not e.shiftDown) {
           delete selectedIndexes;
        }            
    }
    if (e.controlDown and e.code == KeyCode.VK_SPACE) {
        def index:Integer = (e.node as List).focusedIndex;
        if (-1 == Sequences.indexOf(selectedIndexes, index)) {
            insert index into selectedIndexes;
        } else {
            delete index from selectedIndexes;
            if (index == selectedIndex) select(selectedIndexes[0]);
        }
    }
}

As a result

  • So, now you are able to use Ctrl+Space to add new selected items or remove old items from selection.
  • If you use keys such as: HOME, END, PAGE_UP, PAGE_DOWN, UP or DOWN without holding a Ctrl button multiple selection will be lost(I think it's user expected behavior :) )
  • I didn't do anything with Shift key. So, it does nothing.

Small fix

The ListView by Jonathan Giles has a small bug, it's also about using keys for selecting items. In MultiSelectListCellBehavior class, he has a case for deselecting all previously selected items.
public class MultiSelectListCellBehavior extends ListCellBehavior {
    ...
    override function mouseReleased(e) {
       ...
       } else {
           delete listView.selectedIndexes;
           insert row into listView.selectedIndexes;
           listView.focus(row);
       }
    }
}
As you can see in the end he used listView.focus(row); for selecting an item, but this stuff wont wok correctly. If you made some selection with the help of keys you'll get at least two selected items. So, I think it's better to use listView.select(row); instead.

Have a nice code :)

понедельник, 23 августа 2010 г.

Creating Pseudo-class in JavaFx (TabPanel control)

Before reading this article I suggest to read Advanced JavaFX Control Styling by Dean Iverson.
After JavaFx 1.3 has been published we've got a new bunch of controls and a new way to produce our own ones and, of course, don't forget about new CSS support in this release. So, today I'm going to show you how to create your own JavaFx controls with CSS support.

Specification

Currently there are no tabs in JavaFx, and that's a good reason to start having them :). At the end I expect to get such a result:

Specification itself:

  • TabPanel has TabButtons
  • You are able to select only one TabButton in TabPanel
  • You are able to set styles for TabButtons and TabPanel
  • TabButtons can be aligned inside TabPanel
  • TabButtons can fit TabPanel by width, height or both
  • TabPanel can set common width or height for TabButtons

Implementation

I'm not going to explain all the code, but I'll stop in most interesting parts:

Creating your own pseudo-class

Each TabButton should have a status selected in order to display the button as selected and not to perform a click action on it.
public class TabButton extends ButtonBase, Labeled {
    
    override var skin = SkinAdapter {
        rootRegion:ButtonSkin{}
    }

    public var selected:Boolean = false on replace {
        impl_pseudoClassStateChanged("selected");
    }

    override function impl_getPseudoClassState():String[] {
        return [
            if (selected) "selected" else null,
            super.impl_getPseudoClassState()
        ]
    }
}
This is just a piece of the TabButton class. As you can see, I use two functions impl_pseudoClassStateChanged and impl_getPseudoClassState that will help me to organize my pseudo-class &quote;selected&quote; for this control.
Now let's take a look at the CSS file:
.tab-button {
    -fx-padding: 3 10 2 10;
    -fx-background-color: -fx-shadow-highlight-color, -fx-outer-border, -fx-inner-border, -fx-body-color;
    -fx-background-radius: 10 10 0 0;
    -fx-background-insets: 0 0 -1 0, 0, 1, 2;
    -fx-border-radius: 10 10 0 0;
    -fx-border-color: -fx-outer-border, -fx-inner-border;
    -fx-border-style: solid;
    -fx-text-fill: -fx-text-base-color;
    -fx-vpos: CENTER;
    -fx-graphic-vpos: CENTER;
}

.tab-button:hover {
    -fx-color: -fx-hover-base;
}

.tab-button:selected {
    -fx-color: -fx-pressed-base;
}
Please pay attention, that now I can use keyword selected as a name of the new pseudo-class for my control. Also I can use standard pseudo-classes of Button and Labeled controls cause I invoke a super.impl_getPseudoClassState() highlighted on line 14.
Now look at line 4, in order not to write my own Skin class, for this control, I'm using an existing one.

Source

To download a full source code press here

Have a nice code :)