I'm building a user interface using JComponents.
The UI should work in this way: depending on the value set (e.g. through a JComboBox), different JComponents are displayed.
My though was to #Override the isVisible() method of each component or, alternatively, to set them visible or not through the JComboBox. Indeed, both the two methods works, in the sense that the desired components appear and disappear from JPanel when I change the JComboBox value.
The problem is that, even if a component is not visible inside the panel, it is still active and clicking on the position in which it should be (if visible), it triggers its action. E.g. I move the mouse over the position in which another JComboBox should be; the combobox is not visible, but mouse cursor turns into Cursor.HAND_CURSOR and, if I click, the popup list of combobox items appears and I'm able to select one of these items.
Here is a salmple of the code I'm using:
public class MyPanel extends JPanel{
public MyPanel(){
super(new GridBagLayout());
GridBagConstraints g = new GridBagConstraints(
0, // int gridx
-1, // int gridy
1, // int gridwidth
1, // int gridheight
1, // double weightx
1, // double weighty
GridBagConstraints.NORTHWEST, // int anchor
GridBagConstraints.NONE, // int fill
new Insets(0,0,0,0), // Insets insets
0, // int ipadx
0 // int ipady
);
String[] comboItems = {"ShowCombo1","ShowCheckbox","ShowTextField"};
JComboBox combo = new JComboBox(comboItems);
JComboBox combo1 = new JComboBox(){
#Override
public boolean isVisible(){
return combo.getSelectedIndex()==0;
}
};
JCheckBox checkBox= new JCheckBox(){
#Override
public boolean isVisible(){
return combo.getSelectedIndex()==1;
}
};
JTextField textField= new JTextField(){
#Override
public boolean isVisible(){
return combo.getSelectedIndex()==2;
}
};
add(combo,g);
g.gridy += 1;
add(combo1,g);
g.gridy += 1;
add(checkBox,g);
g.gridy += 1;
add(textField,g);
}
}
Is there a better way to manage these kind of user interfaces?
I think with your approach you are just return the flag to true based on the selection. Internally the setVisible(false) does more than just switching the flag off. It calls super.setVisible(false) which does the below.
/**
* Shows or hides this component depending on the value of parameter
* <code>b</code>.
* <p>
* This method changes layout-related information, and therefore,
* invalidates the component hierarchy.
*
* #param b if <code>true</code>, shows this component;
* otherwise, hides this component
* #see #isVisible
* #see #invalidate
* #since JDK1.1
*/
public void setVisible(boolean b) {
show(b);
}
/**
* #deprecated As of JDK version 1.1,
* replaced by <code>setVisible(boolean)</code>.
*/
#Deprecated
public void show() {
if (!visible) {
synchronized (getTreeLock()) {
visible = true;
mixOnShowing();
ComponentPeer peer = this.peer;
if (peer != null) {
peer.setVisible(true);
createHierarchyEvents(HierarchyEvent.HIERARCHY_CHANGED,
this, parent,
HierarchyEvent.SHOWING_CHANGED,
Toolkit.enabledOnToolkit(AWTEvent.HIERARCHY_EVENT_MASK));
if (peer instanceof LightweightPeer) {
repaint();
}
updateCursorImmediately();
}
if (componentListener != null ||
(eventMask & AWTEvent.COMPONENT_EVENT_MASK) != 0 ||
Toolkit.enabledOnToolkit(AWTEvent.COMPONENT_EVENT_MASK)) {
ComponentEvent e = new ComponentEvent(this,
ComponentEvent.COMPONENT_SHOWN);
Toolkit.getEventQueue().postEvent(e);
}
}
Container parent = this.parent;
if (parent != null) {
parent.invalidate();
}
}
}
Though i do not understand what they actually do :-), I feel this part of code needs to be executed.
Related
I am working in a Java desktop game where I am trying to display some panels composed by a JTable with a JScrollPane, which has only one column and several rows, containing the data I want to display. This panels are not visible when the application starts, because they are shown depending on the JButton pressed.
When I start my application, these panels are already added to the frame, but not visible. At the moment I try to display them by pressing the corresponding button, they are shown, but not all the rows are visible. If I press the button again, the panel is hidden, and if I press again the button to display it again, now it is shown with all its rows visible.
For some reason, just by setting its visibility to true -> false -> true by pressing the corresponding button, the JTable of the panel displays all its rows, but when I display it the first time, it doesn't... I need to do it twice.
To check this, I try to set the panel visibility to true -> false -> true when the application starts programmatically, but it doesn't works. It only works if I press the button that displays the panel (remember, twice, the first time it doesn't shows all the rows, the second time it does). Also, I've checked by debugging if, at the moment I set the panel visibility to true the first time, it has all the data inside, and it is correct, so it is something just only graphic, because the data is there. Also I realized that by scrolling with the mouse the first time I show the panel, it shows all the data, so I guess it must be refreshing something when I scroll, or when I show the panel for the second time I press the corresponding button.
I've tried to revalidate() and repaint() the table, to fireTableDataChanged() and fireTableStructureChanged() the model of the table. Also I've created a thread which starts at the same time the application starts, which sets the visibility of these panels to true and false at the beginning, and revalidating the tables, just to try to see what could be happening when showing/hidding the panels.
After create this thread, I realized that if I set the visibility of this panels to true from the beginning, I see how the UI thread "refresh" the panels in some way, as I can see them uncomplete, and after complete, in a split second. But after, I have to close them manually, because I cannot control (I tried) the time needed to see them filled before close them.
Here is a picture of the panel when I setVisible(true) one of these panels:
And here is the picture of the same panel when I hide and show it again (manually), without perform any other actions:
Here is the code of the table model set in the JTable of the panel:
/**
* Class to create the model instance for the selector panel list
*/
public class CustomTableModelForPredefinedMessagesSelectorKeyboard extends DefaultTableModel {
private ArrayList selectedList = new ArrayList<>();
public CustomTableModelForPredefinedMessagesSelectorKeyboard() {
this.addColumn(LanguageConfiguration.getLanguageSelected().getString("selection"));
//setListType(ListType.PREDEFINED_MESSAGES);
setListType();
}
/**
* Function to get the size of the list
*
* #return the list size
*/
//#Override
public int getSize() {
return selectedList.size();
}
/**
* Function to get the element of a specific index from the list
*
* #param index index of the message to be retrieved
* #return the message text of the corresponding index
*/
public Object getElementAt(int index) {
return selectedList.get(index).toString();
}
public ArrayList getSelectedList() {
return selectedList;
}
public void setSelectedList(ArrayList selectedList) {
this.selectedList = selectedList;
}
/**
* Function to set the list of the selector panel according to the list type selected
*/
#SuppressWarnings("unchecked")
public void setListType() {
selectedList.add("selectorPanel_PredefMsg_EnemyOnSight");
selectedList.add("selectorPanel_PredefMsg_GreenAlert");
selectedList.add("selectorPanel_PredefMsg_HowAreYou");
selectedList.add("selectorPanel_PredefMsg_InminentAttack");
selectedList.add("selectorPanel_PredefMsg_No");
selectedList.add("selectorPanel_PredefMsg_Positioned");
selectedList.add("selectorPanel_PredefMsg_Ready");
selectedList.add("selectorPanel_PredefMsg_RedAlert");
selectedList.add("selectorPanel_PredefMsg_ReturnToBase");
selectedList.add("selectorPanel_PredefMsg_TargetOutOfReach");
selectedList.add("selectorPanel_PredefMsg_WaitingOrders");
selectedList.add("selectorPanel_PredefMsg_WeHaveAProblem");
selectedList.add("selectorPanel_PredefMsg_YellowAlert");
selectedList.add("selectorPanel_PredefMsg_Yes");
for (Object string : selectedList)
this.addRow(new Object[]{LanguageConfiguration.getLanguageSelected().getString((String) string)});
if (SwingInterfaceSubJPanels.getPredefinedMessagesSelectorJPanel() != null)
for (Component component : SwingInterfaceSubJPanels.getPredefinedMessagesSelectorJPanel().getComponents())
if (component instanceof JScrollPane) {
JScrollPane messagesListScrollPane = (JScrollPane) component;
JViewport viewport = messagesListScrollPane.getViewport();
preselectFirstElement((CustomJTable) viewport.getView());
break;
}
}
/**
* Function to pre-select the firs item of the list in the selector list
*
* #param customJTable the table where the selection will be performed
*/
private void preselectFirstElement(CustomJTable customJTable) {
if (customJTable.getRowCount() > 0)
customJTable.getSelectionModel().setSelectionInterval(0, 0);
}
}
And here is the code of the panel:
/**
* Class to create and manage a selector keyboard
*/
public class PredefinedMessagesSelectorKeyboard extends JPanel {
/**
* Color of shadow
*/
private final Color shadowColor;
/**
* Double values for Horizontal and Vertical radius of corner arcs
*/
private final Dimension arcs;
/**
* Variables to get the coordinates of the mouse on mouse click event over the jpanel
*/
private int xJPanelCoord, yJPanelCoord;
Component componentToManage;
SelectorConfirmationController.Type selectorPanelType;
/**
* Constructor to create the selector panel structure and add its components
*/
public PredefinedMessagesSelectorKeyboard() {
DisplayMode displayMode = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDisplayMode();
double screenWidth = displayMode.getWidth();
double screenHeight = displayMode.getHeight();
this.setPreferredSize(new Dimension((int) Math.round(screenWidth * 0.35), (int) Math.round(screenHeight * 0.6)));
//this.setBackground(new java.awt.Color(5, 122, 122));
this.setBackground(new Color(150, 150, 150));
addSelectorPanelComponents();
setVisible(false);
setOpaque(false);
makeJPanelDraggable();
shadowColor = Color.black;
arcs = new Dimension(100, 100);
}
/**
* Function to add the components of the JPanel
*/
private void addSelectorPanelComponents() {
this.setLayout(new GridBagLayout());
addSelectorPanelJTable();
addSelectorPanelButtons();
addSelectorPanelCloseJButton();
}
/**
* Function to add the panel with the elements available for selection
*/
private void addSelectorPanelJTable() {
GridBagConstraints gridBagConstraints = new GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 0;
gridBagConstraints.gridwidth = 4;
gridBagConstraints.gridheight = 2;
gridBagConstraints.weighty = 1.0;
gridBagConstraints.fill = GridBagConstraints.BOTH;
gridBagConstraints.insets = new Insets(40, 20, 20, 0);
CustomTableModelForPredefinedMessagesSelectorKeyboard customTableModelForPredefinedMessagesSelectorPanelList = new CustomTableModelForPredefinedMessagesSelectorKeyboard();
CustomJTable predefinedMessagesselectorPanelJTable = new CustomJTable(customTableModelForPredefinedMessagesSelectorPanelList);
predefinedMessagesselectorPanelJTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
predefinedMessagesselectorPanelJTable.setName("predefinedMessagesSelectorPanelJTable");
JScrollPane selectorJTableJScrollPane = new JScrollPane(predefinedMessagesselectorPanelJTable);
selectorJTableJScrollPane.getViewport().setBackground(new Color(24, 27, 34));
add(selectorJTableJScrollPane, gridBagConstraints);
}
/**
* Function to add the close panel button and the selection confirmation button
*/
private void addSelectorPanelButtons() {
GridBagConstraints gridBagConstraints = new GridBagConstraints();
gridBagConstraints.gridx = 4;
gridBagConstraints.gridy = 1;
gridBagConstraints.insets = new Insets(20, 20, 0, 20);
JButton selectPreviousButton = createScaledSwingShapedButton("/images/boton_popup_panel_selector_anterior.png",
"/images/boton_popup_panel_selector_anterior_seleccionado.png");
selectPreviousButton.setName("selectorPanelPreviousButton");
selectPreviousButton.setToolTipText(LanguageConfiguration.getLanguageSelected().getString("tooltipTextPreviousButton"));
selectPreviousButton.addActionListener(e -> {
for (Component component : getComponents())
if (component instanceof JScrollPane) {
JScrollPane messagesListScrollPane = (JScrollPane) component;
JViewport viewport = messagesListScrollPane.getViewport();
selectPrevious((CustomJTable) viewport.getView());
break;
}
});
JButton confirmSelectedButton = createScaledSwingShapedButton("/images/boton_popup_panel_selector_confirmar.png",
"/images/boton_popup_panel_selector_confirmar_seleccionado.png");
confirmSelectedButton.setName("selectorPanelConfirmButton");
confirmSelectedButton.setToolTipText(LanguageConfiguration.getLanguageSelected().getString("tooltipTextConfirmButton"));
confirmSelectedButton.addActionListener(new SelectorConfirmationController(componentToManage, selectorPanelType));
JButton selectNextButton = createScaledSwingShapedButton("/images/boton_popup_panel_selector_siguiente.png",
"/images/boton_popup_panel_selector_siguiente_seleccionado.png");
selectNextButton.setName("selectorPanelNextButton");
selectNextButton.setToolTipText(LanguageConfiguration.getLanguageSelected().getString("tooltipTextNextButton"));
selectNextButton.addActionListener(e -> {
for (Component component : getComponents()){
if (component instanceof JScrollPane){
JScrollPane messagesListScrollPane = (JScrollPane) component;
JViewport viewport = messagesListScrollPane.getViewport();
selectNext((CustomJTable) viewport.getView());
break;
}
}
});
JPanel lateralButtonsPanel = new JPanel();
lateralButtonsPanel.setLayout(new BoxLayout(lateralButtonsPanel, BoxLayout.Y_AXIS));
lateralButtonsPanel.setOpaque(false);
lateralButtonsPanel.setName("lateralButtonsPanel");
lateralButtonsPanel.add(selectPreviousButton);
lateralButtonsPanel.add(confirmSelectedButton);
lateralButtonsPanel.add(selectNextButton);
add(lateralButtonsPanel, gridBagConstraints);
}
/**
* Function to create and add the close panel button to the panel
*/
private void addSelectorPanelCloseJButton() {
GridBagConstraints gridBagConstraints = new GridBagConstraints();
gridBagConstraints.gridx = 4;
gridBagConstraints.gridy = 0;
//gridBagConstraints.anchor = GridBagConstraints.NORTHEAST;
gridBagConstraints.insets = new Insets(20, 20, 0, 20);
JButton closeMessagesConsoleButton = createScaledSwingShapedButton("/images/boton_consola_mensajes_2.png",
"/images/boton_consola_mensajes_2_seleccionado.png");
closeMessagesConsoleButton.setName("selectorPanelCloseButton");
closeMessagesConsoleButton.setToolTipText(LanguageConfiguration.getLanguageSelected().getString("tooltipTextClosePanelButton"));
//closeMessagesConsoleButton.addActionListener(e -> setVisible(false));
closeMessagesConsoleButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
/*if (getComponentToManage().getName().equals("messageBoxTable"))*/
SwingInterfaceSubJPanels.getMainScreenUpperInformationLabel().setVisible(false);
setVisible(false);
}
});
add(closeMessagesConsoleButton, gridBagConstraints);
}
/**
* Function to select the next item of the list in the selector list
* #param customJTable the table where the selection will be performed
*/
private void selectPrevious(CustomJTable customJTable){
if (customJTable.getSelectedRow() > 0) {
customJTable.getSelectionModel().setSelectionInterval(customJTable.getSelectedRow() - 1, customJTable.getSelectedRow() - 1);
customJTable.scrollRectToVisible(new Rectangle(customJTable.getCellRect(customJTable.getSelectedRow()-1, 0, true)));
}
}
/**
* Function to select the next item of the list in the selector list
* #param customJTable the table where the selection will be performed
*/
private void selectNext(CustomJTable customJTable){
if (customJTable.getSelectedRow() >= 0 && customJTable.getSelectedRow() < customJTable.getRowCount()-1) {
customJTable.getSelectionModel().setSelectionInterval(customJTable.getSelectedRow() + 1, customJTable.getSelectedRow() + 1);
customJTable.scrollRectToVisible(new Rectangle(customJTable.getCellRect(customJTable.getSelectedRow()+1, 0, true)));
}
}
public Component getComponentToManage() {
return componentToManage;
}
public void setComponentToManage(Component componentToManage) {
this.componentToManage = componentToManage;
}
public SelectorConfirmationController.Type getSelectorPanelType() {
return selectorPanelType;
}
public void setSelectorPanelType(SelectorConfirmationController.Type selectorPanelType) {
this.selectorPanelType = selectorPanelType;
}
/**
* Function to create and return a scaled swing shaped button with its corresponding images
*
* #param buttonImage button image when the button is not pressed
* #param buttonPressedImage button image when the button is pressed
* #return the button already built
*/
private JButton createScaledSwingShapedButton(String buttonImage, String buttonPressedImage) {
Image scaledImage;
try {
SwingShapedButton swingShapedButton = new SwingShapedButton();
JButton newButton = (JButton) swingShapedButton.createComponent(SwingShapedButton.ButtonShape.ROUND);
Image imageButton = ImageIO.read(getClass().getResource(buttonImage));
scaledImage = scaleImage(imageButton);
newButton.setIcon(new ImageIcon(scaledImage));
Image imageButtonPressed = ImageIO.read(getClass().getResource(buttonPressedImage));
scaledImage = scaleImage(imageButtonPressed);
newButton.setPressedIcon(new ImageIcon(scaledImage));
return newButton;
} catch (IllegalArgumentException | IOException e) {
e.printStackTrace();
DataBase.database.insertSystemErrorLog(Thread.currentThread().getStackTrace()[1].getMethodName(), e);
return (JButton) new SwingShapedButton().createComponent(SwingShapedButton.ButtonShape.ROUND);
}
}
/**
* Function to scale the images of the buttons according to the screen resolution
*
* #param imageButton the image to be set to the button
* #return the image resized
*/
private Image scaleImage(Image imageButton) {
return imageButton.getScaledInstance((int) Math.round(((BufferedImage) imageButton).getWidth() * InterfaceControlsManager.getWidth_percentage()),
(int) Math.round(((BufferedImage) imageButton).getHeight() * InterfaceControlsManager.getHeight_percentage()),
Image.SCALE_SMOOTH);
}
/**
* Function to allow the panel to be draggable over the frame
*/
private void makeJPanelDraggable() {
this.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
xJPanelCoord = e.getX();
yJPanelCoord = e.getY();
}
});
this.addMouseMotionListener(new MouseMotionAdapter() {
#Override
public void mouseDragged(MouseEvent e) {
int x = e.getXOnScreen() - xJPanelCoord;
int y = e.getYOnScreen() - yJPanelCoord;
setLocation(x, y);
}
});
}
/**
* Function to set the appearance of the panel
*
* #param g the graphics to be set
*/
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int width = getWidth();
int height = getHeight();
/*
Distance between shadow border and opaque panel border
*/
int shadowGap = 5;
int shadowAlpha = 10;
Color shadowColorA = new Color(shadowColor.getRed(),
shadowColor.getGreen(), shadowColor.getBlue(), shadowAlpha);
Graphics2D graphics = (Graphics2D) g;
//Sets antialiasing if HQ.
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
//Draws shadow borders if any.
int strokeSize = 1;
boolean shady = true;
graphics.setColor(shadowColorA);
int shadowOffset = 4;
graphics.fillRoundRect(
shadowOffset,// X position
shadowOffset,// Y position
width - strokeSize - shadowOffset, // width
height - strokeSize - shadowOffset, // height
arcs.width, arcs.height);// arc Dimension
//Draws the rounded opaque panel with borders.
graphics.setColor(getBackground());
graphics.fillRoundRect(0, 0, width - shadowGap,
height - shadowGap, arcs.width, arcs.height);
graphics.setColor(getForeground());
graphics.setStroke(new BasicStroke(strokeSize));
graphics.drawRoundRect(0, 0, width - shadowGap,
height - shadowGap, arcs.width, arcs.height);
//Sets strokes to default, is better.
graphics.setStroke(new BasicStroke());
}
}
Can someone explain to me why I see this effect in the panel? There is some reason why the JTable doesn't show its data the first time I setVisible(true) the panel, but it is displayed properly the second time? Could be something with the UI, or maybe I am not realizing about something here?
Thanks for any comment you can give me!
Is there a way to adjust the drop-down window size of a JCombobox?
let's say I have:
comArmor.setBounds(81, 102, 194, 26);
But when the user selects the box and the drop-down list pops up, i'd like for the drop-down window to expand so that a long line of text will be displayed entirely (say size x of 300).
Is this possible?
Small hack to get pop up menu size bigger enough to show items even though the combo box size could be smaller
Source: http://www.jroller.com/santhosh/entry/make_jcombobox_popup_wide_enough
import java.awt.Dimension;
import java.util.Vector;
import javax.swing.ComboBoxModel;
import javax.swing.JComboBox;
public class ComboBoxFullMenu<E> extends JComboBox<E> {
public ComboBoxFullMenu(E[] items) {
super(items);
addActionListener(this);
}
public ComboBoxFullMenu(Vector<E> items) {
super(items);
addActionListener(this);
}
public ComboBoxFullMenu(ComboBoxModel<E> aModel) {
super(aModel);
addActionListener(this);
}
/**
* Small hack to get pop up menu size bigger enough to show items even though
* the combo box size could be smaller
* */
private boolean layingOut = false;
#Override
public void doLayout(){
try{
layingOut = true;
super.doLayout();
}finally{
layingOut = false;
}
}
#Override
public Dimension getSize(){
Dimension dim = super.getSize();
if ( !layingOut ) {
dim.width = Math.max(dim.width, getPreferredSize().width);
}
return dim;
}
}
Not sure if there is built in functionality for this already, but you could always have a ActionListener which listens for selection changes and then programmatically set the width of the JComboBox to the length of the selected content (getSelectedItem()) when it changes.
box.addActionListener (new ActionListener () {
public void actionPerformed(ActionEvent e) {
String item = comboBox.getSelectedItem().toString();
comboBox.setBounds(81, 102, item.length * CONSTANT, 26);
}
});
It seems a bit hacky to me and you might have to play around with this idea to get it to work.
I hope this helps you!
Update:
JComboBox appears to have a setPrototypeDisplayValue(Object) method that is used to calculate component's preferred width based on the length of the parameter.
Sets the prototype display value used to calculate the size of the display for the UI portion.
I might consider using this instead.
I have a class called ComponentMover that allows you to drag around any object that's registered with it. This works if you pass it bare JPanels, but it doesn't work if the JPanel has anything inside of it, and it just stays put.
This is ComponentMover:
public class ComponentMover extends MouseAdapter {
private Insets dragInsets = new Insets(0, 0, 0, 0);
private Dimension snapSize = new Dimension(1, 1);
private Insets edgeInsets = new Insets(0, 0, 0, 0);
private boolean changeCursor = true;
private boolean autoLayout = false;
private Class<?> destinationClass;
private Component destinationComponent;
private Component destination;
private Component source;
private Point pressed;
private Point location;
private Cursor originalCursor;
private boolean autoscrolls;
private boolean potentialDrag;
private boolean shouldLock = true;
/**
* Constructor for moving individual components. The components must be
* regisetered using the registerComponent() method.
*/
public ComponentMover() {
}
/**
* Constructor to specify a Class of Component that will be moved when drag
* events are generated on a registered child component. The events will be
* passed to the first ancestor of this specified class.
*
* #param destinationClass
* the Class of the ancestor component
* #param component
* the Components to be registered for forwarding drag events to
* the ancestor Component.
*/
public ComponentMover(Class<?> destinationClass, JComponent... components) {
this.destinationClass = destinationClass;
registerComponent(components);
}
/**
* Constructor to specify a parent component that will be moved when drag
* events are generated on a registered child component.
*
* #param destinationComponent
* the component drage events should be forwareded to
* #param components
* the Components to be registered for forwarding drag events to
* the parent component to be moved
*/
public ComponentMover(JComponent destinationComponent,
JComponent... components) {
this.destinationComponent = destinationComponent;
registerComponent(components);
}
public void setLock(boolean shouldLock) {
this.shouldLock = shouldLock;
}
/**
* Get the auto layout property
*
* #return the auto layout property
*/
public boolean isAutoLayout() {
return autoLayout;
}
/**
* Set the auto layout property
*
* #param autoLayout
* when true layout will be invoked on the parent container
*/
public void setAutoLayout(boolean autoLayout) {
this.autoLayout = autoLayout;
}
/**
* Get the change cursor property
*
* #return the change cursor property
*/
public boolean isChangeCursor() {
return changeCursor;
}
/**
* Set the change cursor property
*
* #param changeCursor
* when true the cursor will be changed to the Cursor.MOVE_CURSOR
* while the mouse is pressed
*/
public void setChangeCursor(boolean changeCursor) {
this.changeCursor = changeCursor;
}
/**
* Get the drag insets
*
* #return the drag insets
*/
public Insets getDragInsets() {
return dragInsets;
}
/**
* Set the drag insets. The insets specify an area where mouseDragged events
* should be ignored and therefore the component will not be moved. This
* will prevent these events from being confused with a MouseMotionListener
* that supports component resizing.
*
* #param dragInsets
*/
public void setDragInsets(Insets dragInsets) {
this.dragInsets = dragInsets;
}
/**
* Get the bounds insets
*
* #return the bounds insets
*/
public Insets getEdgeInsets() {
return edgeInsets;
}
/**
* Set the edge insets. The insets specify how close to each edge of the
* parent component that the child component can be moved. Positive values
* means the component must be contained within the parent. Negative values
* means the component can be moved outside the parent.
*
* #param edgeInsets
*/
public void setEdgeInsets(Insets edgeInsets) {
this.edgeInsets = edgeInsets;
}
/**
* Remove listeners from the specified component
*
* #param component
* the component the listeners are removed from
*/
public void deregisterComponent(JComponent... components) {
for (JComponent component : components)
component.removeMouseListener(this);
}
/**
* Add the required listeners to the specified component
*
* #param component
* the component the listeners are added to
*/
public void registerComponent(JComponent... components) {
for (JComponent component : components){
component.addMouseListener(this);
}
}
/**
* Get the snap size
*
* #return the snap size
*/
public Dimension getSnapSize() {
return snapSize;
}
/**
* Set the snap size. Forces the component to be snapped to the closest grid
* position. Snapping will occur when the mouse is dragged half way.
*/
public void setSnapSize(Dimension snapSize) {
if (snapSize.width < 1 || snapSize.height < 1)
throw new IllegalArgumentException(
"Snap sizes must be greater than 0");
this.snapSize = snapSize;
}
/**
* Setup the variables used to control the moving of the component:
*
* source - the source component of the mouse event destination - the
* component that will ultimately be moved pressed - the Point where the
* mouse was pressed in the destination component coordinates.
*/
#Override
public void mousePressed(MouseEvent e) {
source = e.getComponent();
int width = source.getSize().width - dragInsets.left - dragInsets.right;
int height = source.getSize().height - dragInsets.top
- dragInsets.bottom;
Rectangle r = new Rectangle(dragInsets.left, dragInsets.top, width,
height);
if (r.contains(e.getPoint()))
setupForDragging(e);
}
private void setupForDragging(MouseEvent e) {
source.addMouseMotionListener(this);
potentialDrag = true;
// Determine the component that will ultimately be moved
if (destinationComponent != null) {
destination = destinationComponent;
} else if (destinationClass == null) {
destination = source;
} else // forward events to destination component
{
destination = SwingUtilities.getAncestorOfClass(destinationClass,
source);
}
pressed = e.getLocationOnScreen();
location = destination.getLocation();
if (changeCursor) {
originalCursor = source.getCursor();
source.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
}
// Making sure autoscrolls is false will allow for smoother dragging of
// individual components
if (destination instanceof JComponent) {
JComponent jc = (JComponent) destination;
autoscrolls = jc.getAutoscrolls();
jc.setAutoscrolls(false);
}
}
/**
* Move the component to its new location. The dragged Point must be in the
* destination coordinates.
*/
#Override
public void mouseDragged(MouseEvent e) {
Point dragged = e.getLocationOnScreen();
int dragX = getDragDistance(dragged.x, pressed.x, snapSize.width);
int dragY = getDragDistance(dragged.y, pressed.y, snapSize.height);
int locationX = location.x + dragX;
int locationY = location.y + dragY;
// Mouse dragged events are not generated for every pixel the mouse
// is moved. Adjust the location to make sure we are still on a
// snap value.
if (shouldLock) {
while (locationX < edgeInsets.left)
locationX += snapSize.width;
while (locationY < edgeInsets.top)
locationY += snapSize.height;
Dimension d = getBoundingSize(destination);
while (locationX + destination.getSize().width + edgeInsets.right > d.width)
locationX -= snapSize.width;
while (locationY + destination.getSize().height + edgeInsets.bottom > d.height)
locationY -= snapSize.height;
}
// Adjustments are finished, move the component
destination.setLocation(locationX, locationY);
}
/*
* Determine how far the mouse has moved from where dragging started (Assume
* drag direction is down and right for positive drag distance)
*/
private int getDragDistance(int larger, int smaller, int snapSize) {
int halfway = snapSize / 2;
int drag = larger - smaller;
drag += (drag < 0) ? -halfway : halfway;
drag = (drag / snapSize) * snapSize;
return drag;
}
/*
* Get the bounds of the parent of the dragged component.
*/
private Dimension getBoundingSize(Component source) {
if (source instanceof Window) {
GraphicsEnvironment env = GraphicsEnvironment
.getLocalGraphicsEnvironment();
Rectangle bounds = env.getMaximumWindowBounds();
return new Dimension(bounds.width, bounds.height);
} else {
return source.getParent().getSize();
}
}
/**
* Restore the original state of the Component
*/
#Override
public void mouseReleased(MouseEvent e) {
if (!potentialDrag)
return;
source.removeMouseMotionListener(this);
potentialDrag = false;
if (changeCursor)
source.setCursor(originalCursor);
if (destination instanceof JComponent) {
((JComponent) destination).setAutoscrolls(autoscrolls);
}
// Layout the components on the parent container
if (autoLayout) {
if (destination instanceof JComponent) {
((JComponent) destination).revalidate();
} else {
destination.validate();
}
}
}
}
What should I do with this code (or the other GUI's building code) to make me able to drag and drop components with children?
You have an adaptation of a ComponentMover by #camickr. The original and your version work fine with components that have children. Perhaps the problem is elsewhere. Consider posting an MCVE that illustrates the problem.
Here is a simple demo:
import java.awt.Color;
import java.awt.Dimension;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class TestMove extends JPanel{
public TestMove() {
setLayout(null);
JPanel panel = new JPanel();
panel.add(new JLabel("label"));
panel.add(new JButton("button"));
panel.setBorder(BorderFactory.createLineBorder(Color.GREEN));
panel.setBounds(50, 50, 200, 50);
add(panel);
ComponentMover cm = new ComponentMover();
cm.registerComponent(panel);
}
public Dimension getPreferredSize() {
return new Dimension(300, 200);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationByPlatform(true);
TestMove panel = new TestMove();
frame.add(panel);
frame.pack();
frame.setVisible(true);
}
});
}
}
I`ve got a mysterious problem with my custom JTable and a custom TableRenderer.
In 95%-99,99% it works perfectly, but sometimes the renderer just stops doing his job, and leaves a portion of the table (which is inside a JScrollPane) blank.
The problem case looks like that:
In all other cases, and after a slight resize of the window, the Table look like that:
Now both columns has a TextAreaCellRenderer associated to, which works as follows:
public class TextAreaCellRenderer extends JTextArea implements TableCellRenderer {
private final Color evenColor = new Color(252, 248, 202);
public TextAreaCellRenderer() {
super();
setLineWrap(true);
setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
}
#Override
public Component getTableCellRendererComponent(final JTable table, final Object value, final boolean isSelected, final boolean hasFocus, final int row, final int column) {
if (isSelected) {
setForeground(table.getSelectionForeground());
setBackground(table.getSelectionBackground());
} else {
setForeground(table.getForeground());
setBackground(table.getBackground());
setBackground((row % 2 == 0) ? evenColor : getBackground());
}
setWrapStyleWord(true);
setFont(table.getFont());
setText((value == null) ? "" : value.toString());
return this;
}
}
I also have to override the doLayout method of the JTable to be able to calculate the hight of a cell depending on the content. The custom table looks like that:
public class MediaMetaDataTable extends JTable {
#Override
public void doLayout() {
TableColumn col = getColumnModel().getColumn(1);
for (int row = 0; row < getRowCount(); row++) {
Component c = prepareRenderer(col.getCellRenderer(), row, 1);
if (c instanceof JTextArea) {
JTextArea a = (JTextArea) c;
int h = getPreferredHeight(a) + getIntercellSpacing().height;
if (getRowHeight(row) != h) {
setRowHeight(row, h);
}
}
}
super.doLayout();
}
private int getPreferredHeight(final JTextComponent c) {
Insets insets = c.getInsets();
View view = c.getUI().getRootView(c).getView(0);
int preferredHeight = (int) view.getPreferredSpan(View.Y_AXIS);
return preferredHeight + insets.top + insets.bottom;
}
}
The table is instantiated once with the following parameters:
metaTable = new MediaMetaDataTable();
metaTable.setModel(new MediaMetaDataTableModel());
metaTable.setEnabled(false);
metaTable.setShowGrid(false);
metaTable.setTableHeader(null);
metaTable.getColumnModel().getColumn(0).setCellRenderer(new TextAreaCellRenderer());
metaTable.getColumnModel().getColumn(1).setCellRenderer(new TextAreaCellRenderer());
metaTable.setPreferredScrollableViewportSize(new Dimension(-1, -1));
metaTable.setShowHorizontalLines(false);
metaTable.setShowVerticalLines(false);
Each time the data to show changes i update table by replacing the underlying models data:
List<MediaMetaData> metaInformation = mediaSearchHit.getMetaInformation();
if (metaInformation != null) {
((MediaMetaDataTableModel) metaTable.getModel()).replaceMetaInfos(metaInformation);
}
On update the model itself fires a table data changed event:
public class MediaMetaDataTableModel extends AbstractTableModel {
private List<MediaMetaData> metaInfos = new LinkedList<MediaMetaData>();
public static final int COL_INDEX_NAME = 0;
public static final int COL_INDEX_VALUE = 1;
public void replaceMetaInfos(final List<MediaMetaData> metaInfos) {
this.metaInfos = null;
this.metaInfos = metaInfos;
fireTableDataChanged();
}
...
Now does anybody has a idea, what causes the described rendering problem?
Thanks for any advices.
I also have to override the doLayout method of the JTable to be able
to calculate the hight of a cell depending on the content.
To achieve this goal there's no need to override doLayout() method. I think the simplest way to do this is by adding the text area used to render the cell content into a JPanel with BorderLayout and set the row height based on the panel's preferred size. This way the layout manager will do the trick for you and all the cell's content will be visible:
#Override
public Component getTableCellRendererComponent(...) {
...
JPanel contentPane = new JPanel(new BorderLayout());
contentPane.add(this);
table.setRowHeight(row, contentPane.getPreferredSize().height); // sets row's height
return contentPane;
}
As #mKorbel pointed out, there's no need to make the renderer extend from JTextArea: a single variable will work. Keeping this in mind take a look to this implementation based on your work:
class TextAreaRenderer implements TableCellRenderer {
private JTextArea renderer;
private final Color evenColor = new Color(252, 248, 202);
public TextAreaRenderer() {
renderer = new JTextArea();
renderer.setLineWrap(true);
renderer.setWrapStyleWord(true);
renderer.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
if (isSelected) {
renderer.setForeground(table.getSelectionForeground());
renderer.setBackground(table.getSelectionBackground());
} else {
renderer.setForeground(table.getForeground());
renderer.setBackground((row % 2 == 0) ? evenColor : table.getBackground());
}
renderer.setFont(table.getFont());
renderer.setText((value == null) ? "" : value.toString());
JPanel contentPane = new JPanel(new BorderLayout());
contentPane.add(renderer);
table.setRowHeight(row, contentPane.getPreferredSize().height); // sets row's height
return contentPane;
}
}
Screenshot
If I had to guess I would say it could be a concurency problem. Are you doing everything in the GUI-Thread? If yes, it can't be a concurency problem. Otherwhise try to call everything with Thread.InvokeLater() in an inital debug step, if you don't encounter the error anymore after a long time of testing, you know the cause of the problem.
In a second step you would then check exactly where it is necessary to make the calls with invokelater() and where not (because you shouldn't do that all the time, because it leads to very poor performance.
As I said, just a wild guess... It can of youre just be another bug. Are you using Java7? There are millions of Bugs in Swing with java 7 code (just all the code that from oracle came).
I have two problems while searching for a text in a JTable:
1) For example, in JTextField I must initially have a 'Search Text' in transparent manner and if I click on it, the textfield must become blank and we can enter text there. How to achieve this in Java Swing?
2) My search coding is,
private void search8()
{
String target8 = sear8.getText();
for(int row = 0; row < table8.getRowCount(); row++)
for(int col = 0; col < table8.getColumnCount(); col++)
{
String next8 = (String)table8.getValueAt(row, col);
if(next8.equals(target8))
{
showSearchResults(row, col);
return;
}
}
But it is case-sensitive. I want it to be case-insensitive search. Where should I make changes in this? Also, for eg, if there is a text 'abc' and now I need to type the entire word 'abc'. Is there any way such that, if I type 'a' or 'bc' it would take me to that cell?
Kindly guide me. Thanks in advance.
1. Create a hint for your JTextView. See this example http://code.google.com/p/xswingx/
2. Use equalsIgnoreCase( ) for comparison with case-Insensitivity...
////////////////////EDITED PART//////////////////////
3. If you dont want to implement a hint as i mentioned in point 1, then use FocusListener.
Eg:
JTextField textField = new JTextField("A TextField");
textField.addFocusListener(this);
public void focusGained(FocusEvent e) {
textField = "" ;
}
See this for more details:
http://docs.oracle.com/javase/tutorial/uiswing/events/focuslistener.html
I use a custom paint method
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
String label = getLabel();
if (label != null && (getText() == null || getText().length() == 0)) {
Insets insets = getInsets();
int width = getWidth() - (insets.left + insets.right);
int height = getHeight() - (insets.top + insets.bottom);
// This buffer should be created when the label is changed
// or the size of the component is changed...
BufferedImage buffer = ImageUtilities.createCompatibleImage(width, height, Transparency.TRANSLUCENT);
Graphics2D g2d = buffer.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(getForeground());
g2d.setFont(getFont());
FontMetrics fm = g2d.getFontMetrics();
Composite comp = g2d.getComposite();
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.25f));
int textHeight = fm.getHeight();
int x = insets.left;
int y = ((height - textHeight) / 2) + fm.getAscent();
g2d.drawString(label, 0, y);
g2d.dispose();
g.drawImage(buffer, insets.left, insets.top, this);
}
}
I've had some issues with it running on MacOS, hence the use of BufferedImage but it should work fine.
I typically wait until the user has typed in the field before clearing the label, but you could use a focus listener and flag to trigger the process instead
UPDATED with FOCUS LISTENER
public class MyTextField extents JTextField implements FocusListener {
private boolean hasFocus = false;
public void addNotify() {
super.addNotify();
addFocusListener(this);
}
public void removeNotify() {
removeFocusListener(this);
super.removeNotify();
}
public void focusGained(FocusEvent evt) {
hasFocus = true;
}
public void focusLost(FocusEvent evt) {
hasFocus = false;
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
String label = getLabel();
if (!hasFocus && label != null && (getText() == null || getText().length() == 0)) {
// As above...
}
}
}
Or something to that effect
For the case sensitive part, you can use String.compareToIgnoreCase().
You should use next8.equalsIgnoreCase(target8) in place of next8.equals(target8) for your search to be case insensitive.
yeah - I'm aware that developers love to re-invent the wheel :-) Biased me prefers to use my favourite framework SwingX which already has all necessary building blocks:
automatic search support working the same way across all collection components
WYMIWYS (what-you-match-is-what-you-see), that is it uses the actual string representation in the renderering component instead of a dumb toString
search components are pluggable
That's the theory, at least, so on to eating my own dog food: the default findbar - that's the component to use for incremental search, that is searching the target while typing - uses a plain text field instead of the required prompt field. A custom implementation:
/**
* A custom JXFindBar which uses a JXTextField instead of a plain as super.
*/
public static class PromptSearchBar extends JXFindBar {
/**
* Overridden to replace the plain text field in super
* with a JXTextField (which supports prompts).
*/
#Override
protected void initComponents() {
super.initComponents();
searchField = new JXTextField() {
#Override
public Dimension getMaximumSize() {
Dimension superMax = super.getMaximumSize();
superMax.height = getPreferredSize().height;
return superMax;
}
};
searchField.setColumns(getSearchFieldWidth());
((JXTextField) searchField).setPrompt(getUIString(SEARCH_FIELD_LABEL));
}
/**
* Overridden to update the prompt in addition to super
*/
#Override
protected void updateLocaleState(Locale locale) {
super.updateLocaleState(locale);
((JXTextField) searchField).setPrompt(getUIString(SEARCH_FIELD_LABEL, locale));
}
/**
* Overridden to not add the search label.
*/
#Override
protected void build() {
setLayout(new FlowLayout(SwingConstants.LEADING));
add(searchField);
add(findNext);
add(findPrevious);
}
}
Installing in custom code:
SearchFactory factory = new SearchFactory() {
#Override
public JXFindBar createFindBar() {
return new PromptSearchBar();
}
};
SearchFactory.setInstance(factory);
factory.setUseFindBar(true);
That's it - focus a JXTable, JXTree, JXList, ... press ctr-f and type away in the searchfield: the next matching cell will be highlighted.