Client-Server-Applikation mit GUI - Bugs ausmerzen

Hallo

Hoffe, das ist das richtige Forum für mein Problem.

Nachfolgend der Programmcode einer kleinen Server-Client-Applikation (Verwaltung von Pendenzen). Die App funktioniert im Wesentlichen ganz gut, allerdings gibt es ein paar Bugs, welche ich, auch nach mehrmaligem Durchsehen des Codes in Eclipse, noch nicht ausmerzen konnte. Hoffe, dass "mehr Augen" auch mehr sehen.

Zur App:

1. Sever starten (Server-Main-Methode)
2. Client starten (Client-Main-Methode, dies beliebig oft)

Mit einem Klick auf den Button "Pendenz hinzufügen" wird am Tabellenende eine neue leere Zeile (nur ID wird automatisch vergeben) hinzugefügt. In die Zellen "Datum" und "Beschreibung" (beide vom Datentyp String) können irgendein Datum und eine beliebige Beschreibung eingegeben werden. Die (neuen) Zeilen, in welchen die Zellen "Datum" und / oder "Beschreibung" mit irgendwelchem Inhalt beschrieben wurden, müssen, BEVOR man eine weitere Zeile hinzufügt, zuerst mittels Klick auf den Button "Pendenz speichern" gespeichert werden, sonst verschwinden die neuen Eingaben wieder. Dabei muss die entsprechende (neu befüllte) Zeile markiert sein. Man kann immer nur eine neue Zeile (mit Inhalt gefüllt) auf einmal speichern, nicht mehrere (Lösungsvorschläge, wie man dies verbessern kann, sind willkommen).

Eine mit der Maus markierte Zeile kann mit einem Klick auf den Button "Pendenz löschen" entfernt werden.

Der Button "Aktualisieren" wird eigentlich nur dann benötigt, wenn mehrere Clients gestartet wurden und die Eingaben in einer Client-Tabelle auf einer anderen sichtbar sein sollen.

Zu den Bugs:

In der 4 Spalte (Status) kann in einer Checkbox ein Haken gesetzt werden (symbolisiert, ob eine Pendenz erledigt ist oder nicht, hat sonst keine Funktion). Dies funktioniert aber nur dann, wenn ein Client neu gestartet wurde. Sobald einer der 4 Buttons geklickt wird, verschwindet die Checkboxen und es wird nur noch "true" oder "false" angezeigt (je nachdem, ob die Checkbox gehäkelt wurde oder eben nicht). Wird ein neuer Client gestartet, sind die Checkboxen wieder da. Warum? Wo muss ich was ändern, damit dies nicht mehr passiert?

Wenn ein Client neu gestartet wird, dann hat die JTable die "korrekten" vordefinierten Spaltenbreiten. Sobald ein Button geklickt wird, werden aber wieder alle Spalten mit "default"-Breiten dargestellt. Warum? Wo muss ich was ändern, damit dies nicht mehr passiert?

Für Hilfen wäre ich sehr dankbar. Ich bin absolut KEIN Profi, nicht mal ein Fortgeschrittener, würde mich immer noch als blutigen Anfänger bezeichnen. Habe den Code nur mit der Hilfe von div. Tutorials, Büchern, etc. zusammengebastelt.

Gruss

Hier nun der Programmcode:

Java:
import java.rmi.*;
import java.rmi.server.*;
import java.rmi.registry.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
import java.io.*;
import java.net.*;
import java.sql.*;
import java.util.*;
import java.text.*;

// Server-Klasse
class Server 
{
	public static void main(String[] args) 
	{
		System.out.println("Starte Server...");
		try
		{      
			LocateRegistry.createRegistry(Registry.REGISTRY_PORT);    
		}
		catch(RemoteException ex) 
		{      
			System.out.println(ex.getMessage());    
		}
		try
		{
			CounterImpl myCounter = new CounterImpl();
			Naming.rebind("Counter", myCounter);	
		}
		catch(Exception e)
		{
			System.err.println("Exception: " + e.getMessage());
		}
        System.out.println("Server gestartet.");
	}
}

