JavaFX GridPane Drag and Drop with a dynamic amount of Labels - java

At the moment I am struggling with a JavaFX problem. I will try to explain it as detailed as possible. If you need any further information, just let me know!
What do I want to achieve:
In the picture below you can see what I am trying to achive
I am creating a variable number of Labels (the amount will differ from time to time)
The references to this labels are stored in an ArrayList. The Labels then are placed on the GridPane
Now I want to be able to drag and drop that Labels in the GridPane
I would like to achieve a solution like in the picture below
When you drag a Label it should be visually shown, where I am dragging it to
When passing another Label the visual effect should show, what happens when I drop it here (nr. 3 in the picture). After I drop the Label there, the Labels on the right are switching their position one field to the right. The opened space where I took the source have to be filled with the Labels on the right.
Here is the picture of what I am dreaming right now
At the moment I am able to create the Labels and place them on the GridPane. But when I try to use an inner class for the .onDragListener I am getting the message, that the Label has to be final. But I can't make it final, because it was created during the runtime and the references are stored in an ArrayList.
Okay here is some Code. I hope it helps.
public class Center {
public static GridPane createCenterContent(List<Tour> tourList){
GridPane gridPaneCenter = new GridPane();
final List<List<Label>> customerLabelList = new ArrayList<List<Label>>();
Label customerLabel = new Label();
for(int i = 0 ; i < tourList.size() ; i++){
customerLabelList.add(i, new ArrayList<Label>());
for(int j = 0 ; j < tourList.get(i).getTour().size() ; j++){
customerLabelList.get(i).add(new Label(tourList.get(i).getTour().get(j).getVorname() + " " + tourList.get(i).getTour().get(j).getNachname() + " \n" +
tourList.get(i).getTour().get(j).getStrasse() + " \n" + tourList.get(i).getTour().get(j).getAdresse()));
customerLabelList.get(i).get(j).getStyleClass().add("kdLabelGridLayout");
customerLabelList.get(i).get(j).setMinWidth(200);
customerLabelList.get(i).get(j).setMinHeight(100);
customerLabel = customerLabelList.get(i).get(j);
customerLabel.setOnDragDetected(new EventHandler<MouseEvent>() {
public void handle(MouseEvent event) {
/* drag was detected, start a drag-and-drop gesture*/
/* allow any transfer mode */
Dragboard db = customerLabel.startDragAndDrop(TransferMode.ANY);
System.out.println("Label wird bewegt!");
}
});
customerLabelList.get(i).get(j).setOnDragOver(new EventHandler<DragEvent>() {
public void handle(DragEvent event) {
/* data is dragged over the target */
/* accept it only if it is not dragged from the same node
* and if it has a string data */
event.acceptTransferModes(TransferMode.COPY_OR_MOVE);
System.out.println("Label berührt anderes Label");
}
});
gridPaneCenter.add(customerLabelList.get(i).get(j), i, j, 1, 1);
}
}
gridPaneCenter.setHgap(10);
gridPaneCenter.setVgap(10);
return gridPaneCenter;
}
}
I am getting an error on the Dragboard db = customerLabel.startDragAndDrop(TransferMode.ANY); line. Eclipse tells me that the variable has to be final.
I have a List which contains Lists of Labels. I am placing that Labels in the GridPane and trying to give them all the drag and drop listeners. The amount of labels in the List> differs from time to time.
What can I do to solve the problem? I am very thankful for any hint or link you guys can provide me with.

Related

Why do I need to open all expansion panes in a list view, else they give null? - Gluon JavaFX

