Select multiple ListView item without pressing CTRL (Shortcut key) - javafx

I'm trying to use a ListView in my application, which has to run on a Windows tablet. The problem is that, to select multiple item on a ListView, the user has to maintain the CTRL key pressed, which is impossible on a tablet.
So my question is : Is there a way to select multiple item in a ListView with a simple click on it?

You may filter the normal mouse Click event and convert it to Ctrl+Click. Actually to Shortcut+Click since the shortcut key may differ on the platform the app run.
EventHandler<MouseEvent> eventHandler = ( event ) ->
{
if ( !event.isShortcutDown() )
{
Event.fireEvent( event.getTarget(), cloneMouseEvent( event ) );
event.consume();
}
};
listview.getSelectionModel().setSelectionMode( SelectionMode.MULTIPLE );
listview.addEventFilter( MouseEvent.MOUSE_PRESSED, eventHandler );
listview.addEventFilter( MouseEvent.MOUSE_RELEASED, eventHandler );
where cloneMouseEvent is
private MouseEvent cloneMouseEvent( MouseEvent event )
{
switch (Toolkit.getToolkit().getPlatformShortcutKey())
{
case SHIFT:
return new MouseEvent(
event.getSource(),
event.getTarget(),
event.getEventType(),
event.getX(),
event.getY(),
event.getScreenX(),
event.getScreenY(),
event.getButton(),
event.getClickCount(),
true,
event.isControlDown(),
event.isAltDown(),
event.isMetaDown(),
event.isPrimaryButtonDown(),
event.isMiddleButtonDown(),
event.isSecondaryButtonDown(),
event.isSynthesized(),
event.isPopupTrigger(),
event.isStillSincePress(),
event.getPickResult()
);
case CONTROL:
return new MouseEvent(
event.getSource(),
event.getTarget(),
event.getEventType(),
event.getX(),
event.getY(),
event.getScreenX(),
event.getScreenY(),
event.getButton(),
event.getClickCount(),
event.isShiftDown(),
true,
event.isAltDown(),
event.isMetaDown(),
event.isPrimaryButtonDown(),
event.isMiddleButtonDown(),
event.isSecondaryButtonDown(),
event.isSynthesized(),
event.isPopupTrigger(),
event.isStillSincePress(),
event.getPickResult()
);
case ALT:
return new MouseEvent(
event.getSource(),
event.getTarget(),
event.getEventType(),
event.getX(),
event.getY(),
event.getScreenX(),
event.getScreenY(),
event.getButton(),
event.getClickCount(),
event.isShiftDown(),
event.isControlDown(),
true,
event.isMetaDown(),
event.isPrimaryButtonDown(),
event.isMiddleButtonDown(),
event.isSecondaryButtonDown(),
event.isSynthesized(),
event.isPopupTrigger(),
event.isStillSincePress(),
event.getPickResult()
);
case META:
return new MouseEvent(
event.getSource(),
event.getTarget(),
event.getEventType(),
event.getX(),
event.getY(),
event.getScreenX(),
event.getScreenY(),
event.getButton(),
event.getClickCount(),
event.isShiftDown(),
event.isControlDown(),
event.isAltDown(),
true,
event.isPrimaryButtonDown(),
event.isMiddleButtonDown(),
event.isSecondaryButtonDown(),
event.isSynthesized(),
event.isPopupTrigger(),
event.isStillSincePress(),
event.getPickResult()
);
default: // well return itself then
return event;
}
}

Related

How to hide action button when QLineEdit is disabled?