// Client-Klasse
class Client 
{
	public static void main(String[] args) 
	{
		String serverName = "127.0.0.1";
		
		System.out.println("Starte Client....");
		
		try
		{
			Counter myCounter = (Counter) Naming.lookup("rmi://" + serverName + "/Counter");
			GUI gui = new GUI(serverName, myCounter);
			gui.setVisible(true);
		}
		catch(Exception e)
		{
			System.err.println("Exception (Client): " + e.getMessage());
		}
		System.out.println("Client gestartet.");
	}
}

class Issue implements Serializable
{   
	private int i;
    private String date;
    private String description;
    private boolean status;
    
    public Issue(int i)
    {
    	this.setID(i);
    }

    public Issue(int i, String date, String description, boolean status)
    {
    	this.setID(i);
    	this.setDate(date);
    	this.setDescription(description);
    	this.setStatus(status);
    }
    
    public int getID()
    {
        return i;
    }

    public void setID(int i)
    {
        this.i = i;
    }
    
    public String getDate()
    {
        return date;
    }

    public void setDate(String date)
    {
        this.date = date;
    }
    
    public String getDescription()
    {
        return description;
    }

    public void setDescription(String description)
    {
        this.description = description;
    }

    public Boolean getStatus()
    {
        return status;
    }

    public void setStatus(Boolean status)
    {
        this.status = status;
    }
}

// Pendenzen-Tabelle
class IssuesTableModel extends AbstractTableModel implements Serializable
{
    private ArrayList<Issue> issues = new ArrayList<Issue>();
    private int i;
    
    @Override
    public int getColumnCount()
    {
    	return 4;
    }
    
    @Override
    public int getRowCount()
    {
        return issues.size();
    }
    
    @Override
    public String getColumnName(int colNumber)
    {
        switch (colNumber)
        {
        	case 0: return "ID";
            case 1: return "Datum";
            case 2: return "Beschreibung";
            case 3: return "Status";
            default: 
                assert false : "Wrong colNumber " + colNumber;
                return null;
        }
    }
    
    @Override
    public Object getValueAt(int rowNumber, int colNumber)
    {
        Issue issue = issues.get(rowNumber);
        switch (colNumber)
        {
        	case 0: return issue.getID();
            case 1: return issue.getDate();
            case 2: return issue.getDescription();
            case 3: return issue.getStatus();
            default: 
                assert false : "Wrong colNumber " + colNumber;
            return null;
        }
    }
    
    @Override
    public void setValueAt(Object value, int rowNumber, int colNumber)
    {
        Issue issue = issues.get(rowNumber);
        switch (colNumber)
        {
            case 1: 
                issue.setDate((String) value);
                break;
            case 2:
                issue.setDescription((String) value);
                break;
            case 3:
            	issue.setStatus((Boolean) value);
                break;
            default:
                assert false : "Wrong colNumber " + colNumber;
        }
    }
    
    @Override
    public boolean isCellEditable(int rowNumber, int colNumber)
    {
    	if (colNumber == 4)
    	{
    		return false;
    	}
        return true;
    }
    
    public void addIssue()
    {
        issues.add(new Issue(i++));
        int newRowNumber = issues.size();
        super.fireTableRowsInserted(newRowNumber, newRowNumber);
    }

    public void updateIssueAtID(Issue changeIssue)
    {
        if (changeIssue.getID() >= 0)
        {
        	updateIssueAt(searchRowNumberByID(changeIssue.getID()), changeIssue);
        }
    }
    
    public void updateIssueAt(int rowNumber, Issue changeIssue)
    {
        if (rowNumber >= 0 && rowNumber < issues.size())
        {
        	issues.set(rowNumber, changeIssue);
        }
    }
    
    public void delIssueAtID(int rowID)
    {
        if (rowID >= 0)
        {
        	delIssueAt(searchRowNumberByID(rowID));
        }
    }
    
    public void delIssueAt(int rowNumber)
    {
        if (rowNumber >= 0 && rowNumber < issues.size())
        {
        	issues.remove(rowNumber);
            super.fireTableRowsDeleted(rowNumber, rowNumber);
        }
    }

