JFrame show labels in a scroll panel - java

I'm making a frame who needs to show labels in a Scroll Panel, but after I add the labels the scroll don't work.
JScrollPane scrollPane = new JScrollPane();
scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
scrollPane.setBounds(1, 1, 210, 259);
panel.add(scrollPane);
JPanel roomList = new JPanel();
scrollPane.setViewportView(roomList);
roomList.setLayout(null);
int x=0;
for(String l : list) {
JLabel c = new JLabel(l+" "+x);
c.setBounds(new Rectangle(1, 1+x*11, 191, 14));
roomList.add(c);
x++;
}
I'm sure the list has more than 22.
I don't know how to google it!

Your basic problem is, you don't understand how the layout management API works, or how to replace it's functionality when you choose to discard it.
You problem starts here:
roomList.setLayout(null);
There's a lot of work going on in the background which provides a great deal of information to various parts of the API, while on the surface, the layout management API is not complex, the role it plays is.
The JScrollPane will use the component's preferredSize to determine when it should display the scrollbars. Since you've done away with this automated calculation, the JScrollPane has nothing to go on
For more information, have a look at Laying Out Components Within a Container
As a simple example...
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Rectangle;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.Scrollable;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JScrollPane scrollPane = new JScrollPane(new TestPane());
frame.add(scrollPane);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel implements Scrollable {
public TestPane() {
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
for (int index = 0; index < 100; index++) {
add(new JLabel("Row " + index), gbc);
gbc.gridy++;
}
}
#Override
public Dimension getPreferredScrollableViewportSize() {
return new Dimension(100, 50);
}
#Override
public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
return 32;
}
#Override
public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
return 32;
}
#Override
public boolean getScrollableTracksViewportWidth() {
return getPreferredSize().width <= getWidth();
}
#Override
public boolean getScrollableTracksViewportHeight() {
return false;
}
}
}
This example implements the Scrollable interface, this is not always required, but is used, for this example, to provide a hint to the JScrollPane about the preferred size of the viewable area it should use, otherwise it will attempt to use the component's preferredSize.
But, as has already been suggested, there are other, simpler and more optimised solutions available to you.
If your information is simple enough, you can use a JList to list a number of values in a vertical manner, see How to use Lists for more details.
If you information is in a more complex structure, you could use a JTable, which provides a row and column style structure. See How to use tables for more information

Have you tried using jLists instead of JScrollPanes ?
They're very easily implemented, look great and work like a charm.
DefaultListModel model = new DefaultListModel();
for(String l : list) {
model.addElement(l);
}
yourList.setModel(model);
Where list is the list with the room data and yourList is the jList.

Related

How can I align all my swing components to the top left of JPanel/JFrame

I have created a simple program that has 1 JLabel that says Would you like to continue and 2 JButtons one that says yes and one that says no.
I'm using GridBaGLayout to organize the JPanel / JFrame. My program compiles and runs just fine but GridBagLayout Centers all my components in the center of the JFrame. Since I'm new to swing can someone show me how its may be possible to align all my components to the top left of the JFrame?
It might not be a real answer, but anyway: stop using GridBagLayout. It has so many pitfalls and is so brittle to use, so please don't use it.
Use a better alternative such as MigLayout, and learn that. Don't bother learning any of Java's default Layout Managers, except if you really, really need to.
An example using MigLayout:
JPanel panel = new JPanel(new MigLayout("","",""));
panel.add(myJButton1, "wrap");
panel.add(myJButton2, "wrap");
panel.add(myJButton3, "wrap");
panel.add(myJButton4, "wrap");
panel.add(myJButton5, "wrap");
panel.add(myJButton6, "wrap");
There's a couple of ways you could do it, you could add all the components to another container first and then layout that container in your container, but the basic principle would remain...
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setLayout(new GridBagLayout());
GridBagConstraints gbc= new GridBagConstraints();
gbc.weightx = 1;
gbc.anchor = GridBagConstraints.WEST;
gbc.gridwidth = GridBagConstraints.REMAINDER;
for (int index = 0; index < 10; index++) {
add(new JButton("Test"), gbc);
}
gbc.weighty = 1;
add(new JLabel(), gbc);
}
}
}
Basically, this adds a "hidden" component to the last row, which wants to use up the remaining space of the container, forcing the components to the top/left corner of the container

How does the sizing of a JFrame actually work?