I have a class which is inherited from QLineEdit, and I set an icon as an action button for this.
MyEdit::MyEdit( QWidget *p_parent ) : QLineEdit( p_parent )
{
m_buttonAction = addAction( QIcon( "search.png" ), QLineEdit::TrailingPosition );
QAbstractButton *button = qobject_cast<QAbstractButton *>( m_buttonAction->associatedWidgets().last() );
m_buttonAction->setVisible( false );
connect( m_buttonAction, &QAction::triggered, this, &MyEdit::openCompleter );
m_completer = new QCompleter( this );
m_sourceModel = new CompleterSourceModel( m_completer );
m_view = new CompleterView();
m_view->setStyle( &m_style );
m_delegate = new CompleterDelegate( m_view );
m_completer->setPopup( m_view );
m_completer->setModel( m_sourceModel );
m_view->setItemDelegate( m_delegate );
setCompleter( m_completer );
}
void MyEdit::setDataForCompleter( const CompleterData &p_data )
{
m_sourceModel->setCompleterData( p_data );
m_buttonAction->setVisible( p_data.data().size() > 0 );
}
When I import data for completer, the icon is always shown. Now I need to hide this icon in case MyEdit is disabled or as ReadOnly.
I am thinking about override setDisabled and setReadOnly for my class, and in there setVisible for the icon. But these functions are not virtual, so can not be overridden.
I am thinking also about a signal like stateChanged of my class, so I can do it in a slot. But I can not find any signal like that for QLineEdit. Do you have any idea how to do it?
You can handle events QEvent::ReadOnlyChange or QEvent::EnabledChange by overriding the QLineEdit::event method
UPDATE:
Here is an example implementation:
bool MyEdit::event(QEvent *e) override {
const auto type = e->type();
if (type == QEvent::ReadOnlyChange || type == QEvent::EnabledChange) {
m_buttonAction->setVisible(m_sourceModel->rowCount() > 0 ? isEnabled() && isReadOnly() : false);
}
return QLineEdit::event(e);
}

javafx - TableView Nested UI elements looses reference data object on dynamic addition of row

