I'm working on a GUI library for Processing, and I have got a class Component. I have a bunch of other classes that extend Component: Label, Button(Which extends Label), Panel and Icon. These classes continue just the "logic" part (position, text, function checking if they are clicked etc...).
The class Panel has inside of it an ArrayListof Component, and you can add to it buttons, labels etc...
To display the components I decided to use a class Theme, which has inside some overloadings of the function display(Component component) (one for Label, one for Button, one for Panel etc...).
Inside display(Panel panel), at some point, I display all the children of the panel, but, instead of treating them (the children) as Button or Label, it treats all of them as Component. How can I solve this ISSUE?
I've already tried to use a method display()inside the classes Component, Button etc... it works partly, but it gives some "coding problems": to override the function display you have to create a new class (Example: GreenBackgroundButton extends Button{}). Also, by putting all the function in an external class, you can control all the grapihcs of you Program with a single class, so you can have the same background for all the components with a single function displayBackground() etc...
instanceof can't be used, because, if I have to use it to cast the children of the panel, the user can't create custom components, because they would be displayed as Component not as TextField (example).
I've tried casting directly display((Button)panel.getChildren.get(0)), but, as it Should, when I use a label, it gives an error (as expected).
I've tried casting like this too: panel.getChildren().get(0).getClass().cast(panel.getChildren().get(0)), and it doesn't work, I don't know why. (I tried all its variants, like creating an external object etc...)
If you need more code, you can ask...
public void display(Panel panel) {
println("panel");
if (panel.isVisible()) {
pushMatrix();
translate(panel.getX(), panel.getY());
displayBackground(panel, #AAAAAA);
for (int i = 0; i < panel.getChildren().size(); i++) {
this.display(panel.getChildren().get(i).getClass().cast(panel.getChildren().get(i))); //THIS IS THE LINE WITH THE ISSUE
//println(panel.getChildren().get(i).getClass().cast(panel.getChildren().get(i)));
}
popMatrix();
}
}
Theme basicTheme;
Button close;
Panel panel;
void settings() {
size(400, 600, P2D);
}
void setup() {
basicTheme = new Theme();
close = new Button();
close.setBounds(10, 10, 100, 25);
close.setText("close");
panel = new Panel();
panel.setBounds(50, 200, 200, 200);
panel.add(close);
}
void draw() {
background(255);
basicTheme.display(panel);
}
I expect to see on the canvas a working button inside the panel, instead of seeing a component. I don't have any particular error I can think of right now.
Using instanceof is almost always a hack, and is probably a symptom of a bad design.
What you've described sounds exactly like an existing Java library called the Abstract Window Toolkit (AWT), so you might consider "borrowing" some of their design patterns.
Component is an abstract class that defines an abstract paint() function. The paint() function takes a Graphics argument. More on that in a second.
Button and Label extend Component and overide the paint() function.
Graphics is a class that contains functions like drawRect() and setBackground(). It encapsulates shared drawing code, similar to what I think your Theme class would do.
If I were you, I would consider taking a similar approach for your library.
I'll try to address some of your other comments:
Example: GreenBackgroundButton extends Button{}
Favor composition over inheritance for cases like this. You should not need to extend Button to set a background. Instead, have a setBackground() function in the Button() class (or in the Component class).
Also, by putting all the function in an external class, you can control all the rapihcs of you Program with a single class, so you can have the same background for all the components with a single function displayBackground() etc.
It sounds like this belongs in your Component class.
Also, taking a step back, I would encourage you to avoid the temptation to over-engineer a solution. You might think you need a complicated design with several levels of inheritance and many different classes, but chances are a simpler design will probably get you almost everything you need. Specifically, I don't see a huge benefit to extracting any code into the Theme class. I would probably put everything directly in my component subclasses like Button and Label.
Related
I'm looking for a way to cleanly organize my UI code in Swing.
Let's say my UI-code is structured in the following way:
class MainWindow extends JFrame {
// SomePanel panel is added here
}
class SomePanel extends JPanel {
// buttons, checkboxes and so on are added here
}
Lets say I'm instantiating a MainWindow-object inside my main method:
MainWindow frame = new MainWindow("I am an App");
What is the best practice for listening to ActionEvents of buttons (which are declared inside SomePanel, which is declared inside MainWindow) from within my main-method?
Thank you very much for your help!
Use a PropertyChangeEvent, seen here and here, to communicate results from one container to another. Other ways to implement the observer pattern are mentioned here.
Addendum: You're suggesting writing custom ActionEvents?
EventListenerList is another way to implement the observer pattern. Such a list is common to every JComponent, and it is appropriate when more than one event type must be managed. JFreeChart is another popular example that uses diverse events to update chart subcomponents when the data model is changed.
When writing a graphical interface, using Java, what's the appropriate way of switching between the different windows of the application, when clicking a button for example? I.E. what are the windows supposed to be, JPanels, JFrames...? And how do all the components 'see' the 'domain controller' (the class that links the graphical package to the application logic package)?
Any guide or reference would be appreciated.
You start your application with your Controller. In the constructor of your controller, you are going to initialize the first GUI you want to open, lets say GUI_A:
private GUI_A gui_a = null;
Controller() {
gui_a = new GUI_A(this);
}
As you might notice, I called the constructor of GUI_A with one parameter: this. this is referencing the instance of the current class, so this is type of Controller. The constructor of GUI_A has to look something like this:
private Controller controller = null;
GUI_A(Controller ctrl) {
controller = ctrl;
}
This is a simple way to get the GUI known to the Controller.
The next thing you would do is displaying GUI_A:
gui_a.setVisible(true);
If you now want to handle button-clicks, you would do it like this:
First, you add the action-performed method to your button. And, as it is best practice in MVC, you don't want to do logic in your view/GUI. So you also create a corresponding method in your Controller for the action-performed, and call it from your GUI:
// Controller
GUI_A_button1_actionPerformed(ActionEvent evt) {
// Add your button logic here
}
// GUI_A
button1_actionPerformed(ActionEvent evt) {
controller.GUI_A_button1_actionPerformed(evt);
}
Usually you don't need to pass the ActionEvent-var to the Controller, as you will not need it often. More often you would read a text out of a TextField and pass it on to your Controller:
// Controller
GUI_A_button1_actionPerformed(String text) {
// Add logic for the text here
}
// GUI_A
button1_actionPerformed(ActionEvent evt) {
controller.GUI_A_button1_actionPerformed(textField1.getText());
}
If you now want to access some fields on your GUI_A from the Controller, be sure not to mark the fields as public in your GUI, but to create public methods which handle how to display the values.
The preferable way is using Actions. You can attach action to each control. When user action happens (e.g. click on button) the appropriate Action is called. Actions can delegate calls deeper into the application logic and call graphical components (JFrams, etc).
suggestion: use tabbed-panel should do this, JPanel is just a Java container, while JFrame should be the outside windows, they are different things. there should be several JPanels on top of One JFrame. your app can have multiple JFrames.
When writing a graphical interface, using Java, what's the appropriate way of switching between the different windows of the application, when clicking a button for example?
Add an ActionListener to the button. In the actionPerformed(ActionEvent) method, do what needs to be done.
I.E. what are the windows supposed to be, JPanels, JFrames...?
I would recommend making the main window a JFrame and using either a JDialog or JOptionPane for most of the other elements. Alternately, multiple GUI elements can be added into a single space in a number of ways - CardLayout, JTabbedPane, JSplitPane, JDesktopPane/JInternalFrame, ..
And how do all the components 'see' the 'domain controller' (the class that links the graphical package to the application logic package)?
One way is to pass a reference to the object between the UIs.
Note: This is for a SWING course I am taking.
I have an assignment to make a simple graphics package (draw circles, squares, etc).
I was thinking of having multiple dialog boxes for entering the shape parameters, i.e:
Point has x,y
Circle has x,y,radius
Rectangle has x,y,width,height
etc.
I was thinking of creating a super dialog class with X,Y and extending it to allow for Width,Height or Radius etc.
For example, the rectangleDialog would invoke the super constructor with the additional parameters required:
public abstract class XYDialog extends JFrame {
public XYDialog(PARAMETERS ... params) {
// build the dialog by iterating through PARAMETERS
}
}
public class RectangleDialog extends XYDialog {
public RectangleDialog() {
super(PARAMETERS.WIDTH, PARAMETERS.HEIGHT);
}
}
then the super class is responsible for building the GUI
Does this seem like a reasonable approach? Does this make sense?
Thanks
Yes, I think it's a good solution. But, as stated before, reconsider the naming of your classes. If you extend a JFrame, call it SomethingFrame. If PARAMETERS is a normal class, it should not be in capitals.
I would also suggest extending JPanel instead of JFrame, and let the one instatiating these classes determine if to put them in a JFrame or a JDialog. A JFrame creates a whole new window, and you normally only have one main window for your application, whereas dialogs and panels are created on the fly.
I want to create a button class and use a ButtonUI to render it instead of overriding the paint component method. I do not want to use this ButtonUI for ALL JButtons. Is that possible? I've only ever seen UIManager.put("ButtonUI","MyCustomButtonUI"), but does this affect ALL JButton rendering? Is it possible to limit the scope of the put operation?
JButton.setUI(ButtonUI) sets the UI for just one JButton. Use that in conjunction with a factory:
public static JButton createStyledButton(String text) {
JButton button = new JButton(text);
button.setUI(STYLE_UI);
return button;
}
EDIT: Or, since you say it's constant for a certain subclass, just call setUI() from the constructor for that subclass.
Another alternative might be to override the method getUIClassID() in your subclass. This will probably allow you to still use the UIManager to choose which style to use, but I haven't tested it.
Well, you can, in your ButtonUI class, check the real component class being given to you and only override default behaviour for our desired subclass, through instanceof or any other mechanism.
But, if you, like me, don't like repeated calls to instanceof use another trick. As your button subclasses JButton, it also subclasses JButton and AbstractButton, allowing you to call AbstractButton#setUI in your constructor or somewhere else, allowing a specific renderer, totally distinct from LnF application.
After some advice on using jpanel - I'm new to java and playing around with the GUI elements.
Bascially what I'm curious about is if I can set up a Jpanel in one class, then somehow add labels etc to the that container, but from another class.
Is this possible ? or do i have to set the entire GUI up in one class, but then I guess I would have the same issue, if I wanted to update those fields I had set up in the main class from another class?
Apologies I don't really have any code that's usefull to demostrate here - I'm just trying to get the idea going, working out if its possible before I go ahead. And I'm not even sure if this is possible. Any advice would be greatly appreciated.
Thanks
As long as you have a reference to the JPanel, you can add whatever GUI-element you want, by calling add(JComponent comp) on the JPanel.
So, you can do something like this:
class Panel extends JPanel{
...
}
class Main{
public Main(JPanel thePanel){
thePanel.add(new JButton("Hello"));
}
}
Was this what you were looking for?
You can also update the fields added to the panel from another class, if you have a public accessor-method set up, in the class. So in your panel class, you have a method:
public JButton getButton(){
return button;
}
Then you can access the button from whatever class with a reference to your panel class, like this:
panel.getButton().setText("Some text");
Note that the button could just as well be public, then you could simply call the method directly: panel.button.setText("Some text"); but this is not considered good code, as it violates some general good OOP practices, not relevant to mention here.