I have no purpose for what I'm really doing, just trying things out with Java Swing. What I have right now are these three variables:
int[] gridIterations = {10,10}; //how many JButtons are made each row/column
int[] frameSize = {600,600}; //size of the JFrame
int[] gridSize = {60,60}; //by gridSize, I mean size of the JButtons
I also have a nested for loop which uses these variables to create a grid of JButtons. I would expect the grids to perfectly fit the JFrame, however this is the result:
After some testing I realized that the frame will actually only fit all the JButtons if the size is (615, 631) But I'm wondering, why does it fit only with these parameters, and why, of all numbers, would it be those? To my understanding a simply calculation of 60 * 10 should equal 600 and successfully have all buttons fit into the JFrame, but I am most likely overlooking something. What could that be? Thanks.
A lot comes down to the requirements of the content and the layout manager. Rather then looking "how big" you'd like the frame to be, focus on the amount of space the content needs. The frame will then "pack" around this.
This means that the frame will "content size + frame border size" big.
JFrame#pack takes into consideration the content's preferred size and adds in the frames border insets automatically.
So, the only thing you need to is call JFrame#pack AFTER you finished adding the content to it
So, based on your code:
public class Testing {
public static void main(String[] args) {
JFrame frame = new JFrame("hi");
for (int i = 0; i < 10; i++) {
JButton a = new JButton();
a.setSize(20,20);
a.setLocation(20*i, 0);
frame.getContentPane().add(a);
}
frame.setLayout(null);
frame.pack();
frame.setVisible(true);
}
}
You are using a null layout. JFrame#pack will use the information provided by the layout manager to determine the "preferred" size of the overall content.
Avoid using null layouts, pixel perfect layouts are an illusion within modern ui design. There are too many factors which affect the individual size of components, none of which you can control. Swing was designed to work with layout managers at the core, discarding these will lead to no end of issues and problems that you will spend more and more time trying to rectify.
Rather the focusing on the absolute, which would be variable between OS's (and even different PC's with the same OS), focus on the user experience.
As has, already, been demonstrated, you can easily get this to work using a GridLayout
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setLayout(new GridLayout(10, 10, 0, 0));
for (int index = 0; index < 10 * 10; index++) {
JButton btn = new JButton(String.valueOf(index)) {
#Override
public Dimension getPreferredSize() {
return new Dimension(50, 50);
}
};
add(btn);
}
}
}
}
If, you need a more complex management (ie depth by breadth), you could use a GridBagLayout instead
Take a look at Laying Out Components Within a Container, How to Use GridLayout and How to Use GridBagLayout for more details ;)
The size of a JFrame includes its insets. This basically means the title bar and borders.
GridLayout will do this perfectly for you with much less effort involved.
class GridButtons implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new GridButtons(4, 5));
}
final int rows;
final int cols;
GridButtons(int rows, int cols) {
this.rows = rows;
this.cols = cols;
}
#Override
public void run() {
JFrame frame = new JFrame();
JPanel grid = new JPanel(new GridLayout(rows, cols));
for (int i = 0; i < (rows * cols); ++i) {
grid.add(new JButton() {
#Override
public Dimension getPreferredSize() {
return new Dimension(60, 60);
}
});
}
frame.setContentPane(grid);
frame.pack();
frame.setResizable(false);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}

Intercepting drag and drop events in a compound component?