I don't know if this is a code issue or a bug. But this picture shows us a Glisten List View where I have included Glisten DropdownButtons inside a ExpandedPaneContainer etc.
The issue here is that I need to open the first the first expansion pane, then the second one, named Layer 2, and then the third one, named Layer 3.
I cannot directly open Layer 2 or Layer 3 at the beginning. After I have open all expanded panes and close them, then I can open them again in a random order.
Question:
Why does it behave like this? Is it a bug?
If you want to run the program, just download the code from here: https://github.com/DanielMartensson/Deeplearning2C and then create a new file inside the software, by clicking the "pen"-icon. Give it a name and then go to "Configurations -> Layer Configuration". Try to open the last expansion panel at the bottom first.
Here is the method I use when I add all these expansion panes inside a expandedPaneContainer. I know there is lot of code, but the idea is that:
Create a expansion panel
Add the expansion panel into expanded panel container
Create a collapsed panel and set its labels etc.
Set the collapsed panel to expansion panel collapsed content
Create a expanded panel and set a grid pane into it
Add a labels at column 0, row j, and drop down buttons box at column 1 and row j inside the grid pane.
Repeat the process again
private void addLayer() {
/*
* Create a expanded panels from these
*/
String[] dropDownTypes = dL4JSerializableConfiguration.getDropdownTypes();
/*
* New expansions panel and add it to the expansions panel container
*/
ExpansionPanel expansionPanel = new ExpansionPanel();
expansionPanelContainer.getItems().add(expansionPanel);
/*
* Create an collapsed panel and first set a label with the name of the layer index e.g. layer 0, layer 1 etc and the layer type
*/
CollapsedPanel collapsedPanel = new CollapsedPanel();
collapsedPanel.getTitleNodes().add(0, new Label("Layer " + expansionPanelContainer.getItems().size())); // Layer index
collapsedPanel.getTitleNodes().add(1, new Label("DenseLayer")); // Layer type
expansionPanel.setCollapsedContent(collapsedPanel); // Set collapsed panel into expansion panel
/*
* Create an expanded panel and begin to set a grid pane into it
*/
ExpandedPanel expandedPanel = new ExpandedPanel();
GridPane gridPane = new GridPane();
expandedPanel.setContent(gridPane);
expansionPanel.setExpandedContent(expandedPanel);
/*
* Add all drop downs and labels
*/
for(int j = 0; j < dropDownTypes.length; j++) {
/*
* Create the element label and set its position inside the grid
*/
Label elementLabel = new Label(dropDownTypes[j]);
gridPane.add(elementLabel, 0, j); // To the left
/*
* Create the drop down button and add the elements and set its position inside the grid
*/
DropdownButton elementDropdown = new DropdownButton();
if(dropDownTypes[j].equals(dropDownTypes[0])) { // Layer types
elementDropdown.getItems().addAll(new MenuItem("DenseLayer"), new MenuItem("LSTM"), new MenuItem("OutputLayer"));
}else if(dropDownTypes[j].equals(dropDownTypes[1])) { // Inputs
for(int i = 1; i <= 100; i++)
elementDropdown.getItems().add(new MenuItem(String.valueOf(i)));
}else if(dropDownTypes[j].equals(dropDownTypes[2])) { // Outputs
for(int i = 1; i <= 100; i++)
elementDropdown.getItems().add(new MenuItem(String.valueOf(i)));
}else if(dropDownTypes[j].equals(dropDownTypes[3])) { // Activations
for(Activation activation : Activation.values())
elementDropdown.getItems().add(new MenuItem(activation.name()));
}else if(dropDownTypes[j].equals(dropDownTypes[4])) { // Loss functions
for(LossFunctions.LossFunction lossFunction : LossFunctions.LossFunction.values())
elementDropdown.getItems().add(new MenuItem(lossFunction.name()));
}
gridPane.add(elementDropdown, 1, j); // To the middle
}
}

MouseEvents inside JList objects