I'm trying to create an API, which can dynamically populate tableview with different Nested UI components in each cell of tableview. I'm able to bind the data set to Model object with the table.
The problem is when I try to dynamically add and try to enable the edit, the object reference is seems to messed up.
FYI:
As you see I've last column with 4 buttons, which is Add, Edit, Delete, Reset features. Once click on Add - it clones the current row, click on edit - it enable the ComboBox of Coulmn Category, click on delete - it deletes current row.
What I face is that upon adding multiple entries, I do get the row added dynamically, but then click on first row edit button - then multiple ComboBox is enabled, which is not intended use. The use case is current row's ComboBox must be only be enabled.
Implementation: I've written custom API which extends TableView<S>.
Following snippet may help:
//column category
final ClumpElement< ConstraintsDataModel, String > categoryElement =
new ClumpElement<>( ClumpType.COMBOBOX, true, getCategoryData() );
categoryElement.setClumpTableCellValue( data -> data.categoryProperty() );
categoryElement.setClumpTableNodeAction( ( control, data ) -> {
final ComboBox< String > comboBox = (ComboBox< String >)control;
comboBox.disableProperty().bind( data.disableProperty() );
} );
clumpTableView.addNewColumn( "Category", categoryElement );
// column Action
final ClumpElement< ConstraintsDataModel, String > buttonsElement =
new ClumpElement<>( ClumpType.GROUP_BUTTONS, 4, "+", "✎", "X", "↻" );
buttonsElement.setClumpTableNodeAction( ( control, data ) -> {
final Button button = (Button)control;
switch( button.getText() ) {
case "+":
final ConstraintsDataModel ref =
clumpTableView.getItems().get( clumpTableView.getItems().size() - 1 );
if( ConstraintsDataModel.isValidModel( ref ) )
clumpTableView.getItems().add( new ConstraintsDataModel( data ) );
else
System.out.println( "ERROR: Finish previous constraints" );
break;
case "✎":
data.setDisableValue( false );
button.setText( "✔" );
break;
case "✔":
data.setDisableValue( true );
button.setText( "✎" );
break;
default:
//NOTHING
break;
}
} );
clumpTableView.addNewColumn( "Action", buttonsElement );
clumpTableView.setItems( getData() );
This is my CustomTableView class:
public < T > void addNewColumn( final String columnName, final ClumpElement< S, T > element ) {
final TableColumn< S, T > column = new TableColumn<>( columnName );
getColumns().add( column );
if( element.getClumpTableCellValue() != null ) {
column.setCellValueFactory( param -> element.getClumpTableCellValue()
.act( param.getValue() ) );
}
clumpCellCall( columnName, element, column );
}
private < T > void clumpCellCall( final String colName, final ClumpElement< S, T > element,
final TableColumn< S, T > column ) {
switch( element.getUiNode() ) {
case COMBOBOX:
if( element.getItems() != null && !element.getItems().isEmpty() ) {
column.setCellFactory( param -> {
final ClumpComboBoxTableCell< S, T > clumpComboBoxTableCell =
new ClumpComboBoxTableCell<>( element.isDisable(), element.getItems() );
clumpComboBoxTableCell.prefWidthProperty().bind( column.widthProperty() );
clumpComboBoxTableCell.selectionListener( element );
return clumpComboBoxTableCell;
} );
}
break;
case GROUP_BUTTONS:
column.setCellFactory( param -> {
final ClumpButtonsTableCell< S, T > clumpButtonsTableCell =
new ClumpButtonsTableCell<>( element.getNoOfElements() );
clumpButtonsTableCell.prefWidthProperty().bind( column.widthProperty() );
IntStream.range( 0, element.getNoOfElements() ).forEach( item -> {
final Button button = clumpButtonsTableCell.getButtons().get( item );
button.setText( element.getNames().get( item ) );
button.setOnAction( event -> {
if( element.getClumpTableNodeAction() != null
&& clumpButtonsTableCell.getIndex() < getItems().size() ) {
element.getClumpTableNodeAction()
.act( button, getItems().get( clumpButtonsTableCell.getIndex() ) );
}
} );
} );
return clumpButtonsTableCell;
} );
break;
default:
column.setCellFactory( params -> {
final TextFieldTableCell< S, T > textFieldTableCell = new TextFieldTableCell<>();
textFieldTableCell.setConverter( new StringConverter< T >() {
#Override
public String toString( final T object ) {
return (String)object;
}
#Override
public T fromString( final String string ) {
return (T)string;
}
} );
return textFieldTableCell;
} );
break;
}
}
In my custom API, which shall invoke a custom TableCell<S,T> which has ComboBox<T> pretty standard implementation as per docs. Here its inside a selection listener, as I found that when the cell renders, only this selection listener is called.
public abstract class AbstractClumpTableCell< S, T > extends TableCell< S, T > {
public AbstractClumpTableCell() {
setContentDisplay( ContentDisplay.GRAPHIC_ONLY );
setAlignment(Pos.CENTER);
}
public abstract void renewItem( T item );
#Override
protected void updateItem( T item, boolean empty ) {
super.updateItem( item, empty );
if( empty ) {
setGraphic( null );
} else {
renewItem( item );
}
}
}
public class ClumpComboBoxTableCell< S, T > extends AbstractClumpTableCell< S, T > {
private final ComboBox< T > comboBox;
#SuppressWarnings( "unchecked" )
public ClumpComboBoxTableCell( final boolean isDisable, final ObservableList< T > item ) {
super();
this.comboBox = new ComboBox<>( item );
this.comboBox.setDisable( isDisable );
this.comboBox.valueProperty().addListener( ( obs, oVal, nVal ) -> {
ObservableValue< T > property = getTableColumn().getCellObservableValue( getIndex() );
if( property instanceof WritableValue ) {
((WritableValue< T >)property).setValue( nVal );
}
} );
}
#Override
public void renewItem( T item ) {
comboBox.setValue( item );
setGraphic( comboBox );
}
public ComboBox< T > getComboBox() {
return comboBox;
}
protected void selectionListener( final ClumpElement< S, T > element ) {
this.comboBox.getSelectionModel().selectedItemProperty().addListener( ( obs, oVal, nVal ) -> {
if( element.getClumpTableNodeAction() != null
&& getIndex() < getTableView().getItems().size() ) {
element.getClumpTableNodeAction().act( this.comboBox,
getTableView().getItems().get( getIndex() ) );
}
} );
}
}
And my Data Model has a SimpleStringProperty that is binded to the column accordingly.
So, How can I bind the Nested UI elements correctly rowise within the TableView<S>? Is my approach right or is there alternatives?
I will make an attempt to answer, but as I said the code is hard for me to follow (especially as it is partial, so some methods I can only assume the purpose of).
The issue, as stated in the comments, is node virtualization in TableView. You can't go around it, and you really don't want to - it is a means to vastly improve performance, as you don't need hundreds or thousands of UI nodes (which are "heavy" and degrade performance), but only enough to fill the displayed portion of the table, thus supporting a much larger dataset.
The problem, as far as I can see, as that you have some property of the row (is it currently editable or not) which you need to be reflected in certain columns. More specifically, you want the combo box's disable property to always reflect the disable property of the row it pertains to, so in updateItem you will have to do something like this:
#Override
protected void updateItem(T item, boolean empty) {
super.updateItem(T, empty);
if (empty) {
setGraphic(null);
} else {
renewItem(item);
// since the disable property if given by the row value, not only the column value
// we need to get the row value. The cast is needed due to a design oversight
// in JavaFX 8, which is fixed in newer versions. See https://bugs.openjdk.java.net/browse/JDK-8144088
ConstraintsDataModel data = ((TableRow<ConstraintsDataModel>)getTableRow())
.getItem();
combobox.disableProperty().unbind();
combobox.disableProperty().bind(data.disableProperty());
}
}
This is assuming your row data type is indeed ConstaintDataModel, I couldn't quite follow through.
Another option which may be more elegant is to use the editing property of a row - bind the combo box's disable property to the negation of the editing property of the row, and use startEdit and cancelEdit/commitEdit when you start and end editing. This way you will not have to re-bind the disable property of the combo box, as it will always refer to the correct row.

