Hello, everybody!
I have been struggling with this problem for many hours, and I have determined that I need outside help. I have a simple swing application that displays a deck of cards. The program is supposd to allow the user to drag the cards around the screen and disallow the gray (empty) slots to be dragged. I was able to handle this without much problem. I am trying to figure out how to get the card being dragged to appear on top of the other cards on the screen. I cannot figure that part out for the life of me! I will post my entire program.
Main:
Deck:Code:package cards; import java.awt.*; import javax.swing.*; public class Main extends JFrame { private Deck deck; private GameScreen game; /** * Constructor */ public Main() { deck = new Deck(); deck.shuffle(); game = new GameScreen(deck); } public static void main(String[] args) { new Main(); } }
GameScreen:Code:package cards; import javax.swing.*; import java.awt.*; public class Deck { private Card[] deck; /** * Constructor */ public Deck() { deck = new Card[52]; fillDeck(); } /** * @return the Card at the given position in teh deck (JPanels) */ public Card getCard(int pos) { return deck[pos]; } /** * Randomizes the order of the cards in the deck */ public void shuffle() { // perform 100 swaps of random cards // TODO: prove performance of this function for (int i=0; i<100; ++i) { int a = (int) (Math.random() * 52); int b = (int) (Math.random() * 52); swap(a, b); } } /** * Fills the deck with 52 cards that are not shuffled */ private void fillDeck() { int tempFace = 0; // Keeps track of which cards are being created int tempSuit = 0; // Fill deck with images from the the filepaths created for (int suit=0; suit<4; ++suit) { for (int face=1; face<14; ++face) { // "cardImages/cards" String filepath = new String("cardImages/cards/"); // append face of card switch (face) { case 1: // "cardsImages/cards/ace" filepath = filepath.concat("ace"); tempFace = 1; break; case 11: filepath = filepath.concat("jack"); tempFace = 11; break; case 12: filepath = filepath.concat("queen"); tempFace = 12; break; case 13: filepath = filepath.concat("king"); tempFace = 13; break; default: filepath = filepath.concat(Integer.toString(face)); tempFace = face; } // append suit of card switch (suit) { case 3: // "cardsImages/cards/aceClubs" filepath = filepath.concat("Clubs"); tempSuit = 1; break; case 2: filepath = filepath.concat("Diamonds"); tempSuit = 2; break; case 1: filepath = filepath.concat("Hearts"); tempSuit = 3; break; default: filepath = filepath.concat("Spades"); tempSuit = 4; } // "cardsImages/cards/aceClubs.gif" filepath = filepath.concat(".gif"); deck[suit*13+face-1] = new Card(filepath, tempSuit, tempFace); } } } /** * Swaps the the two cards at the given * positions in the deck. */ private void swap(int pos1, int pos2) { Card temp = deck[pos1]; deck[pos1] = deck[pos2]; deck[pos2] = temp; } }
Card:Code:package cards; import javax.swing.*; import java.awt.*; public class GameScreen extends JFrame { /** * Constructor */ public GameScreen(Deck deck) { // this GridLayout aligns its components in 4 rows // and 14 columns GridLayout layout = new GridLayout(4, 14); setLayout(layout); placeCards(deck); setTitle("Project1 - Cards Mysterious"); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setMinimumSize(new Dimension(1200, 500)); // set background color to pale green this.getContentPane().setBackground(new Color(51, 255, 102)); pack(); setVisible(true); } /** * Arranges a deck of cards on the JPanel * Places the cards in four rows, and adds a * gray card in front of each row. * * @param deck */ private void placeCards(Deck deck) { String grayCardPath = new String("cardImages/gray.gif"); for (int i=0; i<52; i++) { Card grayCard = new Card(grayCardPath, 0, 0); JPanel panel = new JPanel(); // add one gray card to the front of each row if (i % 13 == 0) { add(grayCard); } else { add(deck.getCard(i)); } } } }
Any help is much appreciated. Thanks!Code:package cards; import java.awt.*; import java.awt.event.*; import javax.swing.*; public class Card extends JPanel { private final ImageIcon card; // the card image to be displayed private final int suit; private final int face; private int deltax = 0; private int deltay = 0; /** * Constructor * * @param filepath the file path where the card image * can be found */ public Card(String filepath, int suit, int face) { card = new ImageIcon(filepath); this.suit = suit; this.face = face; // if a real suit is given 1 - 4, the card is // enabled for user input - otherwise not if (suit < 1) setEnabled(false); else setEnabled(true); // do not show the JPanel's background setOpaque(false); // listen for mouse dragging and mouse pressing addMouseMotionListener(mouseDrag); addMouseListener(mousePress); } public void paintComponent(Graphics g) { super.paintComponent(g); card.paintIcon(this, g, 10, 10); } /** * @return */ public ImageIcon getIcon() { return card; } /** * @return the suit */ public int getSuit() { return suit; } /** * @return the face */ public int getFace() { return face; } // Listens for when the mouse is dragged MouseMotionListener mouseDrag = new MouseMotionAdapter() { @Override public void mouseDragged (MouseEvent e) { // if enabled for user input, move image with pointer if (isEnabled()) { Point p = MouseInfo.getPointerInfo().getLocation(); p.x -= deltax; p.y -= deltay; setLocation(p); } } }; // Listens for when the mouse is pressed MouseListener mousePress = new MouseAdapter() { @Override public void mousePressed (MouseEvent e) { // Records the offset of the mouse and origin of the card. // This prevents the card from immediately jumping to the // position of the mouse. By factoring this in, it if (isEnabled()) { Point p = MouseInfo.getPointerInfo().getLocation(); deltax = p.x - getX(); deltay = p.y - getY(); } } }; }
Hi, hard to solve problem you have there :/
After a while i came up with this. Gonna stop thinking now, clear my mind
Here is what i got so far:
GameScreen:
Card:Code:import javax.swing.*; import java.awt.*; import java.util.Observable; import java.util.Observer; public class GameScreen extends JFrame implements Observer { private int i=0; JLayeredPane pane; Card lastcard; /** * Constructor */ public GameScreen(Deck deck) { pane = new JLayeredPane(); pane.setLayout(new FlowLayout()); placeCards(deck); setTitle("Project1 - Cards Mysterious"); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setMinimumSize(new Dimension(1200, 500)); // set background color to pale green this.getContentPane().setBackground(new Color(51, 255, 102)); getContentPane().add(pane); pack(); setVisible(true); } /** * Arranges a deck of cards on the JPanel * Places the cards in four rows, and adds a * gray card in front of each row. * * @param deck */ private void placeCards(Deck deck) { String grayCardPath = new String("cardImages/gray.gif"); for (int i=0; i<52; i++) { Card grayCard = new Card(grayCardPath, 0, 0); if (i % 13 == 0) { pane.add(grayCard.getLabel()); grayCard.addObserver(this); } else { pane.add(deck.getCard(i).getLabel()); deck.getCard(i).addObserver(this); } } } public void update(Observable o, Object arg) { JLabel label = (JLabel) arg; pane.setComponentZOrder(label,0); } }
Code:import javax.swing.*; import java.util.Observable; import java.awt.event.*; import java.awt.*; public class Card extends Observable { private JLabel label; private final ImageIcon card; // the card image to be displayed private final int suit; private final int face; private int deltax = 0; private int deltay = 0; /** * Constructor * * @param filepath the file path where the card image * can be found */ public Card(String filepath, int suit, int face) { card = new ImageIcon(filepath); label = new JLabel(); label.setIcon(card); this.suit = suit; this.face = face; // if a real suit is given 1 - 4, the card is // enabled for user input - otherwise not if (suit < 1) label.setEnabled(false); else label.setEnabled(true); // do not show the JPanel's backgroun label.setSize(card.getIconWidth(),card.getIconHeight()); label.addMouseListener(mousePress); label.addMouseMotionListener(mouseDrag); } /** * @return */ public ImageIcon getIcon() { return card; } /** * @return the suit */ public int getSuit() { return suit; } /** * @return the face */ public int getFace() { return face; } public JLabel getLabel() { return label; } MouseMotionListener mouseDrag = new MouseMotionAdapter() { @Override public void mouseDragged (MouseEvent e) { // if enabled for user input, move image with pointer if (label.isEnabled()) { Point p = MouseInfo.getPointerInfo().getLocation(); p.x -= deltax; p.y -= deltay; label.setLocation(p); } } }; // Listens for when the mouse is pressed MouseListener mousePress = new MouseAdapter() { @Override public void mousePressed (MouseEvent e) { // Records the offset of the mouse and origin of the card. // This prevents the card from immediately jumping to the // position of the mouse. By factoring this in, it if (label.isEnabled()) { Point p = MouseInfo.getPointerInfo().getLocation(); deltax = p.x - label.getX(); deltay = p.y - label.getY(); setChanged(); notifyObservers(label); } } }; }
As you see I removed the "extends JPanel" from the class Card. I did this because i wanted it to "extends Observerable" and i could easily put the images on a JLabel.
Main things that changed in the Card class is:
in the constructor. I don't think i changed much more in that class. Apart from a getLabel method.Code:card = new ImageIcon(filepath); label = new JLabel(); label.setIcon(card);
+the observable ofcourse. More of that later.
In GameScreen, instead of just putting the cards on the contentpane, i'm now putting them on a JLayeredPane and the layeredpane on the contentpane. I also made Gamescreen "implements Observer"...
For every card i add in "placeCards(..)" i do
So card = observable, gamescreen = observer.Code:deck.getCard(i).addObserver(this);
I did this because i wanted the card to send a message to the gamescreen saying: 'hey i'm being dragged, put me on top.'
So in the mousepressed of Card i doAnd in the gameScreen, i catch this with the "update" methodCode:setChanged(); notifyObservers(label);So here i put the card that has been clicked on as high as possible on the screen.Code:public void update(Observable o, Object arg) { JLabel label = (JLabel) arg; pane.setComponentZOrder(label,0); }
You may have noticed that i changed the GridLayout in a FlowLayout. I know this looks like **** but i've been using the gridlayout and discovered it resizes the stuff that i add inside it. So the label, that normally has the size of the image put onto it. becomes bigger on top, to the right and at the bottom :S So if 'xx' represents the image, the lines around represent the label. It's too big.
You can simply change the current flowlayout in my code to the previous gridlayout and you'll notice that you can click about 10-15 pixel above the image and it will still be dragged around, same with the right side and bottom.Code:-------------- | | xxxxxxxxxxx | xxxxxxxxxxx | xxxxxxxxxxx | | | --------------
I'm not sure if i changed anything else. If you got questions, just ask em.
Neither do i know if this is the best /easiest solution for your problem![]()
you can try to set the draw points of paint() method -which are 10,10- to be variable so that you can change for every card.
so card 1 might have 10,10 and card 2 might have 10,15 so a part of card 1 -5 pixels- will be displayed and then card 2 will be displayed.
"Recursion is just a line of code"
-Karim Hosny-
My flickr
Okay, instead of using all the observer/observable stuff. It may be better to just drop that and give Card an extra parameter, the GameScreen.
and then use gs to give the message 'hey i'm clicked put me on top. This is way more simple and i can't believe i didn't think of that a bit earliers ><Code:public Card(String filepath, int suit, int face, GameScreen gs){ this.gs = gs; ... }
Wow, thank you both for your help! It turns out there is an even SIMPLER solution. I don't know if you will be happy or sad to see how easy it is, but it can actually be done in a single line placed in the event listener in the Card class.
Sorry to have made you think so hard!Code:e.getComponent().getParent().setComponentZOrder(e.getComponent(), 0)
think out of the "Card"
, so nice to see that it has a simple solution
"Recursion is just a line of code"
-Karim Hosny-
My flickr
The funny thing is that when i used the setComponentZOrder on the contentpane (which is the parent of e.getcomponent when you use it. I had really bad fps issues : /
I apologize for my ignorance, but what is "fps?"
There is a slight problem with the code. I am not too worried about fixing it at this point, but it is a curiosity. When the window is resized, all cards are moved back to their original positions. Also, the order of the cards is messed up when I set the ZOrder property, so I had to remember it's original ZOrder and restore it after the card was dropped.
fps = frames per second. It's not really that which was my issue. But when i used the setComponentZOrder on the contentpane and not on the layeredPane, the card that i'm dragging around would be ... blinking really fast. Very annoying. Even worse when i dropped the card on another one and i then click the card below it. the dropped card would become invisible for 75% or so :S
"I had" that means this problem is allready solved right?so I had to remember it's original ZOrder and restore it after the card was dropped
Ya, the problem is solved. I attached a .zip file of the source files and pictures, so you can take a look at it.
There are currently 1 users browsing this thread. (0 members and 1 guests)
Bookmarks