I've got MyJPanel(extends JPanel). Each MyJPanel object has method GetID(). When I create it I set ID with constructor(but also there is method SetID()), set size and then create Jscrollpane and add it to JInternalFrame. All frames are in ArrayList<JInternalFrame> arr.
JInternalFrame frame = new JInternalFrame("Inner frame",true,true,true,true);
final MyJPanel panel = new MyJPanel(f.getAbsolutePath(),count);
panel.setSize(panel.getWidth()/6,panel.getHeight()/6);
JScrollPane pane = new JScrollPane(panel);
pane.setPreferredSize(new Dimension(theDesktop.getWidth() / 2, theDesktop.getHeight() / 2));
frame.getContentPane().add(pane, BorderLayout.CENTER);
To delete frame I add add FrameListener and method internalFrameClosing method
public void internalFrameClosing(InternalFrameEvent e) {
int index = panel.GetID();//get index of panel окна
if (index == arr.size())
arr.remove(index);//remove last element
else{
//reset all indexes of JInternalFrames' MyJPanel
}
}
But I don't know how to reset values for MyJPanels in array of JInternalFrames when one of the frames was deleted because
1)MyJPanel is in JScrollPane. method SetID
2)JScrollPane is in JInternalFrame
3)JInternalFrame is in the array. No method SetID() in arr.get(i).
A simple solution is to have a Map such as a HashMap -- HashMap<JInternalFrame, MyJPanel> -- that would allow you to easily associated the MyJPanel with the internal frame that holds it. Then when you iterate through the JInternalFrames, it would be easy to retrieve the MyJPanels that each hold.
Something like:
public void internalFrameClosing(InternalFrameEvent e) {
int index = panel.GetID();
arr.remove(index);
if (index < arr.size()) {
for (int i = 0; i < arr.size(); i++) {
JInternalFrame internalFrame = arr.get(i);
MyJPanel myPanel = framePanelMap.get(internalFrame);
myPanel.setID(i);
}
}
}
Incidentally, you do know that this if (index == arr.size()) will never be true, since if your index is an index into the list, then index will hold a value between 0 and arr.size() - 1 and will never == arr.size().
Though this is somewhat of a kludge. In my mind a better solution is perhaps to do a code re-design and have the model logic of your code separate from the view portion of the code so that you would have an ArrayList of models rather than JInternalFrames.
This way, if later you decide that you don't really want to use JInternalFrames to display this information, you wouldn't have to change the basic logical structure of the model.
Related
I'm having an issue creating an empty JTabbedPane where the only portion to be seen on the GUI are the row of tabs.
Everytime I add a new tab with an "empty" component, the height of the JTabbedPane increases, but why?
The current workaround is to override getPreferredSize(), but it seems kludgy to me. Comment out the overridden method to see what I mean.
Am I missing something obvious?
Background:
We need a JTabbedPane where the tabbed pane starts off with 2 tabs, but the user can add more tabs as needed, up to 10. In addition, each tab contains the same components, but with different data. The decision was made to fake the look of a JTabbedPane, by implementing an empty JTabbedPane solely for the look, and to use a single fixed JPanel whose contents will be refreshed based on the tab clicked.
(Normally, I could just recreate the JPanel n-times, but that would nightmarish for the presenter classes who control the UI, which is beyond the scope of my question.)
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class CustomTabbedPane implements Runnable
{
static final int MAX_TABS = 11; // includes the "add" tab
JPanel pnlTabs;
JTabbedPane tabbedPane;
public static void main(String[] args)
{
SwingUtilities.invokeLater(new CustomTabbedPane());
}
public void run()
{
JPanel p = buildPanel();
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(p);
frame.setSize(800,400);
frame.setVisible(true);
}
private JPanel buildPanel()
{
tabbedPane = new JTabbedPane()
{
#Override
public Dimension getPreferredSize()
{
Dimension dim = super.getPreferredSize();
dim.height = getUI().getTabBounds(this, 0).height + 1;
return dim;
}
};
tabbedPane.addTab("Tab 1", getEmptyComp());
tabbedPane.addTab("Tab 2", getEmptyComp());
tabbedPane.addTab("+", new TabCreator());
tabbedPane.addMouseListener(new MouseAdapter()
{
#Override
public void mouseClicked(MouseEvent e)
{
addTab();
}
});
JScrollPane scroll = new JScrollPane(new JTable(5,10));
JPanel p = new JPanel(new BorderLayout());
p.add(tabbedPane, BorderLayout.NORTH);
p.add(scroll, BorderLayout.CENTER);
p.setBorder(BorderFactory.createLineBorder(Color.BLUE.darker(), 1));
return p;
}
private void addTab()
{
if (tabbedPane.getSelectedComponent() instanceof TabCreator)
{
int selIndex = tabbedPane.getSelectedIndex();
if (tabbedPane.getComponentCount() < MAX_TABS)
{
if (selIndex == tabbedPane.getComponentCount()-1)
{
String title = "Tab " + (selIndex + 1);
tabbedPane.insertTab(title, null, getEmptyComp(), "", selIndex);
tabbedPane.setSelectedIndex(selIndex);
if (tabbedPane.getComponentCount() == MAX_TABS)
{
tabbedPane.setEnabledAt(MAX_TABS-1, false);
}
}
}
}
}
private Component getEmptyComp()
{
return Box.createVerticalStrut(1);
}
class TabCreator extends JLabel {}
}
Great question! But it's fairly straightforward to get a hint on what's happening.
The problem is that your content does not have a minimum width, preferred size is not set, tab placement is top/bottom and the UI is default.
Since preferred size is not set, then when the layout is revalidated the calculations of space required go into the BasicTabbedPaneUI method Dimension calculateSize(false).
That reads:
int height = 0;
int width = 0;
<other vars>
// Determine minimum size required to display largest
// child in each dimension
<actual method>
Here it calculates the minimum size to accommodate any child and stores it into height/width. In your case this yields something like 10,10 (because of the single Label tab creator I think, I didn't follow that one).
Then happens the magic:
switch(tabPlacement) {
case LEFT:
case RIGHT:
height = Math.max(height, calculateMaxTabHeight(tabPlacement));
tabExtent = preferredTabAreaWidth(tabPlacement, height - tabAreaInsets.top - tabAreaInsets.bottom);
width += tabExtent;
break;
case TOP:
case BOTTOM:
default:
width = Math.max(width, calculateMaxTabWidth(tabPlacement));
tabExtent = preferredTabAreaHeight(tabPlacement, width - tabAreaInsets.left - tabAreaInsets.right);
height += tabExtent;
}
What happens here is it sets the preferred width to be the maximum of the largest tab width and the largest child width. In your case it's around 44 for the tab text. The tabExtent is then calculated to see just how many rows of tabs are needed to support this preferred width. In your case - it's 1 extra row of tabs for each tab. That's where the extra height in preferredSize().height comes from. Essentially because for horizontal tab placement it cares about width first, then height.
How to fix:
Set a preferred size :) I know a lot of people say don't set the preferred size, but in this case this will just work. Since a preferred size is set (via actually setting it, not overriding getPreferredSize()), the code will never get to counting tabs.
Give at least one of your children a size (via setPreferredSize or overriding getPreferredSize). If one of the childrens width is that of the frame, or, say, the table at the bottom the TabbedPane will not be allocating an extra row for each tab, since a single row will fit everything.
Make your own UI for the tabbed pane. It may be easier to make your own tabbed pane though really, I've never done this.
EDIT:
After thinking about this a bit more, I realized that solution number 1 AND your own solution suffer from the flaw that, if the tabbed pane actually does require multiple rows for the tabs (hello frame resizes), bad things will happen. Don't use it.
I need to create some tabbedpane called "menu" and in each of them I need to put a Jlist (product) with some other data extracted from a mysql db. I created this method but unfortunately it shows only the last jlist, the others menu tabbedpane are empty. Why? Thanks.
for (int i = 0; i < menuLista.size(); i++) {
int menuId = menuLista.get(i).getMenuId();
jProductList = new JList(modelProductList);
prodottiLista.clear();
modelProductList.clear();
prodottiLista = DBManager.fillProductList(menuId);
for (int b = 0; b < prodottiLista.size(); b++) {
modelProductList.addElement((Product) prodottiLista.get(b));
}
jProductList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
jProductList.addListSelectionListener(new ListSelectionListener() {
#Override
public void valueChanged(javax.swing.event.ListSelectionEvent evt) {
jProductListValueChanged(evt);
}
});
JPanel pL = new JPanel();
pL.add(jScrollPane3);
// panelList.add(pL);
jTabbedPane1.addTab(menuLista.get(i).getMenuName(), pL);
jScrollPane3.setViewportView(jProductList);
}
I've modified the code like this:
for (int i = 0; i < menuLista.size(); i++) {
int menuId = menuLista.get(i).getMenuId();
modelProductList.clear(); // ricarico la lista dei prodotti.
prodottiLista = DBManager.fillProductList(menuId);
for (int b = 0; b < prodottiLista.size(); b++) {
modelProductList.addElement((Product) prodottiLista.get(b));
}
System.out.println(modelProductList);
JScrollPane scrollPane = new JScrollPane(jProductList);
jTabbedPane1.addTab(menuLista.get(i).getMenuName(), scrollPane);
jProductList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
jProductList.addListSelectionListener(new ListSelectionListener() {
#Override
public void valueChanged(javax.swing.event.ListSelectionEvent evt) {
jProductListValueChanged(evt);
}
});
}
But It still doesn't work as I expected.
EDITED AGAIN:
I noticed that if I put this line:
System.out.println(modelProductList);
after that block of code, it prints exactly only the last products.
So, it's not a problem of visualization. Any suggestion?
jTabbedPane1.addTab(menuLista.get(i).getMenuName(), pL);
jScrollPane3.setViewportView(jProductList);
You add the last JList created to the viewport of the scroll pane. A viewport can only contain a single component.
I would guess you should be adding the tabbed pane to the viewport of the scroll pane. The tabbed pane should be added to the viewport outside of the loop, after all the tabs have been created.
Edit:
Sorry, I misread your code, I see you are adding the list to the scrollpane to a panel to the tabbed pane, so my suggestion is wrong.
However, I was close (I think?). I believe the problem is now with this line:
pL.add(jScrollPane3);
You only have a single instance of the scroll pane, so it can only be displayed on the last tab.
You need to create a new scroll pane for each tab. I would use code like the following:
JScrollPane scrollPane = new JScrollPane( jProductList );
jTabbedPane1.addTab(menuLista.get(i).getMenuName(), scrollPane);
There is no need to add the scroll pane to the panel first. Now the scrollpane will resize as the size of the tabbed pane resizes.
Also, as a side not you should not be using instance variables in your for loop. Instead you should just be using local variables, since these variables will only exist to help you create the GUI objects you add to the tabbed pane.
For example, jProductList, prodottiLista, modelProductList, should all be local variables and you need to create a new instance of each class. You can't just clear() the contents from the model, because each JList will not have a reference to the same model, so again the data that you add to the last tab will be displayed on all tabs.
Ok, I've resolved. This is the working code:
for (int i = 0; i < menuLista.size(); i++) {
int menuId = menuLista.get(i).getMenuId();
DefaultListModel modelProductList = new DefaultListModel();
JList jProductList = new JList(modelProductList);
prodottiLista = DBManager.fillProductList(menuId);
for (int b = 0; b < prodottiLista.size(); b++) {
modelProductList.addElement((Product) prodottiLista.get(b));
}
JScrollPane scrollPane = new JScrollPane(jProductList);
jTabbedPane1.addTab(menuLista.get(i).getMenuName(), scrollPane);
jProductList.addListSelectionListener(new ListSelectionListener() {
#Override
public void valueChanged(javax.swing.event.ListSelectionEvent evt) {
jProductListValueChanged(evt);
}
});
jTable1.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
jProductList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
}
I have a very simple Java program (see below). The GridLayout has 20 rows and 4 columns. As you know the elements are supposed to be added horizontally by (GridLayout) definition. However, I get the two elements (labels) placed one above the other, vertically.
I colored them and realised the labels take up the whole row, hence the vertical effect. But then I also used setSize(5,5) with each to make them smaller, however they still take up the whole row. Any advice as to why this happens and how to fix/set smaller size/etc?
public class Sam extends JFrame {
public JButton btn_arr;
public Container c;
public JLabel[] lbl = new JLabel[20];
public Sam()
{
c = getContentPane();
c.setLayout(new GridLayout(20,4));
lbl[1] = new JLabel("Column1");
c.add(lbl[1]);
lbl[2] = new JLabel("Column2");
c.add(lbl[2]);
show();
}
public static void main(String[] args)
{
Sam x = new Sam();
x.setVisible(true);
x.setSize(7500,4500);
}
}
You're only adding two components to the grid so they will fill it up. You need to add more components to the grid as placeholders so that it can place the original JLabels in their proper place, perhaps empty JLabels or JPanels.
As an aside, you should avoid setting the size of any Swing component. Your current size of 7500, 4500 is a bit on the large size.
As a second aside, perhaps you want to use a JTable instead here.
Edit: if you want a GridLayout with 4 columns and variable number of rows, use 0 for your GridLayout row constant:
c.setLayout(new GridLayout(0, 4));
e.g.,
import java.awt.*;
import javax.swing.*;
public class Sam extends JFrame {
public static final int COLUMN_COUNT = 4;
public JButton btn_arr;
public Container c;
public JLabel[] lbl = new JLabel[COLUMN_COUNT];
public Sam() {
c = getContentPane();
c.setLayout(new GridLayout(0, COLUMN_COUNT));
for (int i = 0; i < lbl.length; i++) {
lbl[i] = new JLabel("Column " + (i + 1));
c.add(lbl[i]);
}
}
public static void main(String[] args) {
Sam x = new Sam();
x.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
x.pack();
x.setLocationRelativeTo(null);
x.setVisible(true);
// x.setSize(7500,4500);
}
}
But still I wonder if a JTable wouldn't work better here.
One thing to keep in mind with the GridLayout is it that it is designed to cover the entire containing panel sizing the cells as equally as possible, and elements added to the cells will be expanded to fill the entire cell. So as the cell sizes change, the labels will also change in size. Effectively grid cells force an expansion/contraction in both X and Y direction of all contained elements.
One way to prevent that from happening if you must use the GridLayout is to not add the labels directly to the container that uses the GridLayout, but instead put each label inside a JPanel that uses a FlowLayout (the default) that you can set alignment of either Left, Middle or Right, then add that JPanel to the Grid container. The JPanel will be resized but it will not change the size of the Label.
Or use the GridBagLayout manager. More complex, but once you understand it, it makes life easier. But as Hovercraft mentioned, if what you are trying to do is create a grid with column headers, a JTable might be a better option.
Hi here are my codes for my table settings:
String [] column = {"MacAddress","PcName","OperatingSystem","IpAddress","Port","Status"};
model = new DefaultTableModel(0,column.length);
model.setColumnIdentifiers(column);
mainTable = new JTable(model);
mainTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
for(int i=0;i<=column.length-1;i++){
mainTable.getColumnModel().getColumn(i).setPreferredWidth(300);
}
pane = new JScrollPane(mainTable);
pnlTabel = new JPanel();
pnlTabel.setBorder(BorderFactory.createTitledBorder(""));
pnlTabel.setPreferredSize(new Dimension(dim.width*70/100, dim.height*60/100));
pnlTabel.add(pane);
addMainPanel(pnlTabel);
Here is my addMainPanel() function:
public void addMainPanel(Component pnl){
mainPanel.add(pnl);
mainPanel.revalidate();
}
And here is my code for my mainPanel:
mainPanel = new JPanel();
mainPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
add(mainPanel,"Center");
and I'm using border layout for my frame:
setLayout(new BorderLayout(0,0));
My problem is that, even i use this set of code to set my JTable to fit but it seems to fail all the this, this code:
mainTable.setAutoResizeMode(JTa![enter image description here][1]ble.AUTO_RESIZE_OFF);
for(int i=0;i<=column.length-1;i++){
mainTable.getColumnModel().getColumn(i).setPreferredWidth(300);
}
When is use that code, my jtable does not resize but only add on a horizontal scroll bar at the bottom.
No offense meant but .. your code and consequently your question is a mess ;-) Plus you don't explain what exactly you want to achieve.
Trying to detangle, taking the nested layouts/resizing characteristics (as seen in the snippets, might not be complete):
frame // BorderLayout
mainPanel // FlowLayout
pnlTabel // FlowLayout, hard-coded prefSize
pane // scrollPane
mainTable // auto-resize-off
Aside: intentionally kept untelling names to demonstrate how mixing naming schemes tend to contribute to overall confusion :-) Doesn't really matter whether you decide for pre or postFixing some type-related marker, but if you do be consistent.
In that hierarchy, there are two levels of FlowLayout which basically will layout their children at their respective prefs and adjusting their own pref accordingly, lest the pref were hard-coded on the level of the pnlTable: however the table's pref will be changed (by changing the column prefs) it cannot bubble further up because ... hard-coding the pref leads not calculating its size (neither by layoutManager and nor uiDelegate, both never get a chance to do it)
Another issue - the more interesting one :-) - is that the JScrollPane is somewhat special in
calculating its own prefSize from its view's pref/scrollablePrefViewportSize depending on whether or not the view implements Scrollable (JTable does so, though in a crappy way)
being a validationRoot: invalidating the view (or any children) doesn't bubble further up the hierarchy
Assuming that you want the table's scrollPane to grow if the prefWidts of the columns change, there are two thingies to tweak:
implement table's getPreferredScrollableWidth to return a value calculated based on the prefWidth of the columns
revalidate a container higher up in the hierarchy
Some code to play with:
final JTable table = new JTable(50, 10) {
// properties to base a reasonable prefScrollable size
int visibleColumns = 3;
int visibleRows = 10;
// hard-coded default in super
Dimension dummySuper = new Dimension(450, 400);
#Override
public Dimension getPreferredScrollableViewportSize() {
Dimension dim = super.getPreferredScrollableViewportSize();
if (!dummySuper.equals(dim)) return dim;
dim = new Dimension();
for (int column = 0; column < Math.min(visibleColumns, getColumnCount()); column++) {
dim.width += getColumnModel().getColumn(column).getPreferredWidth();
}
dim.height = visibleRows * getRowHeight();
return dim;
}
};
table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
for (int i = 0; i < table.getRowCount(); i++) {
table.setValueAt("row: " + i, i, 0);
}
JComponent tablePanel = new JPanel();
tablePanel.add(new JScrollPane(table));
Action sizeColumns = new AbstractAction("size columns") {
int prefWidth = 75;
#Override
public void actionPerformed(ActionEvent e) {
int newWidth = prefWidth + 15;
for (int i = 0; i < table.getColumnCount(); i++) {
if (table.getColumnModel().getColumn(i).getPreferredWidth() == prefWidth)
table.getColumnModel().getColumn(i).setPreferredWidth(newWidth);
}
prefWidth = newWidth;
// revalidate "higher up" than the table itself
frame.revalidate();
}
};
frame.add(new JButton(sizeColumns), BorderLayout.SOUTH);
If you want a JTable to fill the available space, you should put it inside a JPanel which has a BorderLayout layout manager. Also don't forget about the JScrollPane which ensures that if the table doesn't fit into the view (e.g. too many rows), scrollbars will appear:
JFrame frame = new JFrame();
// set up frame
JTable table = new JTable();
// Set up table, add data
// Frame has a content pane with BorderLayout by default
frame.getContentPane().add( new JScrollPane( table ), BorderLayout.CENTER );
If you have other content you wish to display besides the table, you can add those to the NORTH, SOUTH, EAST, WEST parts of the content panel (which can be wrapped into other panels if more components are to be placed there).
I'm having a little trouble building the grids for a Battleship game for my Java class. So far, I can easily make a for loop to add JPanel or JButton objects to the JFrame. However, my issue is that I'll need to use those Panels or Buttons again when playing the game (such as clicking on a button to see if your opponent put a ship on that square, et cetera). Is there a simple way in Java to initialize reference variables for a LOT of objects? Or will I have to declare all of them individually?
You could try a multi dimensional array of JPanels (or any other object). Create an array with the same size as your grid. The line below initializes an array with 5 rows and 5 columns.
JPanel[][] battleField = new JPanel[5][5];
Use nested for loops to create the panels in the array.
for (int rowIndex = 0; rowIndex < battleField.length; rowIndex++)
{
for (int cellIndex = 0; cellIndex < battleField[rowIndex]; cellIndex++)
{
battleField[rowIndex][cellIndex] = new JPanel();
}
}
If you want to reference the battleField array later on you would just make it into a instance variable.
For a battleship game, you most likely want to retrieve the location of a button after is has been clicked. You can create a hashtable using your buttons as keys and the point it is located at as a value.
HashMap<JButton, Point> buttonMap = new HashMap<JButton, Point>();
for (int x = 0; x < COLUMNS; x++)
{
for (int y = 0; y < ROWS; y++)
{
JButton btn = new JButton();
btn.addActionListener(this);
buttonMap.put(btn, new Point(x, y));
//then add the button to your container
}
}
The in your actionPerformed method you can convert the button to the point it is located at like this.
JButton btn = (JButton)actionEvent.getSource();
Point p = buttonMake.get(btn);
Of course you will need to properly handle error conditions such as source not being a button or the button not being in the map...
You can always extend JButton to keep track of the info you need. A simple example:
class MyButton extends JButton{
private MyGameInfo mygameInfo;
private int buttonId;
//More fields....
//Getters/Setters
}
Then instead of creating and adding JButton objects to your layout, create MyButton objects(which is also a JButton so your layout will not be effected) and use its extra functionality for your game logic.
For reference, here is a related matching game that uses a grid of buttons. This related answer demonstrates the application of the Model–View–Controller pattern to a simple game.