    public int searchRowNumberByID(int rowID)
    {
    	for(int i=0; i < issues.size(); i++)
    	{
    		if (issues.get(i).getID() == rowID)
    		{
    			return i;
    		}
    	}
    	return -1;
    }
    
    public void refresh(int rowNumber)
    {
        if (rowNumber >= 0 && rowNumber < issues.size())
        {
        	//issues.refresh(rowNumber);
            //super.fireTableRowsDeleted(rowNumber, rowNumber);
        }
    }  
}

interface Counter extends Remote
{
	public IssuesTableModel addIssue() throws RemoteException;
	public IssuesTableModel getModel() throws RemoteException;
	public void updateIssueAtID(Issue changeIssue) throws RemoteException;
	public void delIssueAtID(int rowID) throws RemoteException;
}

@SuppressWarnings("serial")
class CounterImpl extends UnicastRemoteObject implements Counter 
{
	private IssuesTableModel issueslist = null;
	
	public CounterImpl() throws RemoteException 
	{
		this.issueslist = new IssuesTableModel();
		System.out.println("wenn kein Eintrag, dann addIssue");
		this.addIssue();
	}

	@Override
	public synchronized IssuesTableModel getModel() throws RemoteException 
	{
		System.out.println("Methode getModelPendenz() returns " + this.issueslist);
		return this.issueslist;
	}
	
	@Override
	public synchronized IssuesTableModel addIssue() throws RemoteException 
	{
		System.out.println("Methode addIssue() returns " + this.issueslist);
		issueslist.addIssue();
		return this.issueslist;
	}

	@Override
	public synchronized void updateIssueAtID(Issue changeIssue) throws RemoteException 
	{
		System.out.println("Methode updateIssueAtID() returns " + this.issueslist.getRowCount());
		this.issueslist.updateIssueAtID(changeIssue);
	}

	@Override
	public synchronized void delIssueAtID(int rowID) throws RemoteException 
	{
		System.out.println("Methode delIssueAtID() returns " + this.issueslist.getRowCount());
		this.issueslist.delIssueAtID(rowID);
	}
}

@SuppressWarnings("serial")
class GUI extends JPanel implements ActionListener 
{
	private Counter model = null;
	private JButton addIssue;
	private JButton safeIssue;
	private JButton delIssue;
	private JButton refresh;
	
    JTable table;
    JCheckBox checkStat;
    
    public GUI(String serverName, Counter myCounter)
    {   	
    	this.model = myCounter;
    	try
        {
        	table = new JTable(this.model.getModel());
		}
		catch(RemoteException e)
		{	
		}
	    
		table.getColumn("Status").setCellRenderer(new BooleanCellRenderer());
        table.getColumn("Status").setCellEditor(new BooleanCellEditor());
        
        JFrame frame = new JFrame("Pendenzenverwaltung");
        	
       	JPanel buttons = new JPanel(new GridLayout(0, 1));	
        buttons.add(genButtonAdd());
        buttons.add(genButtonSaf());
        buttons.add(genButtonDel());
        buttons.add(genButtonRef());
        
        Container content = frame.getContentPane();
        content.add(new JScrollPane(table), BorderLayout.CENTER);
        content.add(buttons, BorderLayout.SOUTH);
        
        table.getColumnModel().getColumn(0).setPreferredWidth(20);
	    table.getColumnModel().getColumn(1).setPreferredWidth(80);
	    table.getColumnModel().getColumn(2).setPreferredWidth(400);
	    table.getColumnModel().getColumn(3).setPreferredWidth(20);
        
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setVisible(true);
    }

    private JButton genButtonAdd()
    {
        this.addIssue = new JButton("Pendenz hinzufügen");
        addIssue.addActionListener(this);
        return addIssue;
    }

    private JButton genButtonSaf()
    {
    	this.safeIssue = new JButton("Pendenz speichern");
    	safeIssue.addActionListener(this);
        return safeIssue;
    }

    private JButton genButtonDel()
    {
    	this.delIssue = new JButton("Pendenz löschen");
    	delIssue.addActionListener(this);
        return delIssue;
    }
    
    private JButton genButtonRef()
    {
    	this.refresh = new JButton("Aktualisieren");
    	refresh.addActionListener(this);
        return refresh;
    }
    