I am a bit confused regarding a situation I have. I created a ListModel extending DefaultListModel and ListRenderer implementing ListCellRenderer for displaying a custom cell in a JList. The cells are some objects created from a class extending JPanel, that contain a JLabel and a JButton.
My issue is related to the mouse events: I want to trigger a certain event when clicking on the JButton inside a cell of the JList, yet I can not figure out how to match the mouse source point to that of the JButton from the respective index. More exactly, I added a mouse listener to the list, but I want it to trigger something if the mouse point is located inside the bounds of the JButton, and another action if it's on the data item. I added some prints to find out the cause of this, but before that some code to highlight the structure:
public WifiGuiHandler(JButton reference) {
btnReference = reference;
wifiListener = new WifiListener();
wifiPopupContainer = new JScrollPopupMenu("Connections.");
wifiPopupContainer.setMaximumVisibleRows(7);
connectionsHolder = new ArrayList<>();
listOfConnections = new JList();
listOfConnectionsModel = new ListModel(connectionsHolder);
listOfConnectionsRenderer = new ListRenderer();
listOfConnections.setModel(listOfConnectionsModel);
listOfConnections.setCellRenderer(listOfConnectionsRenderer);
wifiPopupContainer.add(listOfConnections);
wifiPopupContainer.pack();
initializeTestVariables();
initializeListeners();
}
Here, the constructor for the class that takes a JButton and adds a mouse listener to it, that triggers the appearance of a JPopupMenu, which has only one component, the JList that hold the entire data. Also, links the ArrayList with the data items to the ListModel.
public void initializeTestVariables() {
for (int i = 0; i <= 10; i++) {
WifiItem item = new WifiItem("Connection number " + i + ".", i);
connectionsHolder.add(item);
}
}
Setting up the data items.
public void initializeListeners() {
listOfConnections.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
int index = listOfConnections.locationToIndex(e.getPoint());
if (index >= 0) {
WifiItem item = (WifiItem) ((ListModel) listOfConnections.getModel()).getElementAt(index);
System.out.println("Button of " + item.getConnectionName() + " is at location :" + item.getButton().getLocation());
System.out.println("Button has the bounds : " + item.getButton().getBounds());
System.out.println("MouseEvent detected on : " + e.getPoint().getLocation());
if (item.getButton().getBounds().contains(e.getPoint())) {
item.connectHere();
}
if (item.getButton().isVisible()) {
System.out.println("Set expanded on : " + item.getConnectionName());
item.setExpandedState(false);
listOfConnectionsModel.fireContentsChanged(item, index, index);
updateGui(false);
} else {
System.out.println("Set expanded on : " + item.getConnectionName());
listOfConnectionsModel.fireContentsChanged(item, index, index);
item.setExpandedState(true);
updateGui(false);
}
}
}
});
btnReference.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
updateGui(true);
}
});
}
And this is where the confusion arises. I correctly get the data item( WifiItem) from the mouse event location/point, but when I click on the JButton of the WifiItem, it doesn't trigger that method, like it doesn't seem to detect that the JButton actually is there. I also set up the prints, and what is strange, the Point for the JButton is always the same, even though it actually is different, and this seems to be the problem. More exactly, from the output of the program:
Button of Connection number 2. is at location :java.awt.Point[x=137,y=33]
Button has the bounds : java.awt.Rectangle[x=137,y=33,width=90,height=26]
MouseEvent detected on : java.awt.Point[x=172,y=125]
Button of Connection number 3. is at location :java.awt.Point[x=137,y=33]
Button has the bounds : java.awt.Rectangle[x=137,y=33,width=90,height=26]
MouseEvent detected on : java.awt.Point[x=172,y=125]
The above mouse events points were actually located on the JButton itself, only it didn't get that. As another strange fact, only if I click the JButton of the FIRST element of the list does it trigger the required mouse action.
Another print revealed that all the JButtons have the same Point and Rectangle, and I don't get it. There are 10 items in the JList, each displayed properly, how can all their JButtons have the same location? I must be missing some key element here. I looked at other posts and tried other recommendations: converting the point with SwingUtilities, removing all the mouse listeners from the JList and adding them to the data items.
To sum it up,the issue is that the list triggers the events for the correct data item in it(meaning, I do get the correct index for the item located there), but if the mouse event happens on the JButton of any data item inside the list, it doesn't trigger the required effect (the point is not withing the bounds of the button, even though it should be).
More exactly, I added a mouse listener for the list, but I want it to trigger something if the mouse point is located inside the bounds of the JButton, and another action if it's on the data item.
An easier solution would be to use a JTable. The data is separated into columns and the JTable has an API to let you know which row/column was selected.
You can use the Table Button Column as your renderer/editor for the button.
Edit:
only if I click the JButton of the FIRST element of the list does it trigger the required mouse action
Sounds like your conversion of the mouse point is off.
, how can all their JButtons have the same location?
Again, the button location is releative to the renderer panel. The panel itself is relative to the row in the JList. So I would guess you need to need the row index and add the height of each of the previous rows to your calculation.