Say I have a JPanel containing some JTextFields. I would like to perform the same drop action for this JPanel and its children. In other words, I would like the drop action onto the children to be treated the same way as a drop action onto the JPanel.
Is there any other way other than setting the same DropTargetListener for the JPanel and its children?
I know that if I set the TransferHandler of those JTextFields to null, the JPanel will receive the drag and drop event. However, this will destroy the copy and paste functionality of the textfield.
I know that I can intercept mouse events with JLayer. Is there something like this for drag events?
In the end, I added listeners separately to the child components. Because I needed the drop location relative to the parent as well, I used SwingUtilities.convertToPoint() separately on the child components. Which means a lot of different listeners used -- more memory usage. But seems to be the best way for now.
If you want to be able to drop items on a panel but you want any components on the panel to be ignored for dropping purposes you can deactivate the drop target on each of the components added to the panel. You will still be able to cut and paste within them, and there is even a way to initiate a drag from them, but you won't be able to drop anything on them - the drop event goes straight through them to the drop target associated with the panel.
To do this, simply call
component.getDropTarget().setActive(false);
for each component on the panel.
I found this useful when building a calendar panel where I wanted to be able to drag appointments around but drop them on the panel even if it was (partially or completely) covered in other appointments.
Not sure if this is what you had in mind, but, I basically added the same DropTargetListener to all of my components, which meant that it didn't matter where I dragged/dropped the incoming request, all the components triggered the same events...
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class TestDragNDrop100 {
public static void main(String[] args) {
new TestDragNDrop100();
}
public TestDragNDrop100() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel implements DropTargetListener {
public TestPane() {
DropTarget dt = new DropTarget(this, DnDConstants.ACTION_COPY_OR_MOVE, this, true);
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.fill = GridBagConstraints.HORIZONTAL;
for (int y = 0; y < 4; y++) {
gbc.gridy = y;
for (int x = 0; x < 4; x++) {
gbc.gridx = x;
JTextField field = new JTextField(10);
DropTarget child = new DropTarget(field, DnDConstants.ACTION_COPY_OR_MOVE, this, true);
add(field, gbc);
}
}
}
#Override
public void dragEnter(DropTargetDragEvent dtde) {
System.out.println("DragEnter - " + dtde.getDropTargetContext().getComponent());
}
#Override
public void dragOver(DropTargetDragEvent dtde) {
System.out.println("DragOver - " + dtde.getDropTargetContext().getComponent());
}
#Override
public void dropActionChanged(DropTargetDragEvent dtde) {
System.out.println("dropActionChanged" + dtde.getDropTargetContext().getComponent());
}
#Override
public void dragExit(DropTargetEvent dte) {
System.out.println("dragExit" + dte.getDropTargetContext().getComponent());
}
#Override
public void drop(DropTargetDropEvent dtde) {
System.out.println("drop" + dtde.getDropTargetContext().getComponent());
}
}
}
I should also note. I tested the fields cut/copy/paste functionality and had no issues.
Implement a custom TransferHandler on the container which delegates to its children as appropriate, something along the lines of:
for (int i = 0; i < 5; i++) {
parent.add(new JTextField("item" + i, 20));
};
TransferHandler handler = new TransferHandler() {
#Override
public boolean canImport(TransferSupport support) {
TransferHandler childHandler = getTargetHandler();
return childHandler.canImport(
getTargetSupport(support));
}
protected TransferSupport getTargetSupport(TransferSupport support) {
return new TransferSupport(getTarget(), support.getTransferable());
}
protected TransferHandler getTargetHandler() {
return getTarget().getTransferHandler();
}
protected JComponent getTarget() {
return (JComponent) parent.getComponent(0);
}
#Override
public boolean importData(TransferSupport support) {
return getTargetHandler().importData(getTargetSupport(support));
}
};
parent.setTransferHandler(handler);

GridBagLayout does not distribute sizes between components correctly

