I currently have a simple Java AWT/Swing code that creates a simple GUI that takes multiple String User-input and stores and displays it in the Intellij terminal like so:
import javax.swing.*;
import java.awt.*; // Using AWT container and component classes
import java.awt.event.*; // Using AWT event classes and listener interfaces
import java.util.ArrayList;
import java.util.Scanner;
// An AWT program inherits from the top-level container java.awt.Frame
public class DateTime extends JFrame implements ActionListener {
private Label lblCount, lblsource, lbldate1, lbldate2; // Declare a Label component
private JTextField tfCount, date1, date2; // Declare a TextField component
private Button btnCount; // Declare a Button component
private int count = 0; // Counter's value
static String type = null;
private JCheckBox source1, source2;
boolean a = false;
boolean b= false;
static String source, datedefined1, datedefined2;
ArrayList<String> texts = new ArrayList<String>();
// Constructor to setup GUI components and event handlers
public DateTime () {
setLayout(new FlowLayout());
// "super" Frame, which is a Container, sets its layout to FlowLayout to arrange
// the components from left-to-right, and flow to next row from top-to-bottom.
lblCount = new Label("Enter the type of report you want generated; Hourly/ Daily/ Weekly/ EventComparison:"); // construct the Label component
add(lblCount); // "super" Frame container adds Label component
tfCount = new JTextField("", 20); // construct the TextField component
tfCount.setEditable(true); // set to read-only
// "super" Frame container adds TextField component
tfCount.setBounds(10,50,200,40);
add(tfCount);
tfCount.addActionListener(this);
lblsource = new Label("Now choose the source type:");
add(lblsource);
source1 = new JCheckBox("Drivetest", a);
source1.setBounds(10,100,50,30);
add(source1);
source2 = new JCheckBox("Ookla Dump",b);
add(source2);
source1.addActionListener(this);
source2.addActionListener(this);
lbldate1 = new Label("Please enter the Start DATETIME of the chosen duration(YYYY-MM-DD HH:MM:SS) :");
add(lbldate1);
date1 = new JTextField("", 30); // construct the TextField component
date1.setEditable(true);
add(date1);
date1.addActionListener(this);
lbldate2 = new Label("Please enter the end DATETIME of the chosen duration(YYYY-MM-DD HH:MM:SS): ");
add(lbldate2);
date2 = new JTextField("",30);
date2.setEditable(true);
add(date2);
date2.addActionListener(this);
// set to read-only
// "super" Frame container adds TextField component
// "btnCount" is the source object that fires an ActionEvent when clicked.
// The source add "this" instance as an ActionEvent listener, which provides
// an ActionEvent handler called actionPerformed().
// Clicking "btnCount" invokes actionPerformed().
setTitle("Report Generator"); // "super" Frame sets its title
setSize(800, 700); // "super" Frame sets its initial window size
// For inspecting the Container/Components objects
// System.out.println(this);
// System.out.println(lblCount);
// System.out.println(tfCount);
// System.out.println(btnCount);
setVisible(true); // "super" Frame shows
// System.out.println(this);
// System.out.println(lblCount);
// System.out.println(tfCount);
// System.out.println(btnCount);
}
// The entry main() method
public static void main(String[] args) {
// Invoke the constructor to setup the GUI, by allocating an instance
DateTime app = new DateTime();
// or simply "new AWTCounter();" for an anonymous instance
}
// ActionEvent handler - Called back upon button-click.
#Override
public void actionPerformed(ActionEvent evt) {
Object actionsource = evt.getSource();
if(actionsource instanceof JTextField){
JTextField dateget1 = (JTextField) evt.getSource();
JTextField dateget2 = (JTextField) evt.getSource();
if (dateget1 == date1){
datedefined1 = date1.getText();
System.out.println(datedefined1);}
else if(dateget2 == date2){
datedefined2 = date2.getText();
System.out.println(datedefined2);}
else{
type = tfCount.getText();
System.out.println(type);
}
}
else if(actionsource instanceof JCheckBox){
JCheckBox cb = (JCheckBox) evt.getSource();
if(cb == source1){
source = "Drivetest";
System.out.println(source);
}
else if(cb == source2){
source = "Ookla Data Dump";
System.out.println(source);
}
}
}
}
The thing is, my main program needs to take in and store multiple string variables(ie. type, source, date1 AND date2) before it should execute.
My code for a normal terminal-style running of the program would look like this:
System.out.println("Enter the report type you would like: DailyComparison or HourlyComparison or WeeklyComparison or EventComparison; Type the exact words!");
type = scan.next();
System.out.println("Now enter the type of data you would like analysed: OOKLA or ManualTest: ");
source = scan.next();
if("DailyComparison".equals(type) || "HourlyComparison".equals(type) || "WeeklyComparison".equals(type) ){
Scanner scan2 = new Scanner((System.in));
System.out.println("Now enter the lower bound of the DateTime range(FORMAT YYYY-MM-DD HH:00:00):");
date1 = scan2.nextLine();
System.out.println("Now enter the upper bound of the DateTime range(FORMAT YYYY-MM-DD HH:00:00):");
date2 = scan2.nextLine();
}
Where user-input is taken through the terminal as normal.
The user input is then used to run the rest of the program, calling the methods in other classes i have defined:
Report.report(date1, date2, type, filename, source);// Creates the excel .xlsx file report
MailSender.MailSender(filename, type); // Send a email containing the attached report xlsx file
So my question is: How do i extend this GUI code's functionality so that the user-input string variables can ALL be gathered first then used to run the rest of the program?
EDIT:
Thanks for the advice guys.
I sort of got it to work, but I'm not sure if the structure is sound. What was happening previously was that since each component was handling a different variable and i wanted to store all the variables first before calling the main method classes that would process these variables.
So i created an additional button called "Generate Report" and under the actionlistener condition+action for this button, I placed the class.methods like so. Where basically I key in all my variables in the respective components(checkboxes, button etc) THEN press "generate report"
if (evt.getActionCommand() == "Generate Report") {
if ("DailyComparison".equals(type)) {
filename = "\\Users\\User\\Documents\\Reports\\" + " Daily SpeedTest Telco Comparison Report";
datedefined3 = null;
datedefined4 = null;
datedefined5 = null;
datedefined6 = null;
} else if ("WeeklyComparison".equals(type)) {
filename = "\\Users\\User\\Documents\\Reports\\" + " Weekly Telco Comparison Report";
datedefined3 = null;
datedefined4 = null;
datedefined5 = null;
datedefined6 = null;
} else if ("HourlyComparison".equals(type)) {
filename = "\\Users\\User\\Documents\\Reports\\" + "Hourly Telco Comparison Report";
datedefined3 = null;
datedefined4 = null;
datedefined5 = null;
datedefined6 = null;
}
if("HourlyComparison".equals(type)|"DailyComparison".equals(type)|"WeeklyComparison".equals(type)) {
Report.report(datedefined1, datedefined2, datedefined3, datedefined4, datedefined5, datedefined6, type, filename, source);// Creates the base excel .xlsx file report
LinechartGenerator.chartgen(0, "upload", datedefined1, datedefined2, datedefined3, datedefined4, datedefined5, datedefined6, source, type, filename);
LinechartGenerator.chartgen(0, "download", datedefined1, datedefined2, datedefined3, datedefined4, datedefined5, datedefined6, source, type, filename);
LinechartGenerator.chartgen(0, "latency", datedefined1, datedefined2, datedefined3, datedefined4, datedefined5, datedefined6, source, type, filename);
}
}
Although the code has its limitations where I cannot press generate report first or the program will just throw an error seeing as no variables were stored.
I also faced a roadblock where I'm trying to find the Swing-equivalent of the Flush Scanner function to allow the user to generate multiple reports in the same program instance.
This would invoke some basic principles:
Model-View-Controller - where the "data" is separated from the view and mechanisms used to collect it
Observer pattern - which is used to generate notifications when some state changes so interested parties can take action.
The observer pattern is used extensively in most UI frameworks, which tend to be event driven (something happens, you respond to it), rather then procedural or linear driven.
Typically, you would create a "form" which included the fields which would capture the data you need and some kind of "button", which when pressed, would initiate the next step - validating the data, building the model and generating the notification that the form has been completed.
The observer to this would then take the model and process it as required.
These are just some of the basic concepts used in UI development. Have a look at:
Creating a GUI With JFC/Swing
How to use buttons
How to write Action Listeners
for some more details
I noticed that you set this as the action listener for all the check boxes and textfields. What I think you really want, is to only process the user input when the user clicks the button, right?
Remove lines like these:
tfCount.addActionListener(this);
so that this only handles the click of the button.
Now, you can access all the inputs in the actionPerformed method:
dateDefined1 = date1.getText();
dateDefined2 = date2.getText();
type = tfCount.getText();
if (source1.isChecked()) {
source = "Drivertest";
} else {
source = "Ookla Data Dump"
}
// now you can use dateDefined1, dateDefined2, type and source!
Also, why use check boxes but not radio buttons? For this kind of "select one from the below option" usage, radio buttons are better suited.
You may break down this in two different jars:
Read the input and write that in a temp file.
Once the input has been read and written to the file, invoke your GUI jar application this can read contents from the temp file.
You may refer to this question on how to invoke jar from java application.
I've been having a problem with my program that has been driving me crazy and I cannot understand why this is happening. I have a GUI that when the "Add" button is pressed, a new system listing appears (Which is a class called SystemPanel that extends JPanel and cotains system details, which is created and then put into the Frame's main panel.)
To put it shortly, when I try to add a new SystemPanel, it does not appear for whatever reason. I have code using JSch that connects to the system and verifies whether its processes are online or not, but the line of code that does this is after the creation of the SystemPanel. It is only after the code for testing the processes of the system are executed that the SystemPanel becomes visible, and I can't understand why this is the case. Here is the code for adding a new SystemPanel:
public void actionPerformed(ActionEvent e) {
//If the "Add" button is pressed
if (e.getActionCommand() == "Add") {
PopupWindow popup = new PopupWindow(this);
popup.setVisible(true);
String[] results = popup.getResults();
if (results[0] != null && results[1] != null && results[2] != null && results[3] != null && results[4] != null) {
SystemPanel newSystem = new SystemPanel(this, results[0], results[1], results[2], results[3], results[4]);
systemsPanel.add(newSystem);
revalidate();
systemsList.add(newSystem);
System.out.println("Did the stuff");
boolean[] status = SystemChecker.checkOnline(results[0], results[1], results[2], results[3]);
}
}
}
The PopupWindow is a custom JDialog that allows the user to enter the required information which is returned in a String array and is used to create a new SystemPanel. The checkOnline function grabs the user's inputs and uses them to connect to the system and determine whether the processes are working or not, and returns the results into a boolean array, true for working, false for not.
What's even weirder is that I have another part of my program that reads from an .ini file to obtain existing systems and then creates SystemPanels based on the data that it reads. Through this method, the SystemPanels are added the way I want and work perfectly for some reason, even though the code for adding the panels is hardly any different. Code:
for (int i = 0; i < systems.size(); i++) {
SystemPanel newSystem = new SystemPanel(this, systems.get(i)[0], systems.get(i)[1], systems.get(i)[2], systems.get(i)[3], systems.get(i)[4]);
systemsPanel.add(newSystem);
revalidate();
systemsList.add(newSystem);
}
for (int i = 0; i < lineNum; i++) {
boolean[] status = SystemChecker.checkOnline(systems.get(i)[0], systems.get(i)[1], systems.get(i)[2], systems.get(i)[3]);
systemsList.get(i).updateIcons(status);
}
This code grabs the details from the file and then makes the SystemPanels based on those details. Here, all of the SystemPanels are added and show up before the connection is tested, which is what I want to happen when I add one normally.
Why is it that the SystemPanel doesn't appear until the connection is tested, even though the code for displaying the SystemPanel is executed before the connection test? Any help would be greatly appreciated, thanks.
Try it of the current event queue handling, on which actionPerformed is done.
public void actionPerformed(ActionEvent e) {
EventQueue.invokeLater(() -> { ... your code here ... });
}
Also you cannot add the same component to two parents, every component object has a single parent (container).
(Java 8 notation)
I'm having difficulty changing unselected options in a JList table to set them to setEnable(false). The method that is receiving the values is an ActionListener button method that, once pressed, receives the selected values from the JList. Here is the method and the buildEnemySelectionPanel() method is creating the JList with the appropriate JPanel for later placement:
private String[] enemies = {"Goblin", "Skeleton"};
private void buildEnemySelectionPanel()
{
enemyPanel = new JPanel();
enemyListPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
enemyListPanel.setPreferredSize(new Dimension(180, 85));
enemyListPanel.setBackground(Color.WHITE);
enemyPanel.setLayout(new BoxLayout(enemyPanel, BoxLayout.Y_AXIS));
enemyList = new JList(enemies);
enemyList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
enemyList.addListSelectionListener(new EnemyListListener());
enemyListPanel.add(enemyList);
enemyPanel.add(enemyListPanel);
}
public void actionPerformed(ActionEvent e)
{
if (e.getActionCommand().equals("Select Enemy"))
{
indexEnemy = enemyList.getSelectedIndex();
indexEnemyWeapon = weaponList.getSelectedIndex();
/*
here is where I'm having problems
*/
}
}
So far I've tried to take all of the items from the JList and, matching them with the references from the original String[] list items that I sent to the JList, parsed the indexes and if they didn't match set to false. Unfortunately as you are all probably well aware, compilation errors came up as result due to the fact that the JList is not actually a list. Here is a sample of my for loop that I tried to use in my method above:
for(int x = 1; x < enemyList.length(); x++)
{
if (!(enemies[x] == indexEnemy))
{
enemyList[x].setEnable(false);
}
}
I've read the http://docs.oracle.com/javase/8/docs/api/ , (tried to link 'setEnable') among some examples but don't seem to be making the connection.
Ideally, what I wish to happen is that when the ActionEvent of my button is triggered, all non-selected options in my JList will be disabled. I understand that the end-user will still be able to change his/her mind and make a different selection. But I'd like to still receive some help on how I can set the non-selected items in my JList to false if they are not the indexEnemy from my method above.
I need to be able to tell if there are any modal dialogs open in my application. Is there any way to do this?
Solution
Alright, so based on the accepted answer, this is what I came up with do accomplish what I needed:
Window[] wins : myMainFrame.getOwnedWindows();
for(Window w : wins) {
if(w instanceof JDialog) {
JDialog jd = (JDialog)w;
if(jd.isModal() && jd.isVisible()) {
// back out of current operation (return null in my case)
return null;
}
}
Perhaps calling Window's getOwnedWindows() is what you're looking for, and all class that derive from Window will have this method, including JFrame and JDialog.
But I agree that more context would help!
I have several components, all inheriting JCheckBox without overwriting anything from it, stored in a vector, which is then traversed and each of the components is added to a dialog
CreateLists(); // initialises the checkbox vector
for(int i = 0; i < checkBoxes.size() ; i++){
myPanel.add(checkBoxes.elementAt(i));
}
Some of these checkboxes are already selected.
My problem now is: When I open the dialog and select or unselect any checkbox, the value of the checkboxes in the vector doesn't change. Selected stays selected and unselected stays unselected.
I also tryed to get the new values by using JPanels getCompoents(), but the values of these are wrong, too.
An ItemListener in the checkbox inheritors confirmes that the changes do happen, but whenever I try to get the new values, there just the same as those with which the checkboxes were initialised.
Here is a console output I used to keep track of the changes:
create lists
print values:
checkBox1 = true
checkBox2 = true
checkBox3 = false
checkBox2 clicked new value = false
checkBox3 clicked new value = true
print values:
checkBox1 = true
checkBox2 = true
checkBox3 = false
Here is some more code and information:
CreateList() compares a watchlist with the watchable things, creates the checkboxes accordingly (true = watched etc) and adds them to the new initalised vector.
To read the values i use this:
Component[] components = pnlGesamt.getComponents();
for(int i = 0; i < components.length; i++){
if(components[i] instanceof WLElementCheckBox){
WLElementCheckBox cb = (WLElementCheckBox) components[i];
System.out.println(cb.WlElement().Name() + " = " + cb.isSelected());
}
}
The JCheckBox inheritor:
private WatchListElement wlElement;
public WLElementCheckBox (WatchListElement wl, boolean selected)
{
super();
WlElement(wl);
setSelected(selected);
setText(wlElement.Name());
addItemListener(new MIL());
}
public WatchListElement WlElement ()
{
return wlElement;
}
public void WlElement (WatchListElement wlElement)
{
this.wlElement = wlElement;
}
public class MIL implements ItemListener{
public void itemStateChanged(ItemEvent arg0) {
System.out.println("Ckb " + wlElement.Name() +" geklickt Item neu = " + isSelected());
}
}
There is a small possibility that the changes to your checkboxes are not visible because you're querying them from another thread (such as main) instead of the Event Dispatch Thread (EDT). Swing is not thread-safe. When you check the checkboxes, the values are set on the EDT, but other threads might have the old state saved in memory and don't check to see if it's been updated. So, the first thing you need to do is ensure that when you're checking the states of your checkboxes, you're doing it from the EDT, using a call to SwingUtilities.invokeAndWait() or SwingUtilities.invokeLater().
For more information on why this happens, search for Thread Safety in Java.
(Also, don't be tempted to use synchronization to solve the problem or you might end up with a deadlock in your GUI).
One final pro tip: using new Java for-loop syntax makes code easier to read and write, as well as more efficient in some cases. Your code...
for(int i = 0; i < checkBoxes.size() ; i++){
myPanel.add(checkBoxes.elementAt(i));
}
... can turn into...
for(JCheckBox c : checkBoxes) {
myPanel.add(c);
}
This works on arrays and anything that is Iterable (which includes Collection).