libgdx update label text and position simultaniously

What I'm trying to do is add a label to follow a slider knob and display the knob's value. I currently have a vertical slider and a label stacked inside a table cell. I'm able to position the label to where the knob is and update it's text correctly but am having a problem while moving the knob.
When I move the knob I can make the label follow it as long as I don't update the text. But if I update the text then the label re-centers itself in the cell till the knob has stopped moving at which point it places itself on the knob.
How do I make it so that the label doesn't reposition while it's updating text?
Here's some sample code of what I have going on:
public class OptionsWindow implements Screen {
private Stage stage;
private Table table;
private Slider slider;
private Label label;
private Skin textSkin = new Skin(Gdx.files.internal("skins/skin.json"),
new TextureAtlas(Gdx.files.internal("skins/text.pack")));
private Skin sliderSkin = new Skin(Gdx.files.internal("skins/skin.json"),
new TextureAtlas(Gdx.files.internal("skins/text.pack")));
private min = 1, max = 2;
#Override
public void show() {
stage = new Stage();
table = new Table();
slider = new Slider(min, max, 0.001f, true, sliderSkin);
label = new Label(""), textSkin);
table.stack(label, slider).height(1000);
table.setFillParent(true);
stage.addActor(table);
Gdx.input.setInputProcessor(stage);
}
#Override
public void render() {
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
label.setY(slider.getValue() - min);
label.setText(slider.getValue());
stage.act();
stage.draw();
}
}
I've done a lot of searching for an answer to this question but have yielded no results. I have also tried placing the label in a container and moving the container while changing the label text, but the same result happens. I'm sure somebody had done this before. It seems like something people would want. Please, any help is welcome.
Never done it before, I always show the amount in a label next to or above the slider to give feedback. But the result you are after gives a nice touch. Did you try adding a ChangeListener to your slider? Everytime the slider changes this would be called. So if you insert the new position and text in there it should be updating correctly.
//Slider has to be "final" for the listener.
final Slider slider = new Slider(0, 10, 1, true, skin);
slider.addListener(new ChangeListener() {
#Override
public void changed(ChangeEvent event, Actor actor) {
label.setY(slider.getValue() - min);
label.setText(slider.getValue());
}
});
This does not need to be in your render method. It's a listener so whenever something happens the listener listens for it will execute.
Ok, so I came up with a work around. It would seem that labels move back to their cell alignment position while updating text. to solve this I simply placed my label in a table with a row on either side like so:
Table subTable = new Table();
subTable.add().row();
subTable.add(label).row();
subTable.add();
Then in render I calculate new height values for the exterior cells and apply them:
float topH, botH;
// Calculate heights
topH = (1 - slider.getPercent()) * parentTable.getHeight();
botH = slider.getPercent() * parentTable.getHeight();
// Apply heights to exterior table cells
subTable.getCells().get(0).height(topH);
subTable.getCells().get(2).height(botH);
// Update label text
label.setText(slider.getValue());
This is just a simple form of what I have. You'll want to take into account the label height to get it to position correctly. Also if you want it to follow with the slider at the slider's set animateDuration, you'll want to use getVisualPercent() instead of getPercent(). However this causes issues when your finger flies out of the cell. For some reason the visual percent is linked to where the slider knob is when your finger leaves the cell, not where it ends up.
My work around for this was to just set the animateDuration to 0.0f and just use getPercent(). If I or someone comes up with a solution to the getVisualPercent() option, then that would be nice to have posted as well.