Bindings.and() in JavaFX not firing?

On startup my JavaFX app fires off two background tasks, one to initialize the database and the other to authenticate against an email server. Each task has a Boolean property indicating successful completion. Once both of those tasks complete I want to start a third task to start polling for new emails.
I thought I could use javafx.beans.binding.Bindings.and() to combine the result of these two tasks, but it's not firing. Here's some code:
BooleanBinding databaseInitializedProperty = NotesLocalDatabase.initializationStateProperty().isEqualTo( State.SUCCEEDED );
BooleanProperty isLoggedInProperty = loginPanel.isLoggedInProperty();
databaseInitializedProperty.addListener( (value, oldVal, newVal ) -> {
if( newVal ) log.info( "DATABASE INITIALIZED" ); } );
isLoggedInProperty.addListener( (value, oldVal, newVal ) -> {
if( newVal ) log.info( "IS LOGGED IN" ); } );
Bindings.and( databaseInitializedProperty, isLoggedInProperty ).addListener( (value, oldVal, newVal) -> {
log.info( "COMPOUND BINDING CHANGED: " + value + ", " + oldVal + ", " + newVal );
if( newVal ) {
log.info( "Login and database initialization both complete. Starting message check service." );
startUpdates();
}
} );
I know my properties are working correctly because I see both "DATABASE INITIALIZED" and "IS LOGGED IN" in the logs. However, I never see "COMPOUND BINDING CHANGED". From reading the docs I was under the impression that the change listener on Bindings.and() binding be fired whenever either property changes. Is that not the case?
You are running into another version of this: Bidirectional JavaFX Binding is destroyed by unrelated code
The problem is that the result of Bindings.and(...) has gone out of scope by the time the properties change. This makes it eligible for garbage collection. It's not unreasonable to assume your tasks are consuming enough memory to force the garbage collector to run, so the binding gets destroyed.
For a simple example, run the following with and without the call to System.gc();:
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
public class BindingTest {
public static void main(String[] args) {
BooleanProperty x = new SimpleBooleanProperty(false);
BooleanProperty y = new SimpleBooleanProperty(false);
x.addListener((obs, oldX, newX) -> System.out.printf("x changed from %s to %s%n", oldX, newX));
y.addListener((obs, oldY, newY) -> System.out.printf("y changed from %s to %s%n", oldY, newY));
Bindings.and(x, y).addListener((obs, oldXAndY, newXAndY) -> System.out.printf("x&y changed from %s to %s%n", oldXAndY, newXAndY));
x.set(true);
// System.gc();
y.set(true);
}
}
The fix is to force the binding to stay in scope. You may need to (somewhat unnaturally) make it an instance variable somewhere:
public class SomeClass {
private BooleanBinding initializationComplete ;
public void whereverYouHadThisCodeBefore() {
// ..
initializationComplete = Bindings.and(databaseInitializedProperty, isLoggedInProperty);
initializationComplete.addListener(...);
}
}