	public void actionPerformed(ActionEvent event) 
	{
		String varAction = event.getActionCommand();
		
		System.out.println("action = " + event.getActionCommand());
		int index = table.getSelectedRow();
				
		if(varAction =="Pendenz hinzufügen")
		{
			try
			{
				table.setModel(this.model.addIssue());	
	        	System.out.println(this.table.getRowCount());
			}
			catch(RemoteException e)
			{	
			}
		}
		else if(varAction == "Pendenz speichern")
		{
			table.editCellAt(index, 2);
			table.getCellEditor().stopCellEditing();
			
			try
			{
				Issue changeIssue = new Issue(Integer.valueOf(table.getValueAt(index, 0).toString()),
												table.getValueAt(index, 1).toString(),
												table.getValueAt(index, 2).toString(), 
												Boolean.valueOf(table.getValueAt(index, 3).toString()));
				
				if (table.getValueAt(index, 2) == null)
				{
					changeIssue.setDescription(" ");
				}
				else
				{
					changeIssue.setDescription(table.getValueAt(index, 2).toString());
				}
				
				System.out.println(changeIssue.getDescription() + "  " + changeIssue.getID());
				model.updateIssueAtID(changeIssue);	
			}
			catch(RemoteException e)
			{
			}
		}
		else if(varAction == "Pendenz löschen")
		{
			if (index >= 0)
            {
				try
				{
					model.delIssueAtID(Integer.valueOf(table.getValueAt(index, 0).toString()));
				}
				catch(RemoteException e)
				{	
				}
            }
		}
		
		try
		{
			table.setModel(this.model.getModel());
		}
		catch(RemoteException e)
		{		
		}
	}

	class BooleanCellEditor extends DefaultCellEditor 
	{
		public BooleanCellEditor() 
		{
				super(new JCheckBox());
				JCheckBox checkBox = (JCheckBox)getComponent();
				checkBox.setHorizontalAlignment(JCheckBox.CENTER);
		}	
	}
	
    class BooleanCellRenderer extends JCheckBox implements TableCellRenderer 
    {
		public BooleanCellRenderer() 
		{
			super();
			setHorizontalAlignment(JLabel.CENTER);
		}

		public Component getTableCellRendererComponent(JTable table, Object value,
				boolean isSelected, boolean hasFocus, int row, int column) 
		{
			if (isSelected) 
			{
				setForeground(table.getSelectionForeground());
				setBackground(table.getSelectionBackground());
			} 
			else 
			{
				setForeground(table.getForeground());
				setBackground(UIManager.getColor("Checkbox.background"));
			}
			setSelected((value != null && ((Boolean)value).booleanValue()));
			return this;
		}
	}
}
 

CroniD

Aktives Mitglied
Ich weiß selbst nicht so viel über JTables, aber ich glaube es wird jedes Mal, wenn du ein neues Model über die Methode setModel(tablemodel) einsetzt auch ein neues ColumnModel erzeugt. Das Verhalten kann man aber mit einer Methode bei der JTable umstellen denke ich (setAutoCreateColumn() oder ähnlich könnte sie heißen).
Was mir aber auffällt ist, dass in den Button Actions immer zum Schluss "table.setModel(this.model.getModel());" ausgeführt wird. Ich bin mir nicht sicher, aber wenn du am Model eine Änderung vornimmst, dann brauchst man doch nur die fireTableModelChange() (oder so ähnlich) Methode beim TableModel aufzurufen und alle JTable Objekte aktualisieren sich von selbst, oder nicht? Jedes Mal ein neues TableModel einzusetzen ist nicht klug glaube ich.
Jedenfalls würde ich schätzen, dass es daran liegt.
 
Hallo CroniD

Vielen Dank für Deine Antwort! :)

Hat mich auf eine Idee gebracht. Habe eine neue Methode geschrieben und die ganzen Spaltenbreite- und Checkbox-Definitionen da reingepackt (und dafür im GUI rausgenommen):

