I am designing a game in Swing. Currently I am designing the maze for this game. The maze is generated by using Depth First Search algorithm. In my main JFrame, I have some JPanel. One JPanel, named mazePanel contains the maze. There are some other JPanel also, which contains the JButton for controlling. Following is the mazePanel code.
import java.awt.Graphics;
import javax.swing.BorderFactory;
import javax.swing.JPanel;
public class MazePanel extends JPanel {
private MazeGenerator mazeGenerator;
private boolean startNewMaze = false;
public MazePanel() {
setBorder(BorderFactory.createTitledBorder("Maze"));
setToolTipText("This is the maze");
}
public void addNewMaze() {
startNewMaze = true;
mazeGenerator = new MazeGenerator();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
if (startNewMaze) {
mazeGenerator.generate(g);
startNewMaze = false;
}
}
}
There is one JButton, which calls the method mazePanel.addNewMaze() and set the Boolean startNewMaze to true. After setting the startNewMaze, maze should be generated. i.e. mazeGenerator.generate(g) is inside if() condition. Method mazeGenerator.generate(g) recursively draw the random maze. That is why I don’t want to run this method not more than once.
Up to this everything is looking fine. But while I am running the main JFrame and clicks on the JButton, maze is not rendered in the mazePanel. Sometimes when I minimize and maximize the JFrame, maze rendered (might be because of repaint() occur). Even if I comment mazeGenerator.generate(g) inside if() condition and put some g.drawString(). The string is not rendered while action performed (i.e.Pressing JButton).
Where is the problem? Please help.
Thank you.
So basically you have a JPanel which contains nothing, you call a method unknown to Swing and expects the paintComponent method is magically called when you change the state of a private field.
You already discovered that minimizing and maximizing again solves your problem due to a repaint. That should be sufficient information to know you have to trigger a repaint yourself when you press that button.
If you would have followed the suggestion from #kleopatra to, and I quote,
change the state and then trigger the revalidation/painting
you would already have solved your problem
Related
I am making a program using the amazing libGDX+scene2d API and I structured it as follows:
I have a single MyGame instance, holding a single PolygonSpriteBatch instance.
There is an abstract MyScreen class, holding a MyStage class (see below)
Then there are lots of different screen classes that inherit from MyScreen, and instantiate each other at will.
(in all cases, removing the "My" gives you the name of the respective library class that it extends)
This model worked fine, until I encountered some problems to perform actions between screens using the Action system. I decided then that it would be a good idea to have a single OmnipresentActor belonging to MyGame that, as the name says, is present in every scene. So I modified MyStage to look more or less like this:
public class MyStage extends Stage {
public MyStage(MyGame g) {
super(new FitViewport(MyGame.WIDTH, MyGame.HEIGHT), g.batch);
addActor(game.omnipresentInvisibleActor);
}
#Override
public void clear() {
unfocusAll();
getRoot().clearActions();
getRoot().clearListeners();
removeActorsButNotListenersNorActions();
}
public void removeActorsButNotListenersNorActions() {
for (Actor a : getActors()) if (a.getClass()!= OmnipresentInvisibleActor.class) a.remove();
}
It followed a painful debugging phase, until I found out the following:
public PresentationScreen(MyGame g) {
// super() call and other irrelevant/already debugged code
System.out.println("PRINT_BEFORE: "+ stage.getActors().toString()); // OmnipresentActor is there
mainMenuScreen = new MainMenuScreen(game);
System.out.println("PRINT_AFTER: "+ stage.getActors().toString()); // OmnipresentActor is not there anymore, but was added to the mainMenuScreen
the "PRINT_BEFORE" statement shows that the stage holds the omnipresentActor. In "PRINT_AFTER" it isn't there anymore, whereas mainMenuScreen is indeed holding it. So my question, now more precise:
does scene2d prevent this to happen, or am I doing something wrong here?
Answers much appreciated! Cheers
An actor can only be a member of one stage: Thanks to #Tenfour04 for confirming that. The explanation is quite clear after doing a little research:
Stage.addActor() looks like this:
(here the github code of Stage.java)
/** Adds an actor to the root of the stage.
* #see Group#addActor(Actor) */
public void addActor (Actor actor) {
root.addActor(actor);
}
whereas root is simply initialized as a group in the Stage constructor: root = new Group();.
And Group.addActor() looks like this:
(here the github code of Group.java)
/** Adds an actor as a child of this group. The actor is first removed from its parent group, if any. */
public void addActor (Actor actor) {
if (actor.parent != null) actor.parent.removeActor(actor, false);
children.add(actor);
actor.setParent(this);
actor.setStage(getStage());
childrenChanged();
}
So in the tree first lines is the answer: when creating the new stage, if the actor to add already has a parent, it is removed from its current parent. So, There are two possible solutions to the problem I enounced:
SOLUTION 1: Override addActor removing the if statement, or any other alteration of the library, which I'm not sure if it would work. I rather think this could be very problematic, for instance it could prevent the stages from disposing correctly
SOLUTION 2: Change the design so you don't need an omnipresent actor, nor changing/reimplementing the libraries. For the moment this is what I've done based on this answer, it isn't very clean but it works so far:
1) In the MyScreen class added the following fields:
private boolean watchingTemp;
private Actor watchActorTemp;
private Action actionTemp;
2) Then added this method:
public void addActionOnStageAfterActorEndsHisActions(Actor actor, Action action) {
watchActorTemp = actor;
actionTemp = action;
watchingTemp = true;
}
3) then in the render method, I added the following:
if (watchingTemp && !watchActorTemp.hasActions()) {
watchingTemp = false;
stage.addAction(actionTemp);
}
4) finally, when wishing to perform an action at a screen transition (and eventually disposing the first one), you can do something like this: I use something similar when clicking on a door between screens
public void movePlayerTHENgotoNewScreen(float xPos, float yPos, whatever else...) {
game.player.walkToAnyPoint(xPos, yPos);
yourFavoriteScreen.addActionOnStageAfterActorEndsHisActions(game.player, gotoNewScreen(wathever else...));
}
Hope it helps!
Tried this,
package {
import flash.display.MovieClip;
import flash.events.*;
public class test extends MovieClip {
public function test() {
addEventListener(Event.ADDED_TO_STAGE, registerBtn);
}
private function registerBtn(e:Event):void {
this.parent["Homebtn"].addEventListener(MouseEvent.CLICK, myButtonClick);
}
private function myButtonClick(e:MouseEvent):void {
trace("CLICKED");
}
}
}
Image
And the same code on frame 1, And there's a MovieClip Button on stage having Instance name "Homebtn".
Imports
import flash.events.*;
Importing all classes from a package that originates in flash has zero impact on compile size because they're already present in the Flash Player runtime environment. It's pure monotony that you're required to explicitly declare these imports, but good practice when dealing with third party packages.
Stage Relationship
Document code (i.e., code in the Flash IDE timelines) have a direct relationship to MainTimeline, whereas class files do not. If you want to add button1.addEventListener(MouseEvent.CLICK, myButtonClick); to your class, you're not going to be able to do so unless you:
A: Pass a pointer to the button/stage/root to the class when instantiating your test class:
var myObj:test = new test(root)
B: Wait to add the event listener until after you've given the test object a parent relationship to the stage from which to traverse to the button:
addChild(test);
inside your class...
public function test() {
// constructor code
addEventListener(Event.ADDED_TO_STAGE, registerBtn)
}
private function registerBtn():void {
this.parent.button1.addEventListener(MouseEvent.CLICK, myButtonClick);
}
Turn on Debugging
To find the cause of your bugs, you need to debug your code. If you're using Flash IDE CS6, then you can enable this by going to your publish settings and enabling "Permit Debugging". This will take your ambiguous error...
null object reference at myDocument/doSomething()
...to a much clearer...
null object reference at myDocument/doSomething() package\myClass.as:20
...which now denotes which line in your code to look for your issue.
Use the Debug Console
Use the debugging compile mode to bring up the Debug Console. This will provide you with an immediate look at the line of code in question, as well as the Call Stack, and the state of all available Variables. No programmer should be without it.
Run by going to the menu "Debug > Debug Movie > Debug", or use the keyboard combo CONTROL+SHIFT+ENTER.
Now that you're armed with the know-how to do this on your own, I'll cover what you'd encounter, and how you'd fix it (since you're new).
First, it's flash.events with an "s". So we'll change that.
Next, compiling it we get the following errors:
So we see on line 7 of our test.as class: you've placed the timeline code into the class.
var myObj:test = new test(root);
addChild(test);
You don't want to instantiate you class from within itself as it'll never get instantiated. Think of your code as a railroad. The train starts with your timeline code, and only runs on the rails set before it. Your class is floating off to the side, ready with all its interesting turns and zigzags, but you have to add it to the rails for it to be actually run. That's instantiation; we're copying that path onto the current track, and the train runs on it.
So, we get rid of lines 6 & 7, and we're left with Access of possibly undefined property Homebtn. Calling this.parent is actually a getter function, and it returns a DisplayObjectContainer. Because AS3 is a strongly datatyped language, the compiler will know that there is no such property "Homebtn" on DisplayObjectContainers (silly compiler). But of course, you know it's there (or at least it will be by the time this code runs). A simple way of getting around that is by making it evaluate the reference at run-time.
this.parent["Homebtn"].addEventListener(MouseEvent.CLICK, myButtonClick);
By encapsulating the button name as a string and within brackets, we've done that.
Now we recompile again, and get the following:
This is because all event listeners receive one argument: an event object. You may not use it, but not having a variable to hold it is a no-no.
private function registerBtn(e:Event):void {
As a final point. All class functions need to be denoted as to what namespace they exist in. myButtonClick needs one, so we'll add it as private since no external (ie., non-class based) functions need access to it.
Here's your revised code:
test.as
package {
import flash.display.MovieClip;
import flash.events.*;
public class test extends MovieClip {
public function test() {
addEventListener(Event.ADDED_TO_STAGE, registerBtn);
}
private function registerBtn(e:Event):void {
this.parent["Homebtn"].addEventListener(MouseEvent.CLICK, myButtonClick);
}
private function myButtonClick(e:MouseEvent):void {
trace("CLICKED");
}
}
}
test.fla (timeline code on frame 1)
import test;
var Homebtn:MovieClip = new MovieClip();
Homebtn.graphics.beginFill(0xFF0000, 1);
Homebtn.graphics.drawRect(0, 0, 150, 25);
Homebtn.graphics.endFill();
addChild(Homebtn);
var testObj:test = new test();
addChild(testObj);
here is my problem. In my document class TowerDefenseGame.as, I defined a variable Turrent1Flag:
package
{
import flash.display.MovieClip;
import flash.events.*;
import flash.display.Sprite;
import flash.display.Shape;
import flash.text.TextField;
import flash.text.TextFormat;
import flash.text.TextFieldType;
public class TowerDefenseGame extends MovieClip
{
public var Turrent1Flag==0;
}
public function TowerDefenseGame()
{
......
}
Now, in another class Turrent1Button.as, I need to create a mouse click event, by which the Turrent1Flag is set to 1:
package
{
import flash.display.MovieClip;
import flash.events.*;
import flash.display.Sprite;
import TowerDefenseGame;
public class TurretButton1 extends MovieClip
{
public var ButtonBase:Sprite=new Sprite();
public var TurretBase:Sprite=new Sprite();
public var Gun:Sprite=new Sprite();
public function TurretButton1()
{
......
this.addEventListener(MouseEvent.CLICK, MouseClick);
}
public function MouseClick(event:MouseEvent):void
{
MovieClip(root).Turret1Flag = 1;
}
Well, this does not work. I am using Adobe flash cs6 and it says the value cannot be accessed. Someone know how to do this?
try this:
private static var _instance:TowerDefenseGame;
public static function get instance():TowerDefenseGame { return _instance; }
public function TowerDefenseGame()
{
_instance = this;
}
public function MouseClick(event:MouseEvent):void
{
TowerDefenseGame.instance.Turret1Flag = 1;
}
So, to start out, AS3 makes it difficult to so what you've been used to doing in AS2 on purpose, to allow for better Object Oriented practices. When you maintain high and tight walls between Classes, it becomes easier to change Class A without having any effect whatsoever on Class B. Class B only knows about the doors and windows that are the "official" ways into the house of Class A, so it doesn't matter if you move the couch. You also make it extremely easy to replace Class A with Class C that has similar doors and windows.
When you reach through the walls by introducing global state, you can't replace Class B with Class C without changing Class A, because Class A has a direct reference to Class B and knows exactly where the couch is.
One way to handle this is through Inversion of Control (IoC). So, for our house, the couch might be supplied from outside and whatever component supplied it might keep a reference to it, so it would be able to access the couch no matter where in the house it went. So, you might choose to create your TurretButton and pass that into whoever owns that, while your TowerDefenseGame keeps a reference to it and listens directly to it, changing its own flag in response to the click.
Looking at your code, you probably don't need to go that far, because I don't see any sign that your TurretButton is actually nested. In that case, you can listen directly to it, whether you're creating it on the stage or creating it through code (which I'm not a fan of). If you're using the stage in the IDE and it exists on the stage, then just create a public variable of type TurretButton (or you could probably use SimpleButton since you no longer have need for a special Class here based on the code you've shown). That instance will be available in the constructor of your TowerDefenseGame. Just add your event listener to it, and then the listener and the variable you want to change are in the same scope. Ergo, the problem you were trying to solve never existed--you were simply looking at the problem from a perspective that overcomplicated things.
If, in fact, your code is nested in a way that's not shown, you can use ActionScript 3's event system, which is fabulous, to handle the issue without introducing direct coupling and without having to create the button through code and push it down to where it's used. One way is to just listen for any mouse click (since that is a bubbling event) and look to see what was clicked. Another solution is to generate a custom event from the button that you can then listen to from the top level to change the flag. That would look something like:
package view.button {
public class TurretButton extends MovieClip {
public function TurretButton() {
super();
mouseChildren = false;
addEventListener(MouseEvent.CLICK, broadcastTurretEvent);
}
protected function broadcastTurretEvent(e:Event):void {
dispatchEvent(new Event('turretClicked', true));//the true parameter makes it bubble
}
}
}
Then your tower Class would look like
package {
public class TowerDefenseGame extends MovieClip {
//the fact that you're calling it Turret1Flag suggests you're going to have more than 1
protected var turretFlags:Array /*of Boolean*/ = [false, false];
//your turret button instances
//doesn't show nesting, this is just to indicate that
//these are named instances so you know how what they're called
//they could be nested at any level
public var turret1:TurretButton;
public var turret2:TurretButton;
//...etc.
public function TowerDefenseGame() {
super();
addEventListener('turretClicked', onTurretClicked);
}
protected function onTurretClicked(e:Event):void {
//you can also just extract the number and do the math
//to get the correct array index
switch(e.target.name) {
case 'turret1':
turretFlags[0] = !turretFlags[0];
break;
case 'turret2':
turretFlags[1] = !turretFlags[1];
break;
}
}
}
}
Note how well this scales. You don't have to have a different Class for each button to change each separate flag. It's also easy to add more buttons without a whole lot of code changes. You could take this solution further and create a custom event that has a property that says which button was clicked, and you could supply the value to use to that for the button through dependency injection, etc.
I also made the assumption in my code that flags should be boolean and that they're turning on with the first click and off with the second click, etc. You could take the same idea and change it to, for example, increment with every click. I suspect you don't actually want to do what your code was showing and just turn it on with the first click and just leave it on forever.
Another note is that if you think you might want to code in AS3 over the longer term, you should probably learn the coding conventions that are used in AS3. One of these is that Class names start with a capital letter, but properties and methods do not.
It's probably not a great habit to get into for everything, but a static variable looks like it would work here.
public class TowerDefenseGame extends MovieClip
{
public static var Turrent1Flag = 0;
}
In Turrent1Button.as:
public function MouseClick(event:MouseEvent):void
{
TowerDefenseGame.Turret1Flag = 1;
}
I have an application with jTabbedPane. There are two tab (JPanel) in jTabbedPane. First tab includes canvas and second one includes simple JLabel. Button draws rectangle into canvas.
Every thing is fine until then. However, when switching tabs, canvas would lose everything. It should be repainted by itself.
Rectangle should exist after changing tabs. Do you have any idea about the problem?
My button code is here:
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
Graphics g = canvas1.getGraphics();
g.drawRect(10, 10, 100, 100);
}
Thanks in advance.
First of all, you shouldn't put AWT components inside Swing components. Use JComponent or JPanel instead of Canvas.
Second, no, it shouldn't repaint itself. When the button is clicked, you should simply store what should be painted in some variable, and the paintComponent() method should be overridden in order to paint what is stored in this variable. This way, every time the component is repainted, it will repaint what has been stored last in this variable.
For example:
public class RectangleComponent extends JComponent {
private boolean shouldPaintRectangle = false;
public void setShouldPaintRectangle(boolean b) {
this.shouldPaintRectangle = b;
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
if (shouldPaintRectangle) {
g.drawRect(10, 10, 100, 100);
}
}
}
In general, you should never ask the Graphics of a component and paint on it. Instead, you should override paintComponent() and paint the component using the Graphics passed as argument.
I'm writing code for a 3D graph, and I want the scene graph to update when the user presses a JButton. I'm trying to use a Behavior class, but I can't find any information on using swing events to wake up the behavior. I would REALLY appreciate any help! Thank you!!
You can use a special behavior object which contains a queue of Runnables. You can then post runnables to the behaviour and wake it up. You will have to sort out proper synchronisation so the behaviour only goes to sleep when there are no more commands in the queue, but it should work.
Make the class into a singleton to be able to run Runnable's inside the BehaviorScheduler, analogous to the SwingUtilities.invokeLater() method.
public class ThreadTransferBehavior extends Behavior {
private final static int POST_ID = 9997;
private final WakeupOnBehaviorPost m_wakeupPost = new WakeupOnBehaviorPost(this, POST_ID);
private final Stack<Runnable> commands;
public synchronized void processStimulus(Enumeration i) {
while(!commands.isEmpty()) commands.pop().run();
wakeupOn(m_wakeupPost);
}
public synchronized void queueCommand(Runnable r) {
commands.push(r);
postId(POST_ID);
}
}