I'm having what seems to me should be a really simple issue. In my opinion it's just a massive oversight of the people who made GridBagLayout...
Anyway. I'm using GridBagLayout to display a 27x13 grid of tiles for the game I'm making. I used this layout because of its ability to resize the components and because of how easy it is to configure, but there's just one small problem. When it's resizing the tiles, if the width is not a multiple of 27 or if the height is not a multiple of 13, there will be white space put around the borders.
To show what I mean:
Here is what it looks like when I resize the frame so that the JPanel within is sized 864x416, perfect multiples of 27 and 13.
Here is what it looks like when I resize the frame so that the JPanel within is sized 863x415, just barely not multiples of 27 or 13.
It simply is not distributing the extra pixels among the tiles. I don't know why. When I fiddle with the minimum/maximum/preferred size using their respective methods or overriding them, or even use GridBagLayout's ipadx and ipady constraints, I can remove the white space - but all it does is just squish the outermost tiles to fit the rest. You can see this in the example code below.
Here's an SSCCE:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.util.Random;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Game extends JPanel {
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
start();
}
});
}
static void start() {
JFrame frame = new JFrame("Game");
JPanel newFrame = new MainGameScreen();
frame.getContentPane().add(newFrame);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
class MainGameScreen extends JPanel {
public MainGameScreen() {
setPreferredSize(new Dimension(864, 551));
setLayout(new GridBagLayout());
setBackground(Color.green);
GridBagConstraints gbc = new GridBagConstraints();
gbc.fill = GridBagConstraints.BOTH;
gbc.weightx = 1;
gbc.weighty = 1;
gbc.ipadx = 0; //Change to 64 to see undesired effects
gbc.ipady = 0; //^
for (int i=0;i<13;i++) {
for (int j=0;j<27;j++) {
gbc.gridx = j;
gbc.gridy = i;
add(new ImagePanel(), gbc);
}
}
}
}
class ImagePanel extends JComponent {
private int r,g,b;
public ImagePanel() {
Random generator = new Random();
r = generator.nextInt(100)+1;
g = generator.nextInt(100)+1;
b = generator.nextInt(100)+1;
}
#Override
public void paintComponent(Graphics gr) {
super.paintComponent(gr);
gr.setColor(new Color(r,g,b));
gr.fillRect(0, 0, getWidth(), getHeight());
}
}
My question is, how can I make the layout constantly look like the first image? Do I need a different layout? I'm very lost here.
I would use a GridLayout for the panel with square tiles, and nest that inside another layout (either GridBagLayout, BorderLayout, BoxLayout, or some combination of layouts) for the rest of the window.
That said, both GridLayout and GridBagLayout will distribute pixels evenly in this case. Since you can only use whole pixels, you'll be left with a remainder after dividing up the the space, and that will be used as padding. This is largely unavoidable, although there are a few potential workarounds:
enforce certain window sizes
draw a larger panel than you need, but use a viewport so the extra tiles are truncated in the view
add other elements bordering the tiled area to effectively hide the fact that there's extra padding
If you're okay with the first option, you can make a slight modification to snap the window to a compatible size, such that there is no remainder (note: I also changed a couple of your hard-coded ints to constants):
package com.example.game;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.util.Random;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Game extends JPanel {
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
start();
}
});
}
static void start() {
final JFrame frame = new JFrame("Game");
JPanel newFrame = new MainGameScreen();
frame.addComponentListener(new ComponentAdapter() {
#Override
public void componentResized(ComponentEvent e) {
int h = frame.getContentPane().getHeight() % MainGameScreen.ROWS;
int w = frame.getContentPane().getWidth() % MainGameScreen.COLS;
// Subtract the remainder pixels from the size.
frame.setSize(frame.getWidth() - w, frame.getHeight() - h);
}
});
frame.getContentPane().add(newFrame);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
class MainGameScreen extends JPanel {
static final int ROWS = 13;
static final int COLS = 27;
public MainGameScreen() {
setPreferredSize(new Dimension(864, 551));
setLayout(new GridBagLayout());
setBackground(Color.green);
GridBagConstraints gbc = new GridBagConstraints();
gbc.fill = GridBagConstraints.BOTH;
gbc.weightx = 1;
gbc.weighty = 1;
gbc.ipadx = 0; //Change to 64 to see undesired effects
gbc.ipady = 0; //^
for (int i=0;i<ROWS;i++) {
for (int j=0;j<COLS;j++) {
gbc.gridx = j;
gbc.gridy = i;
add(new ImagePanel(), gbc);
}
}
}
}
class ImagePanel extends JComponent {
private int r,g,b;
public ImagePanel() {
Random generator = new Random();
r = generator.nextInt(100)+1;
g = generator.nextInt(100)+1;
b = generator.nextInt(100)+1;
}
#Override
public void paintComponent(Graphics gr) {
super.paintComponent(gr);
gr.setColor(new Color(r,g,b));
gr.fillRect(0, 0, getWidth(), getHeight());
}
}

sticking components onto a JScrollPane

I have a program that adds bunch of components to a JPanel (in JScrollbar). However, since it adds so many components, most of them don't fit into the visible area (Viewport).
When everything loads and I start to scroll down, I notice that components, as they get into the Viewport area, are aligning and setting their positions. That causes my JScrollPane to be higher than necessary. That makes it "snap" when I get to the end (components abruptly move up (align properly), and so does the viewport).
I tried calling repaint() and validate(), but with no effect whatsoever. What am I doing wrong?
I would suggest posting an SSCCE in order to exactly replicate your specific problem.
I did a short example that may lead you in the right direction.
Basically will just add 225 JButtons to JPanel with GridLayout which in turn is added to JScrollPane.
import java.awt.Dimension;
import java.awt.GridLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class JScrollPaneOfComponents {
/**
* Default constructor for ScrollBarOfComponents.class
*/
public JScrollPaneOfComponents() {
initComponents();
}
/**
* Initialize GUI and components (including ActionListeners etc)
*/
private void initComponents() {
JFrame jFrame = new JFrame();
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel panel = new JPanel(new GridLayout(15, 15));
//create 225 JButtons and add them to JPanel;
for (int i = 0; i < (15*15); i++) {
panel.add(new JButton(String.valueOf((i + 1))) {
//make buttons bigger for demonstartion purposes
#Override
public Dimension getPreferredSize() {
return new Dimension(100, 100);
}
});
}
JScrollPane scrollpane = new JScrollPane(panel) {
//size the JScrollPane purposelfully smaller than all components
#Override
public Dimension getPreferredSize() {
return new Dimension(300, 300);
}
};
//add scrollpane to frame
jFrame.add(scrollpane);
//pack frame (size JFrame to match preferred sizes of added components and set visible
jFrame.pack();
jFrame.setVisible(true);
}
public static void main(String[] args) {
/**
* Create GUI and components on Event-Dispatch-Thread
*/
javax.swing.SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
try {
//set nimbus look and feel
for (UIManager.LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
if ("Nimbus".equals(info.getName())) {
UIManager.setLookAndFeel(info.getClassName());
break;
}
}
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException e) {
e.printStackTrace();
}
//create new instance of GUI
JScrollPaneOfComponents test = new JScrollPaneOfComponents();
}
});
}
}

Categories

Resources