Context
I have implemented a NatTable (v1.1.0.201405012245) - please consider this simplified example:
package testproject;
import java.util.ArrayList;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.nebula.widgets.nattable.NatTable;
import org.eclipse.nebula.widgets.nattable.config.AbstractRegistryConfiguration;
import org.eclipse.nebula.widgets.nattable.config.ConfigRegistry;
import org.eclipse.nebula.widgets.nattable.config.DefaultNatTableStyleConfiguration;
import org.eclipse.nebula.widgets.nattable.config.EditableRule;
import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;
import org.eclipse.nebula.widgets.nattable.data.IColumnAccessor;
import org.eclipse.nebula.widgets.nattable.data.IDataProvider;
import org.eclipse.nebula.widgets.nattable.data.ListDataProvider;
import org.eclipse.nebula.widgets.nattable.edit.EditConfigAttributes;
import org.eclipse.nebula.widgets.nattable.edit.config.DefaultEditBindings;
import org.eclipse.nebula.widgets.nattable.edit.config.DefaultEditConfiguration;
import org.eclipse.nebula.widgets.nattable.edit.editor.TextCellEditor;
import org.eclipse.nebula.widgets.nattable.grid.GridRegion;
import org.eclipse.nebula.widgets.nattable.layer.DataLayer;
import org.eclipse.nebula.widgets.nattable.layer.LabelStack;
import org.eclipse.nebula.widgets.nattable.layer.cell.IConfigLabelAccumulator;
import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer;
import org.eclipse.nebula.widgets.nattable.selection.config.DefaultSelectionStyleConfiguration;
import org.eclipse.nebula.widgets.nattable.style.DisplayMode;
import org.eclipse.nebula.widgets.nattable.viewport.ViewportLayer;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.part.ViewPart;
public class ViewPart1 extends ViewPart
{
#Override
public void createPartControl(final Composite parent)
{
final ArrayList<String> list = new ArrayList<>();
list.add("one");
list.add("two");
list.add("three");
final IColumnAccessor<String> columnAccessor = new IColumnAccessor<String>()
{
#Override
public void setDataValue(final String rowObject, final int columnIndex, final Object newValue)
{
if (!(newValue instanceof String) || ((String) newValue).contains("x"))
{
MessageDialog.openError(getSite().getShell(), "Error", "Invalid Input");
return;
}
list.set(list.indexOf(rowObject), (String) newValue);
}
#Override
public Object getDataValue(final String rowObject, final int columnIndex)
{
return rowObject;
}
#Override
public int getColumnCount()
{
return 1;
}
};
final IDataProvider dataProvider = new ListDataProvider<>(list, columnAccessor);
final DataLayer dataLayer = new DataLayer(dataProvider);
final SelectionLayer selectionLayer = new SelectionLayer(dataLayer);
final ViewportLayer viewportLayer = new ViewportLayer(selectionLayer);
final NatTable table = new NatTable(parent, viewportLayer, false);
GridDataFactory.fillDefaults().grab(true, true).applyTo(table);
viewportLayer.addConfiguration(new DefaultEditConfiguration());
viewportLayer.addConfiguration(new DefaultEditBindings());
viewportLayer.setRegionName(GridRegion.BODY);
viewportLayer.setConfigLabelAccumulator(new IConfigLabelAccumulator()
{
#Override
public void accumulateConfigLabels(final LabelStack configLabels, final int columnPosition, final int rowPosition)
{
configLabels.addLabel("myLabel");
}
});
table.setConfigRegistry(new ConfigRegistry());
table.addConfiguration(new DefaultNatTableStyleConfiguration());
table.addConfiguration(new DefaultSelectionStyleConfiguration());
table.addConfiguration(new AbstractRegistryConfiguration()
{
#Override
public void configureRegistry(final IConfigRegistry registry)
{
registry.registerConfigAttribute(EditConfigAttributes.CELL_EDITOR, new TextCellEditor(true), DisplayMode.NORMAL, "myLabel");
registry.registerConfigAttribute(EditConfigAttributes.CELL_EDITABLE_RULE, EditableRule.ALWAYS_EDITABLE, DisplayMode.NORMAL, "myLabel");
}
});
table.configure();
}
#Override
public void setFocus()
{
}
}
Of course, this is not the real code, but my issue can be demonstrated with this code as well.
The important part is that in my actual project, when the user modifies a value, I need to update a model (including a complex tree of dependencies) and if that fails (e.g. in a numerical calculation the change results in a division by zero), I need to show an error (and revert to the previous value).
To show my core problem, in the code shown here I check for a simple condition in IColumnAccessor#setDataValue (the input contains an 'x') and show the error accordingly.
Problem
My actual problem is that if you enter an x into the TextCellEditor, the error dialog pops up twice (in sequence - meaning that as soon as I click ok for the first one, the second one will show).
Analysis
My analysis shows that the reason is that setDataValue is called twice:
because the ENTER key is pressed - Stacktrace
TextCellEditor(AbstractCellEditor).commit(SelectionLayer$MoveDirectionEnum, boolean) line: 331
TextCellEditor(AbstractCellEditor).commit(SelectionLayer$MoveDirectionEnum) line: 326
TextCellEditor$1.keyPressed(KeyEvent) line: 246
because the TextCellEditor loses focus - Stacktrace
TextCellEditor(AbstractCellEditor).commit(SelectionLayer$MoveDirectionEnum, boolean, boolean) line: 341
TextCellEditor(AbstractCellEditor).commit(SelectionLayer$MoveDirectionEnum, boolean) line: 331
AbstractCellEditor$InlineFocusListener.focusLost(FocusEvent) line: 462
So, my main question is: how can I prevent (or at least detect) the second event?
The issue with your implementation is, that you are performing a conversion in the IColumnAccessor and open a dialog to inform the user about the error. But that is not the way to do this with NatTable because of various use cases.
If you need to perform conversion and/or validation you should register an appropriate IDisplayConverter and an IDataValidator. As you need a String you don't need to register a different converter, as the default converter that is registered via DefaultEditConfiguration is doing that already. So what you need is an IDataValidator that checks for the value x and throws a ValidationFailedException in that case. If you register the DialogErrorHandling as validation error handler, the error with the exception message will be shown in a dialog. And the checks for not opening the dialog twice is done internally.
This is explained in the (currently small) documentation http://www.eclipse.org/nattable/documentation.php?page=editing
BTW, I suggest to update to the latest NatTable release 1.3.0 as it also contains several bugfixes.
Related
Reproduced in OpenJFX 11.0.2 & 12.0.1 SDK (Windows 10, x64), not reproducible in JavaFX 8
Right-click on a table-column, then try to resize the column. No resize cursor is shown and column can't be resized until you manually click on the column again.
Any ideas for a workaround? I need to usecontextMenu for TableColumns, so potential workarounds that make the header ignore right mouse click aren't possible.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.stage.Stage;
public class Foo extends Application {
#Override
public void start(Stage stage) throws Exception {
TableView<Object> testView = new TableView<>();
testView.getColumns().addAll(new TableColumn<Object, Object>("C1"), new TableColumn<Object, Object>("C2"), new TableColumn<Object, Object>("C3"));
stage.setScene(new Scene(testView));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Ok I found the following (very, very dirty) workaround. I never tried this before because I assumend it would prevent the context menu from showing (as I noted in my original question), but apprently simply consuming the mouse event of every TableColumnHeader works and the context menu is still shown correctly (also works with TableColumns without context menus).
Not sure if anything internal could go wrong with this, but as the right click doesn't seem to be doing anything useful by default, I hope not.
Of course lookupAll needs to be called after it has been rendered.
Note 1: If you have TableMenuButtonVisible set to true, you need to do this every time a column is set to visible.
Note 2: Its getting dirtier and dirtier. Simply calling this again after a column has been set to visible (see note 1) doesn't always suffice (also not with a Platform.runLater call). I assume that's because the column header hasn't been rendered at that point. You either
need to wait until the Set<Node> is fully filled, i.e. the size of
it must be amountOfVisibleColumns + 1. If its equal to the amount
of visible columns, it won't work for the newly shown column.
or call layout() on the TableView before lookupAll
or if you have a class that extends TableView, override layoutChildren and execute the lookup if the amount of visible columns has changed
Note 3: You need to keep track of the old onMousePressed and execute it if the button isn't SECONDARY, otherwise the reordering of columns won't work.
Please let me know if you can think of any cleaner way.
import java.util.Set;
import javafx.application.Application;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.skin.TableColumnHeader;
import javafx.scene.input.MouseButton;
import javafx.stage.Stage;
public class Foo extends Application {
#Override
public void start(Stage stage) throws Exception {
TableView<Object> testView = new TableView<>();
testView.getColumns().addAll(createColumn("C1"), createColumn("C2"), createColumn("C3"));
stage.setOnShown(ev -> {
Set<Node> headers = testView.lookupAll("TableColumnHeader");
for (Node header : headers) {
if (header != null) {
((TableColumnHeader) header).setOnMousePressed(e -> {
if (e.getButton() == MouseButton.SECONDARY) {
e.consume();
}
});
}
}
});
stage.setScene(new Scene(testView));
stage.show();
}
private TableColumn<Object, Object> createColumn(String text) {
MenuItem item = new MenuItem("Context");
item.setOnAction(e -> {
System.out.println("Action");
});
ContextMenu contextMenu = new ContextMenu();
contextMenu.getItems().add(item);
TableColumn<Object, Object> column = new TableColumn<>(text);
column.setContextMenu(contextMenu);
return column;
}
public static void main(String[] args) {
launch(args);
}
}
EDIT: Found the described bug in the Java bug tracker and filed a PR with the fix:
https://github.com/openjdk/jfx/pull/483
EDIT 2: My PR was accepted and merged back. The bug is fixed now, you can test it by using 17-ea+11. :-)
I have the same problem. This bug is caused by the mousePressedHandler added in TableColumnHeader. This class has even more problems, for example if I close a PopupControl with setConsumeAutoHidingEvents(true) by a click on a column, the sorting will be triggered. Those methods needs to be changed, maybe the addEventHandler methods should be used instead of the convenience setOn... methods.
I fixed it by consuming the event when I'm about to show my PopupControl:
public class MyTableColumnHeader extends TableColumnHeader {
public MyTableColumnHeader(TableColumnBase tc) {
super(tc);
addEventHandler(MouseEvent.MOUSE_PRESSED, this::onMousePressed);
}
private void onMousePressed(MouseEvent mouseEvent) {
if (mouseEvent.getButton() == MouseButton.SECONDARY) {
showPopup();
// Consume here, so the column won't get 'stuck'.
mouseEvent.consume();
}
}
private void showPopup() {
...
}
}
Eventually, someone should open at least a bug. I may will also have a look in the not too distant future.
I am trying to add a texture to an item, yet the texture just doesn't appear. I have the texture made, and in the right file directory, but while in game it doesn't display. Thus, I think it's an error in my code.
For the whole class file, see below:
package Moonstone;
import cpw.mods.fml.common.Mod;
import cpw.mods.fml.common.Mod.EventHandler;
import cpw.mods.fml.common.event.FMLInitializationEvent;
import cpw.mods.fml.common.event.FMLPostInitializationEvent;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import cpw.mods.fml.common.registry.GameRegistry;
import net.minecraft.creativetab.CreativeTabs;
import net.minecraft.init.Items;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraftforge.common.util.EnumHelper;
#Mod(modid = "ms", name = "Moonstone", version = "1.0")
public class MoonstoneMain {
public static Item moonstone;
#EventHandler
public void preInit(FMLPreInitializationEvent event) {
//All init
moonstone = new Moonstone().setUnlocalizedName("Moonstone").setTextureName("moonstone").setMaxStac kSize(64);
GameRegistry.registerItem(moonstone, moonstone.getUnlocalizedName().substring(5));
}
#EventHandler
public void init(FMLInitializationEvent event) {
//Proxy, TileEntity, entity, GUI and packet registering
}
#EventHandler
public void postInit(FMLPostInitializationEvent event) {
}
public static CreativeTabs tabMoonstone = new CreativeTabs("tabMoonstone"){
#Override
public Item getTabIconItem(){
return new ItemStack(Items.stick).getItem();
}
};
}
For just the item, look below-
moonstone = new Moonstone().setUnlocalizedName("Moonstone").setTextureName("moonstone").setMaxStackSize(64);// I have tried with ms:moonstone and without, both don't work.
GameRegistry.registerItem(moonstone, moonstone.getUnlocalizedName().substring(5));
Recommended changes but not necessary:
First:
When registering the item you should remove the .substring(5),
having this in will name the item "Moons" instead of "Moonstone".
Second:
unlocalized names should always be lowercase and should be formatted
as modid_itemname
Third:
Package names should be lowercase, package moonstone
Fourth:
Your should make a Refstrings.java file and put the modid, version and name in it
package Moonstone
public RefStrings{
public static String NAME = "Moonstone";
public static String MODID = "ms";
public static String VERSION = "1.0";
}
Necessary changes:
Your setTextureName should be passed "ms:moonstone"
You didn't post your folder structure but it should look like this:
src/main/java/Moonstone/MoonstoneMain.java
src/main/resources/assests/ms/textures/items/moonstone.png
it is possible that some of the recommend changes will be what fixes the problem, minecraft can be a bit finicky with naming.
I'm trying to write a generic widget that takes a (any) enumset and pops up a simple form to allow the the individual enums to be turned on and off. Below is a minimal version of the class (actually set up to run on android), but eclipse is moaning that "The method add(Capture#-7 of ?) in the type AbstractCollection is not applicable for the arguments(T)".
A few lines earlier I have invoked remove (which has exactly the same signature) with no problems. Trying to turn the enum into a set first doesn't help either.
What is the magic syntax I need to fix this?
I may well not be using the best class definition either ;) I'm using the Android Developer Tools v21.1.0-569685, but the problem also shows up on Eclipse proper (Indigo)
package com.test.ui;
import java.util.EnumSet;
import android.util.Log;
import android.view.View;
import android.view.LayoutInflater;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.CheckBox;
public class EnumSetSelector<T extends Enum<?>> implements OnClickListener{
Class<T> enumclass;
EnumSet<?> theset; // EnumSet<T> theset; fails
boolean haschanged;
public EnumSetSelector(Class<T> eclass) {
enumclass = eclass;
}
public ViewGroup prepareSet(EnumSet<?> esvals, ViewGroup vg, LayoutInflater li) {
theset = esvals;
T[] allvals = enumclass.getEnumConstants();
Log.d("XXXXXXZ","here are " + allvals.length);
ViewGroup grp = (ViewGroup)li.inflate(R.layout.ui_enum_selector,vg,false);
for (T av:allvals) {
Log.d("XXXXXXZ"," - " + av);
CheckBox cb = (CheckBox)li.inflate(R.layout.ui_enum_selector_entry,vg,false);
cb.setText(String.valueOf(av));
cb.setChecked(theset.contains(av));
cb.setTag(av);
grp.addView(cb);
esvals.remove(av);
}
vg.addView(grp);
return grp;
}
#Override
public void onClick(View v) {
final T thisenum = (T)v.getTag();
final boolean oldval = theset.contains(thisenum);
final CheckBox cb = (CheckBox)v;
if (cb.isChecked() != oldval) {
if (cb.isChecked()) {
cb.setChecked(false);
theset.remove(thisenum);
} else {
cb.setChecked(true);
theset.add(thisenum);
theset.add(EnumSet.of(thisenum));
}
Log.d("XXXXXXZ", "" + String.valueOf(v.getTag()) + " now "
+ (theset.contains(thisenum)));
}
}
}
Echoing JB Nizet's comment, you should declare your class as:
EnumSetSelector<T extends Enum<T>>
In other words, with the same recursive bounds that Enum and EnumSet declare.
Then make sure to type theset and esvals as EnumSet<T> instead of EnumSet<?>.
I'm working on a custom ROM and I'm receiving this error while trying to compile it. I tried to add a custom battery selector.
com.android.settings.pcf.StatusBar is not abstract and does not override abstract method onPreferenceChange(android.preference.Preference,java.lang.Object) in android.preference.Preference.OnPreferenceChangeListener
This is the the com.android.settings.pcf.StatusBar file:
package com.android.settings.pcf;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.PreferenceScreen;
import android.provider.Settings;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.R;
public class StatusBar extends SettingsPreferenceFragment implements OnPreferenceChangeListener {
private static final String PREF_BATT_ICON = "battery_icon_list";
ListPreference mBatteryIcon;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.prefs_statusbar);
mBatteryIcon = (ListPreference) findPreference(PREF_BATT_ICON);
mBatteryIcon.setOnPreferenceChangeListener(this);
mBatteryIcon.setValue((Settings.System.getInt(getActivity()
.getContentResolver(), Settings.System.STATUSBAR_BATTERY_ICON,
0))
+ "");
}
public boolean OnPreferenceChange(Preference preference, Object newValue) {
if (preference == mBatteryIcon) {
int val = Integer.parseInt((String) newValue);
return Settings.System.putInt(getActivity().getContentResolver(),
Settings.System.STATUSBAR_BATTERY_ICON, val);
}
return false;
}
}
Can anyone help?
The error is telling you that you didn't fully implement the OnPreferenceChangeListener interface. In particular, the onPreferenceChange method is not implemented.
Either implement this method, or I'm actually guessing you meant OnPreferenceChange to be onPreferenceChange - note the lowercase "o" - (in which case I believe you will also need a #Override before it)
In a javafx2 application, a ComboBox should present a list of items, in the example they are String s for simplicity.
This list contains a null item, since i want the user to be allowed to make no choice.
Since I was playing with the converter property of the ComboBox, I wondered if I could use it to provide a nice representation for the no-choice case, for example "[none]" instead of an empty line.
But i've discovered that the toString(Object object) is never called for the null item.
Here follows the shortest code that reproduces the case. Version information included.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.stage.Stage;
import javafx.util.StringConverter;
public class T05 extends Application {
#Override public void start(Stage primaryStage) throws Exception {
System.out.println(System.getProperty("java.runtime.version"));
System.out.println(System.getProperty("javafx.runtime.version"));
System.out.println(System.getProperty("os.version"));
System.out.println(System.getProperty("os.name"));
ComboBox c = new ComboBox();
//c.setEditable(true);
primaryStage.setScene(new Scene(c));
primaryStage.show();
c.getItems().add("first");
c.getItems().add(null);
c.setConverter(new StringConverter<String>(){
#Override public String toString(String object) {
System.out.print("converting object: ");
if (object==null) {
System.out.println("null");
return "[none]";
}
System.out.println(object.toString());
return object.toString();
}
#Override public String fromString(String string) {
throw new RuntimeException("not required for non editable ComboBox");
}
});
}
}
And here it's the output, as you can see the true branch of the if (object==null) statement is never called. Is it a bug or a feature, and is it yet possible to customize null representation?
1.6.0_29-b11
2.2.3-b05
6.1
Windows 7
converting object: first
converting object: first
converting object: first
update
Adding a (just uncomment it):
c.setEditable(true);
I get another behavior, that is, clicking on the null item in the combo selection box, I get the method toString called, but the result is not presented in the selection box.
you can use setPromptText method to display "[None]" in comboBox if user make no choice.
Sample Code :
comboBox.setValue(null);
comboBox.setPromptText("[None]");