JavaFX 8.0: How to change the focus traversal key?

The regular focus traversal keys are TAB for moving focus forward and SHIFT+TAB for moving focus backward. And now I want to use ENTER key instead of TAB. Are there any idea for this?
Qinn
This is how I have done it:
rootNode.addEventFilter( KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>()
{
Robot eventRobot = new Robot();
#Override
public void handle( KeyEvent KV )
{
switch ( KV.getCode() )
{
case ENTER :
{
if ( ! (KV.getTarget() instanceof TextArea) )
{
eventRobot.keyPress( java.awt.event.KeyEvent.VK_TAB );
eventRobot.keyRelease( java.awt.event.KeyEvent.VK_TAB );
KV.consume();
}
break;
}
case TAB :
{
if ( ! (KV.getTarget() instanceof TextArea) )
{
KV.consume();
}
break;
}
}
}
}
I Think it would be better off if you use two classes:
KeyCombination for a more precise key checking along with
KeyboardShortcutsHandler to move focus.
So there's no need to use a Robot to send tab key
final KeyCombination ENTER = new KeyCodeCombination(KeyCode.ENTER);
rootNode.addEventFilter(KeyEvent.KEY_PRESSED, (KeyEvent KV) -> {
EventTarget tgt = KV.getTarget();
if (ENTER.match(KV)) {
KeyboardShortcutsHandler ksh = new KeyboardShortcutsHandler();
ksh.traverse((Node) tgt, Direction.NEXT);
KV.consume();
} else if ( ! (KV.getTarget() instanceof TextArea) ) {
KV.consume();
}
});

Mouse Button click wont register

var note : String = "blah"
function OnGUI()
{
if (IsPaused)
{
GUI.Box (Rect (580,20,100,210), "Pause");
if (GUI.Button (Rect (590,130,80,20), "Info"))
{
Debug.Log("I pressed it");
GUI.Button (Rect (450, 200, Screen.width/2, Screen.height/2), info);
}
else if (GUI.Button (Rect (590,160,80,20), "Quit"))
{
Application.Quit();
}
else if(GUI.Button (Rect(590,190,80,35), "Back"))
{
IsPaused = false;
GetComponent(LookAtMouse).enabled = true;
}
}
This is my function for my GUI, as you can see, i have three buttons within it. The quit and back buttons work fine but when i click the info button, nothing will register.
I put GUI.Button (Rect (450, 200, Screen.width/2, Screen.height/2), info); This line of code outside the if statement to check if it works and it will but when its inside the statement i get nothing.
Can anyone tell me why?
var displayInfo : boolean = false;
if (IsPaused)
{
GUI.Box (Rect (580,20,100,210), "Pause");
displayInfo = GUI.Toggle(Rect (590,130,80,20), displayInfo, "Info");
{
if(displayInfo)
{
GUI.Button (Rect (450, 200, Screen.width/2, Screen.height/2), info);
}
}
#rutter thanks for the idea, i used the boolean idea you had and created a toggle to check, if the info is displayed, create the instance of the textfield when it was toggled on

Resources