Java:
private void setTableDefinition()
	{
	    table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
	    table.setGridColor(Color.BLACK);
	    table.getColumn("Status").setCellRenderer(new BooleanCellRenderer());
	    table.getColumn("Status").setCellEditor(new BooleanCellEditor());
	    table.getColumnModel().getColumn(0).setPreferredWidth(20);
	    table.getColumnModel().getColumn(1).setPreferredWidth(80);
	    table.getColumnModel().getColumn(2).setPreferredWidth(400);
	    table.getColumnModel().getColumn(3).setPreferredWidth(20);    
	}

Diese Methode habe ich dann bei den Button Actions hinzugefügt, d.h. die Methode wird bei jedem Button-Klick aufgerufen. Jetzt bleiben die Spalten immer so, wie sie sollen. Auch der "Checkbox"-Bug ist weg! :applaus:

Nochmals danke für die "Inspiration". :)

Gruss
 

CroniD

Aktives Mitglied
Ja, gerne. :) Aber das eigentliche grundlegende Problem, dass du nicht immer ein neues TableModel hinzufügen musst bleibt weiterhin. Du hast es nur nun umgangen, aber auf Kosten der Laufzeitperformanz. ;)
 
Hallo

Ja, das stimmt wohl (leider). :oops: - Aber immerhin macht die App jetzt genau das, was sie soll. Optimieren könnte man sicher noch einiges... :rtfm:

Werde Deinen Vorschlag später nochmals prüfen (auf Anhieb habe ich damit leider noch nicht das gewünschte Funktionieren herbeiführen können, aber das liegt wohl eher an meinem Unvermögen als am Vorschlag).

Gruss
 
Ähnliche Java Themen
  Titel Forum Antworten Datum