How to implement freeze column in GXT 3.x?

How can frozen columns be implemented in GXT 3.x (from Sencha)? Ext-JS, another product from Sencha seems to implement this, but I can't see where the Java based GXT implement the same thing:
http://dev.sencha.com/deploy/ext-4.0.0/examples/grid/locking-grid.html
The basic idea is that you need two different scrolling containers, one with the fixed column(s), one with the scrolling columns. Each of these need to be in a distinct viewport, so the standard Grid/GridView doesn't work well with this - they make assumptions about how scrolling should behave, so simply subclassing one or both is likely to be fairly involved.
Instead, you can build two grids, one for the locked columns, one for the scrolling ones. Each can deal with their own ColumnConfig classes, to draw headers and rows, and will be linked to the same ListStore to ensure their data is in sync - changes in the store will be passed along to both listening grids.
To get the full effect, some additional wiring will be needed:
Linking scrolling. Listen to the BodyScrollEvent from each Grid, and scroll the other to the same place (changing only top, not left, since you don't want one to control the other).
Sizing is the second big piece - both grids need their scrollable height to be the same, but the horizontal scrolling one needs a buffer on the bottom when it is actually showing that scrollbar. Usually the Grid is told to size based on its parent's instructions, though sometimes you directly size the Grid - in this case, this step is not needed, just size the two grids slightly differently. Otherwise, you'll need to structure the layout to properly configure this.
Finally, the locked column needs its vertical scrollbar to be hidden away - the user has no need to see two vertical scrollbars.
This covers the basic use case, but doesn't deal with things like alternate GridView implementations - GroupingView and subclasses will need to link expanding (and to hide the group headings so they don't appear twice, plus deal with the fact that the group row shouldn't get split when the second half scrolls sideways), TreeGridView and TreeGrid will need to link expanding nodes and hide the tree +/- icons from the second grid.
Here's this basic set of modifications applied to the basic grid example at http://www.sencha.com/examples/#ExamplePlace:basicgrid. To help avoid confusing the issue, I've deleted a number of other features in that grid such as tooltips and changing the selection model:
public class GridExample implements IsWidget, EntryPoint {
private static final StockProperties props = GWT.create(StockProperties.class);
private ContentPanel root;
#Override
public Widget asWidget() {
if (root == null) {
final NumberFormat number = NumberFormat.getFormat("0.00");
ColumnConfig<Stock, String> nameCol = new ColumnConfig<Stock, String>(props.name(), 50, SafeHtmlUtils.fromTrustedString("<b>Company</b>"));
ColumnConfig<Stock, String> symbolCol = new ColumnConfig<Stock, String>(props.symbol(), 100, "Symbol");
ColumnConfig<Stock, Double> lastCol = new ColumnConfig<Stock, Double>(props.last(), 75, "Last");
ColumnConfig<Stock, Double> changeCol = new ColumnConfig<Stock, Double>(props.change(), 100, "Change");
changeCol.setCell(new AbstractCell<Double>() {
#Override
public void render(Context context, Double value, SafeHtmlBuilder sb) {
String style = "style='color: " + (value < 0 ? "red" : "green") + "'";
String v = number.format(value);
sb.appendHtmlConstant("<span " + style + " qtitle='Change' qtip='" + v + "'>" + v + "</span>");
}
});
ColumnConfig<Stock, Date> lastTransCol = new ColumnConfig<Stock, Date>(props.lastTrans(), 100, "Last Updated");
lastTransCol.setCell(new DateCell(DateTimeFormat.getFormat("MM/dd/yyyy")));
List<ColumnConfig<Stock, ?>> l = new ArrayList<ColumnConfig<Stock, ?>>();
//Remove name from main set of columns
// l.add(nameCol);
l.add(symbolCol);
l.add(lastCol);
l.add(changeCol);
l.add(lastTransCol);
//create two column models, one for the locked section
ColumnModel<Stock> lockedCm = new ColumnModel<Stock>(Collections.<ColumnConfig<Stock, ?>>singletonList(nameCol));
ColumnModel<Stock> cm = new ColumnModel<Stock>(l);
ListStore<Stock> store = new ListStore<Stock>(props.key());
store.addAll(TestData.getStocks());
root = new ContentPanel();
root.setHeadingText("Locked Grid Sample");
root.setPixelSize(600, 300);
final Resizable resizable = new Resizable(root, Dir.E, Dir.SE, Dir.S);
root.addExpandHandler(new ExpandHandler() {
#Override
public void onExpand(ExpandEvent event) {
resizable.setEnabled(true);
}
});
root.addCollapseHandler(new CollapseHandler() {
#Override
public void onCollapse(CollapseEvent event) {
resizable.setEnabled(false);
}
});
//locked grid
final Grid<Stock> lockedGrid = new Grid<Stock>(store, lockedCm) {
#Override
protected Size adjustSize(Size size) {
//this is a tricky part - convince the grid to draw just slightly too wide
//and so push the scrollbar out of sight
return new Size(size.getWidth() + XDOM.getScrollBarWidth() - 1, size.getHeight());
}
};
lockedGrid.setView(new GridView<Stock>(){{
this.scrollOffset=0;
}});
//require columns to always fit, preventing scrollbar
lockedGrid.getView().setForceFit(true);
//main grid, with horiz scrollbar
final Grid<Stock> grid = new Grid<Stock>(store, cm);
//don't want this feature, want to encourage horizontal scrollbars
// grid.getView().setAutoExpandColumn(nameCol);
grid.getView().setStripeRows(true);
grid.getView().setColumnLines(true);
grid.setBorders(false);
grid.setColumnReordering(true);
grid.setStateful(true);
grid.setStateId("gridExample");
//link scrolling
lockedGrid.addBodyScrollHandler(new BodyScrollHandler() {
#Override
public void onBodyScroll(BodyScrollEvent event) {
grid.getView().getScroller().scrollTo(ScrollDirection.TOP, event.getScrollTop());
}
});
grid.addBodyScrollHandler(new BodyScrollHandler() {
#Override
public void onBodyScroll(BodyScrollEvent event) {
lockedGrid.getView().getScroller().scrollTo(ScrollDirection.TOP, event.getScrollTop());
}
});
HorizontalLayoutContainer gridWrapper = new HorizontalLayoutContainer();
root.setWidget(gridWrapper);
//add locked column, only 300px wide (in this example, use layouts to change how this works
HorizontalLayoutData lockedColumnLayoutData = new HorizontalLayoutData(300, 1.0);
//this is optional - without this, you get a little offset issue at the very bottom of the non-locked grid
lockedColumnLayoutData.setMargins(new Margins(0, 0, XDOM.getScrollBarWidth(), 0));
gridWrapper.add(lockedGrid, lockedColumnLayoutData);
//add non-locked section, taking up all remaining width
gridWrapper.add(grid, new HorizontalLayoutData(1.0, 1.0));
}
return root;
}
#Override
public void onModuleLoad() {
RootPanel.get().add(asWidget());
}
}
There are a handful of issues (no line between locked and unlocked column, locked column header menu context icon is slightly out of place), but it covers most of the details without much hassle, and leaves almost all of it open to configuration - want the lock at the end? Just move the modifications around - want more than one locked column? just add more to the lockedCm.
That functionality is not implemented in GXT, but there is an user called The_Jackal who made a workaround for this problem in the Sencha Forum - Thread. I haven't tried yet, but I hope it can help you.
GXT - Freeze grid workaround Download

Aligning components of a JScrollPane to the left?

Currently, I have a JScrollPane nested within a Panel having a BorderLayout. More importantly, within the JScrollPane, I intend to have multiple text areas listed vertically, with a width that does not exceed the JScrollPane's, and as much height as they need (with the scroll pane having only a vertical scroll bar).
My problem is that the text areas appear to be centered in the JScrollPane relative to the largest text area, and they are all one line.
Can anyone tell me, looking at my code, how I can get the text areas to wrap before the right side of the scroll pane, and how they can be left aligned?
private JPanel generateStateView(State state) {
//Create a new panel, and the info label.
JPanel container = new JPanel(new BorderLayout());
JLabel infoLabel = new JLabel("The state of " + state.getName() +
", with " + state.getElectoralVotes() + " Electoral Votes.");
container.add(infoLabel, BorderLayout.NORTH); //Put the label on top.
//Will scroll through polls.
JScrollPane pollViewer = new JScrollPane();
//This will be the scroll pane's "viewport".
JViewport pollViewport = new JViewport();
//And finally, this will actually hold the individual polls.
JPanel pollPanel = new JPanel(new GridLayout(
state.getPolls().size(), 1));
pollPanel.setAlignmentX(LEFT_ALIGNMENT);
//Iteratively add the polls to the container
for (Poll poll : state.getPolls()) {
//Holds individual polls
Box pollBox = Box.createHorizontalBox();
//Create the poll's panel and add it to the poll container.
pollBox.add(generatePollPanel(poll));
pollBox.add(Box.createHorizontalGlue()); //Fill to the right
pollBox.setAlignmentX(LEFT_ALIGNMENT);
pollPanel.add(pollBox); //Put the box into the pollPanel.
}
//Put the panel into the viewport.
pollViewport.add(pollPanel);
//Put the viewport "into" the scroll pane
pollViewer.setViewport(pollViewport);
//Put the pane into the state view.
container.add(pollViewer, BorderLayout.CENTER);
return container; //And give back the container.
}
/**
* Method: generatePollPanel
* Purpose: Generates a panel containing information on a particular poll.
* #param poll The poll to have information generated from.
* #return The JPanel.
*/
private JPanel generatePollPanel(Poll poll) {
//Create a new panel, then a text area to fill with the info.
JPanel pollPanel = new JPanel();
JTextArea pollInfo = new JTextArea("Conducted by " + poll.getAgency() +
" on day " + poll.getDate() + " for " + poll.getNumDays() +
" days, " + poll.getPercentVoteDem() +
"% voted Democrat, and " + poll.getPercentVoteRep() +
"% voted Republican.");
pollInfo.setEditable(false); //We don't want the user editing this.
pollPanel.add(pollInfo); //Throw the area in, and return the panel.
pollPanel.setAlignmentX(LEFT_ALIGNMENT);
return pollPanel;
}
/**
* Method: valueChanged
* Purpose: Handle the event of a different list item being selected.
* #param event The object containing information about this event.
*/
public void valueChanged(ListSelectionEvent event) {
//Instantiating JList with a type of ? seems to solve the issue of
//eclipse complaining about an unchecked cast (originally to
//JList<String>, so I should be able to cast child values directly
//to string later on anyways.
JList<?> stateList = (JList<?>) event.getSource();
//Account for keyboard and mouse actions
if (!event.getValueIsAdjusting()) {
State chosenState; //Keep track of the picked state.
try {
//Try to get the currently selected state
chosenState = db.findState((String) stateList.getSelectedValue());
}
catch (NoSuchElementException exception) {
//Somehow the picked state was not found, notify the user.
reportError("The selected state is not available.");
return;
}
//Find the container for the gui window.
Container container = getContentPane();
//Remove the empty state view container by generating a new one.
container.remove(currentStateView);
//Generate the state view and add that to the container.
currentStateView = generateStateView(chosenState);
container.add(currentStateView, BorderLayout.CENTER);
//Redraw everything.
revalidate();
}
}
Looks like a severe case of lost-in-nesting :-)
While it nesting can be a means to achieve layout requirements, it can be hard to find out what exactly goes wrong if it doesn't give the intended output. Also, there are general warning signals that something is basically wrong with the nesting
deep levels of containers with wildly varying managers
containers with only a single child
In your case, all except the top-level borderlayout have a single "netto" (that is, not intended to hack around a layout issue) child, with levels:
BorderLayout (the state view)
GridLayout (the panel that's the scrollPane's view)
BoxLayout (the panel that contains a single pollBox)
FlowLayout (the panel that contains the textArea)
The experienced problems:
text areas are single-line without ever wrapping
text areas are centered
are both due to the characteristics of the LayoutManagers, either partly (first) or completely (second):
The part that's not due the manager is that a textArea doesn't wrap by default, you have to explicitly configure it to do so. The part that's due to the manager (== FlowLayout) is that it always lays out its children according to their prefSize, nothing else. That boils down to have a fixed child size, no matter how much space is available for the parent. So even if the textArea were configured to wrap, it would stick to that initial size, no matter how wide its parent can get.
The inner FlowLayout aligns its children ... centered. So whatever alignment you set on the level of the BoxLayout doesn't have any effect. As doesn't the glue: it only takes all the excess space if the other child/ren have a maximum size in that dimension (which a panel with FlowLayout does not have, simply because it's that simple LayoutManager doesn't have the notion of max, only managers of type LayoutManager2 have)
Time to take a step back and review the inital requirement:
multiple text areas listed vertically, with a width that does not
exceed the JScrollPane's, and as much height as they need
The zeroth order solution is to
configure the textArea as needed
throw out all layout nesting (inside the scrollPane)
In code:
JComponent overview = new JPanel(new BorderLayout());
overview.add(new JLabel("All polls for state XY"), BorderLayout.NORTH);
JComponent pollPanel = new JPanel();
pollPanel.setLayout(new BoxLayout(pollPanel, BoxLayout.PAGE_AXIS));
for (int i = 0; i < 10; i++) {
pollPanel.add(generatePollTextArea(i)); //Put the box into the pollPanel.
}
pollPanel.add(Box.createVerticalGlue());
JScrollPane pollViewer = new JScrollPane(pollPanel);
overview.add(pollViewer);
showInFrame(overview, "layout text only");
protected JComponent generatePollTextArea(int i) {
String agency = "agency ";
// simulates different size text
for (int j = 0; j < i; j++) {
agency += i;
}
String text = "Conducted by " + agency +
" on day " + "today" + " for " + 20 +
" days, " + 99 +
"% voted Democrat, and " + 0.2 +
"% voted Republican.";
JTextArea pollInfo = new JTextArea(text);
// give it some reasonable initial width
// in terms of content
pollInfo.setColumns(20);
pollInfo.setLineWrap(true);
pollInfo.setWrapStyleWord(true);
pollInfo.setEditable(false); //We don't want the user editing this.
return pollInfo;
}
"Das Wort zum Dienstag": nesting layouts isn't a means of avoiding to learn the characteristics of the LayoutManagers used on the individual levels of the nested layout.
Firstly, I don't think Box is suppose to be used with anything other then the BoxLayout (I could be wrong, but that's how I read it)
Personally, I'd either use either GridBagLayout or VerticalLayout from SwingLabs.
In order to facilitate the horizontal restrictions, you will also want to take a look at the Scrollable interface, in particular getScrollableTracksViewportWidth

Categories