M Server/Client thread von GUI Trennen AWT, Swing, JavaFX & SWT 2
T LookAndFeel L&F Citrix-Server-Client AWT, Swing, JavaFX & SWT 4
E Client-Server mittels AWT AWT, Swing, JavaFX & SWT 27
E Swing Server-Client OnlineListe AWT, Swing, JavaFX & SWT 4
J Client Server Anwendung - Mehrere Clients in einer Server GUI AWT, Swing, JavaFX & SWT 11
T Serversocket an Client Socket senden AWT, Swing, JavaFX & SWT 2
H Lust auf eine Open-Source Lösung für universelle Java Client-Technologie? AWT, Swing, JavaFX & SWT 11
D Serverdirectory auf Client browsen mit JFileChooser AWT, Swing, JavaFX & SWT 7
Rudolf JavaFx als Client und MySQL AWT, Swing, JavaFX & SWT 9
T Chat-Client AWT, Swing, JavaFX & SWT 15
F Applet vs Stand-Alone-Client AWT, Swing, JavaFX & SWT 3
G Relativer Pfad in Rich Client Anwendung AWT, Swing, JavaFX & SWT 4
G Fertiger Email Client als Bibliothek AWT, Swing, JavaFX & SWT 5
D Standard Email Client starten AWT, Swing, JavaFX & SWT 8
G JProgressBar + Thread + Client AWT, Swing, JavaFX & SWT 10
O Trennung Gui und Logik - Strukturierte Client Anwendung AWT, Swing, JavaFX & SWT 4
M Eine Datei auf den Apache Server hochladen - über das Menü im JavaProgramm AWT, Swing, JavaFX & SWT 7
L JavaFX JavaFX stürtzt durch einen Server#connect Exception AWT, Swing, JavaFX & SWT 3
J Server stellt sich hin AWT, Swing, JavaFX & SWT 13
A Applet Applet vom Web-Server aus aufrufen AWT, Swing, JavaFX & SWT 11
M AWT Datei auf server speichern und beschreiben AWT, Swing, JavaFX & SWT 7
N Server mit Thread und Statusbox AWT, Swing, JavaFX & SWT 3
H PopUp-Fenster in einer Java Server Faces Anwendung AWT, Swing, JavaFX & SWT 4
T Java Application auf X-Server ohne Windowmanager AWT, Swing, JavaFX & SWT 9
O Sicherung gegen nicht gefundenen DB Server AWT, Swing, JavaFX & SWT 2
bernd JTextPane und Server AWT, Swing, JavaFX & SWT 20
bernd UDP-Server AWT, Swing, JavaFX & SWT 3
ExceptionOfExpectation Mit einer laufenden GUI Applikation eine Datei in dem Rechner löschen AWT, Swing, JavaFX & SWT 2
N JavaFX applikation auf anderen Systemen zum laufen bringen AWT, Swing, JavaFX & SWT 7
S Swing-Applikation die ein Numpad nachbildet samt Keybindings..? AWT, Swing, JavaFX & SWT 5
M JavaFX Applikation lädt Scrollpanes nicht AWT, Swing, JavaFX & SWT 19
N Applikation startet nicht AWT, Swing, JavaFX & SWT 2
L LookAndFeel Eigenes Design für die Applikation AWT, Swing, JavaFX & SWT 4
M Applikation mit mehreren Scenes AWT, Swing, JavaFX & SWT 5
C Swing globale "Key Bindings" für Swing-Applikation AWT, Swing, JavaFX & SWT 6
D JDialog alwaysOnTop, aber nur innerhalb der Applikation AWT, Swing, JavaFX & SWT 3
C Java-applikation über Fullscreen AWT, Swing, JavaFX & SWT 4
dzim SWT SWT-AWT-Problem: Crash einer Applikation AWT, Swing, JavaFX & SWT 8
maddin86 Browserfenster / Webbrowser in Java Applikation einbetten AWT, Swing, JavaFX & SWT 15
B Swing Sprünge in einer Swing Desktop Applikation AWT, Swing, JavaFX & SWT 2
L JavaFX in Java Applikation einbinden AWT, Swing, JavaFX & SWT 2
M Swing Java Applikation in MVC gut strukturieren AWT, Swing, JavaFX & SWT 14
M Swing Ändern des Themes einer fremden Applikation AWT, Swing, JavaFX & SWT 8
R Applikation so sehen wie ein Saudi oder Japaner..? AWT, Swing, JavaFX & SWT 4
S Allgemeine Frage zur Unterbringung von allgemeinen Daten einer Applikation AWT, Swing, JavaFX & SWT 2
P Schriftart setzen für die ganze Applikation AWT, Swing, JavaFX & SWT 3
J Applikation als Desktop-"Widget" AWT, Swing, JavaFX & SWT 2
GambaJo GUI sieht in NB anders aus, als fertige Applikation AWT, Swing, JavaFX & SWT 2
T WaitCursor zuverlässig für die ganze Applikation setzen? AWT, Swing, JavaFX & SWT 3
W Soundeffekt in Applikation AWT, Swing, JavaFX & SWT 3
G Desktopverhalten einer Applikation AWT, Swing, JavaFX & SWT 6
M Windows-Fensterinhalt in Java-Applikation darstellen AWT, Swing, JavaFX & SWT 6
M Swing Applikation mit Fenster "inside" AWT, Swing, JavaFX & SWT 2
M google maps in Java-Applikation AWT, Swing, JavaFX & SWT 13
S Tastaturevents an eine externe Applikation weiter leiten AWT, Swing, JavaFX & SWT 6
G Seltsames Phänomen beim starten meiner Applikation AWT, Swing, JavaFX & SWT 8
isowiz Einige Fragen zur ersten eigenen Sing-Applikation AWT, Swing, JavaFX & SWT 4
A Applikation mit Netbeans-Design AWT, Swing, JavaFX & SWT 4
L Java-Logo in Java-Applikation durch eignes ersetzen AWT, Swing, JavaFX & SWT 12
M Anzeigefehler nach langer Laufzeit der Applikation AWT, Swing, JavaFX & SWT 4
N Netzwerk-Applikation, SWT und Threads AWT, Swing, JavaFX & SWT 4
G ConsolenFenster in Applikation einbinden? AWT, Swing, JavaFX & SWT 2
O Applikation mit unterschiedlichen Fenstern AWT, Swing, JavaFX & SWT 4
O Problem beim Umwandeln eines Applets in eine Applikation AWT, Swing, JavaFX & SWT 15
G Mediaplayer oder ähnliches in Java-Applikation einbinden AWT, Swing, JavaFX & SWT 2

Ähnliche Java Themen

Neue Themen


Oben