Tuesday, 15 April 2014

java - The right approach to update complex JTables, TableModel and others -



java - The right approach to update complex JTables, TableModel and others -

my gui shows vehicles in park, , vehicles want set availables in 2 different vehicletables (classes extend jtable). availables intend these vehicles can observed agent (third-part software). both tables show descriptions of vehicles in rows...for have created vehicletablemodel , vehicle classes. vehicle class abstract class , subclasses are: car, truck, trailer, etc. .

you can see snapshot of software:

my problems these: in current implementation don't think of manage updates of rows. can see in vehicletablemodel (fire...() methods) , in shipperagentgui (coordinators , listeners). think have partially resolved problem utilize of coordinator inner class updates between tables, don't know how optimize these. illustration in case of delete or update of row create xxxtable.repaint(); ... whole table...

...another way?

shipperagentgui.java

public class shipperagentgui extends jframe implements actionlistener { // graphics variables.. // bla bla... // headers, tablemodels, jtables tables private columns[] parkmodelheader = {columns.image_column, columns.targa_column, columns.car_type_column, columns.marca_column, columns.state_column, columns.ptt_column }; private columns[] availablesmodelheader = {columns.image_column, columns.targa_column, columns.car_type_column, columns.marca_column }; private vehicletablemodel parkmodel = new vehicletablemodel(parkmodelheader); private vehicletablemodel availablesmodel = new vehicletablemodel(availablesmodelheader); private vehicletable parktable; private vehicletable availablestable; // third-part software, jade agent: protected shipperagent shipperagent; // -------------------------------------------------------------------------- // constructor shipperagentgui(shipperagent agent) { shipperagent = agent; // valorizes agent settitle("shipper agent: "+agent.getlocalname()+" gui"); // graphic bla bla... // park table , available table: parktable = new vehicletable(parkmodel); // bla bla... availablestable = new vehicletable(availablesmodel); // bla bla... // jbuttons: add/remove vehicle in park table , available table btnpm_plus = new jbutton(); btnpm_plus.settooltiptext("add vehicle"); btnpm_plus.seticon(...); btnpm_plus.setactioncommand("+park"); btnpm_plus.addactionlistener(this); // similar things other 3 buttons: // remove parktable, add together , remove availablestable //bla bla... // info agent: vector<vehicle> veicoli = shipperagent.getvehicles(); iterator<vehicle> = veicoli.iterator(); while (i.hasnext()){ addvehicle(parkcoordinator, i.next()); } showgui(); } /////////////////////////////////////////////////////////////////////// // methods: public void showgui() { // bla bla } ////////////////////////////////////////////// // actionperformed method @override public void actionperformed(actionevent e) { switch (e.getactioncommand()) { case "+park": { new insertvehiclejdialog(this, parkcoordinator); } break; case "-park": { int selectedrow = parktable.getselectedrow(); if (selectedrow != -1) removevehicle(parkcoordinator, selectedrow); } break; case "+available": { int selectedrow = parktable.getselectedrow(); if (selectedrow != -1){ addvehicle(availablescoordinator, parkmodel.getvehicleat(selectedrow)); } } break; case "-available": { int selectedrow = availablestable.getselectedrow(); if (selectedrow != -1) removevehicle(availablescoordinator, selectedrow); } break; } } /////////////////////////////////////// // add/remove vehicle methods: void addvehicle(coordinator coordinator, vehicle v) { coordinator.notifyandaddrow(v); } // mhm... void removevehicle(coordinator coordinator, vehicle v) { int row = coordinator.indexof(v); if (row!=-1) coordinator.notifyanddeleterow(row); } void removevehicle(coordinator coordinator, int index) { coordinator.notifyanddeleterow(index); } // on dispose, delete agent public void dispose() { super.dispose(); shipperagent.dodelete(); } /////////////////////////////////////// // inner class coordinator: protected abstract class coordinator { private vehicletablemodel tablemodel; public coordinator(vehicletablemodel tm) { tablemodel = tm; notifyrowupdated(); } public abstract void notifyandaddrow(vehicle vehicle); public abstract void notifyanddeleterow(int rowindex); public abstract void notifyrowupdated(); public int indexof(vehicle v) { homecoming tablemodel.indexof(v); } boolean vehicleexists(vehicle vehicle){ int bool = indexof(vehicle); if (bool==-1) homecoming false; else homecoming true; } } // coordinator parktable coordinator parkcoordinator = new coordinator(parkmodel) { @override public void notifyandaddrow(final vehicle vehicle) { if (!vehicleexists(vehicle)){ // right control? or in vehicletablemodel ? shipperagent.newtruck(vehicle.getplate()); swingutilities.invokelater(new runnable() { @override public void run() { parkmodel.addrow(vehicle); if (vehicle.getstate().equals(stato.disponibile)) availablesmodel.addrow(vehicle); // or availablescoordinator.notifyandaddrow(vehicle) ? // or addvehicle(availablescoordinator, vehicle) ? // or kind of listener on vehicle's state ? } }); } } @override public void notifyanddeleterow(final int rowindex) { final vehicle v = parkmodel.getvehicleat(rowindex); removevehicle(availablescoordinator, v); // remove "availables" shipperagent.removetruck(v.getplate()); swingutilities.invokelater(new runnable() { @override public void run() { parkmodel.removerow(rowindex); } }); } @override public void notifyrowupdated() { parkmodel.addtablemodellistener(new tablemodellistener() { public void tablechanged(tablemodelevent e) { switch (e.gettype()) { case (tablemodelevent.delete): parktable.repaint(); break; case (tablemodelevent.update): int row = e.getlastrow(); vehicle v = parkmodel.getvehicleat(row); if (v.getstate().equals(stato.disponibile)){ addvehicle(availablescoordinator, v); availablestable.repaint(); } else removevehicle(availablescoordinator, v); parktable.repaint(); break; } } }); } }; // coordinator availablestable coordinator availablescoordinator = new coordinator(availablesmodel) { @override public void notifyandaddrow(final vehicle vehicle) { if (!vehicleexists(vehicle)){ // right control? or in vehicletablemodel ? vehicle.setstato(stato.disponibile); parktable.repaint(); shipperagent.activatetruck(vehicle.getplate()); swingutilities.invokelater(new runnable() { @override public void run() { availablesmodel.addrow(vehicle); } }); } } @override public void notifyanddeleterow(final int rowindex) { vehicle v = availablesmodel.getvehicleat(rowindex); if (v!=null){ v.setstato(stato.non_disponibile); // mhm shipperagent.deactivatetruck(v.getplate()); swingutilities.invokelater(new runnable() { @override public void run() { availablesmodel.removerow(rowindex); } }); } } @override public void notifyrowupdated() { availablesmodel.addtablemodellistener(new tablemodellistener() { public void tablechanged(tablemodelevent e) { switch (e.gettype()) { case (tablemodelevent.delete): parktable.repaint(); break; case (tablemodelevent.update): parktable.repaint(); break; } } }); } }; }

vehicletablemodel.java

public class vehicletablemodel extends abstracttablemodel { private arraylist<vehicle> vehicles ; private columns[] header; // possible column names: public enum columns { image_column, targa_column, car_type_column, marca_column, state_column, ptt_column, }; /////////////////////////////////////////////////////// // constructor: public vehicletablemodel(columns[] headertable) { this.vehicles = new arraylist<vehicle>(); this.header = headertable; } /////////////////////////////////////////////////////// // obligatory override methods (from abstracttablemodel): @override public int getcolumncount() { homecoming header.length; } @override public int getrowcount() { homecoming vehicles.size(); } @override public object getvalueat(int row, int col) { object value = "?"; vehicle v = vehicles.get(row); if (v!=null) { columns column = header[col]; switch (column) { case image_column: value = vehicleutils.findimagebycolumncartype(v.gettype()); break; case targa_column: value = v.getplate(); break; case car_type_column: value = vehicleutils.findstringbycolumncartype(v.gettype()); break; // other cases... bla bla... } } homecoming value; } /////////////////////////////////////////////////////// // methods: public void addrow(vehicle vehicle) { vehicles.add(vehicle); firetablerowsinserted(0, getrowcount()); // right? } /*public boolean removerow(vehicle vehicle) { boolean flag = vehicles.remove(vehicle); firetablerowsdeleted(0, getrowcount()); // right? homecoming flag; }*/ public void removerow(int row) { vehicles.remove(row); firetablerowsdeleted(row, row); // right? } public vehicle getvehicleat(int row) { homecoming vehicles.get(row); } public int indexof(vehicle v){ homecoming vehicles.indexof(v); } // found corresponding column index public int findcolumn(columns columnname) { (int i=0; i<getcolumncount(); i++) if (columnname.equals(header[i])) homecoming i; homecoming -1; } // value in column exist in table? private boolean controllifexist(object value, int col) { boolean bool = false; (int i=0; i<getrowcount();i++){ if (value.equals(getvalueat(i, col))){ bool=true; break; } } homecoming bool; } public int getcolumnindex(columns column){ for(int i=0;i<header.length;i++){ if (column.equals(header[i])){ homecoming i; } } homecoming -1; } /////////////////////////////////////////////////////// // other methods (from abstracttablemodel) override: @override public class<?> getcolumnclass(int col) { class<?> c; columns column = header[col]; if (column.equals(columns.image_column)) c = imageicon.class; else if (column.equals(columns.state_column)) c = jcombobox.class; else c = super.getcolumnclass(col); homecoming c; } @override public string getcolumnname(int col) { columns column = header[col]; if (column.equals(columns.image_column)) homecoming " "; else if (column.equals(columns.targa_column)) homecoming "targa"; // others... bla bla... homecoming super.getcolumnname(col); }; @override public boolean iscelleditable(int row, int col) { homecoming true; } @override public void setvalueat(object value, int row, int col) { vehicle v = vehicles.get(row); boolean flag = false; if (v!=null) { columns column = header[col]; switch (column) { case targa_column: if (!v.getplate().equals(value)){ if (!controllifexist(value, col)){ // mhm... v.setplate((string) value); flag = true; } } break; case marca_column: if (!v.getmark().equals(value)){ v.setmark((string) value); flag = true; } break; // others ... bla bla... } // update if necessary: if (flag) firetablerowsupdated(row, row); // right? } } }

the whole matter starts @ tablemodel implementation, let's take it:

public class vehicletablemodel extends abstracttablemodel { private arraylist<vehicle> vehicles; // of code here, didn't examine closer though public void addrow(vehicle vehicle) { int rowindex = vehicles.size(); vehicles.add(vehicle); firetablerowsinserted(rowindex, rowindex); // notify lastly row == vehicles.size() == getrowcount() } public void removerow(int row) { vehicles.remove(row); firetablerowsdeleted(row, row); // right? yes, looks ok. } @override public void setvalueat(object value, int row, int col) { vehicle v = vehicles.get(row); if (v != null) { columns column = header[col]; switch (column) { case targa_column:...; break; case marca_column:...; break; // others... } firetablecellupdated(row, column); // appropriate fire method. } } /** * convenience method notify if vehicle updated in * outside, not through setvalueat(...). */ public void notifyvehicleupdated(vehicle vehicle) { vehicle[] elements = (vehicles[])vehicles.toarray(); (int = 0; < elements.length; i++) { if (elements[i] == vehicle) { firetablerowsupdated(i, i); } } } }

some other hints:

never utilize repaint() nor updateui() refresh table's data. it's table model responsibility notify view right event.

never utilize firetabledatachanged() (as suggested) unless whole table model info has changed. there appropriate firexxxx() methods rows, columns , cells changes.

as far understand problem, both tables share vehicles list , have maintain them in synch. if so, i'm wondering why need 2 different table models? if reason status available/parked (mutually exclusive) can have single table model shared along 2 tables , apply different filters accordingly vehicle's status. on status field update, both tables notified , vehicle transferred 1 table another.

update

some time ago in comment this answer thought of adding method such notifyrowupdated() coordinator abstract class seemed appropriated solve synchronization matter between both tables.

but think best approach sharing same table model along 2 tables , filtering sec table based on vehicle's status: if available (disponibile) show it, if not hide it.

this way on both row update , row delete both tables notified , deed accordingly. on cell update can add together tablemodellistener model applies filter on sec table, showing available vehicles , hidding non available ones. not mention coordinator abstract class remain simple , maintain original purpose: notify third-party agent on row updates/deletes.

so please take code illustration below (sorry extension). notes:

i have emulated vehicle class simpler one. status defined available boolean property. dataobjecttablemodel code available in tablemodel tag wiki , i've used class emulate table model. because don't have coordinator class add/remove rows straight on table model, should hrough appropriate coordinator. don't know why have re-apply filters on table cell update events. far understand table row sorter should notified , automatically apply filters. doesn't work in way , have manually re-apply fitlers. minor problem though. code example import java.awt.borderlayout; import java.awt.event.actionevent; import java.util.arrays; import javax.swing.abstractaction; import javax.swing.action; import javax.swing.defaultrowsorter; import javax.swing.jbutton; import javax.swing.jframe; import javax.swing.jpanel; import javax.swing.jscrollpane; import javax.swing.jtable; import javax.swing.listselectionmodel; import javax.swing.rowfilter; import javax.swing.swingutilities; import javax.swing.event.tablemodelevent; import javax.swing.event.tablemodellistener; import javax.swing.table.tablecolumnmodel; public class demosharedtablemodel { private dataobjecttablemodel<vehicle> model; private jtable table1, table2; private action addaction, removeaction; private void createandshowgui() { string[] columnidentifiers = new string[] { "plates", "description", "available" }; model = new dataobjecttablemodel<vehicle>(arrays.aslist(columnidentifiers)) { @override public class<?> getcolumnclass(int columnindex) { switch (columnindex) { case 0: case 1: homecoming string.class; case 2: homecoming boolean.class; } homecoming super.getcolumnclass(columnindex); } @override public boolean iscelleditable(int rowindex, int columnindex) { homecoming columnindex == 2; } @override public object getvalueat(int rowindex, int columnindex) { vehicle vehicle = getdataobject(rowindex); switch (columnindex) { case 0 : homecoming vehicle.getplates(); case 1: homecoming vehicle.getdescription(); case 2: homecoming vehicle.isavailable(); default: throw new arrayindexoutofboundsexception(columnindex); } } @override public void setvalueat(object avalue, int rowindex, int columnindex) { if (columnindex == 2) { vehicle vehicle = getdataobject(rowindex); vehicle.setavailable((boolean)avalue); firetablecellupdated(rowindex, columnindex); } else { throw new unsupportedoperationexception("unsupported column " + columnindex); } } }; model.addrow(new vehicle("aaa1", "car - peugeot", true)); model.addrow(new vehicle("aaa2", "truck - volvo", true)); model.addrow(new vehicle("aaa3", "car - ford", false)); model.addrow(new vehicle("aaa4", "car - mercedes-benz", false)); model.addrow(new vehicle("aaa5", "car - ferrari", true)); model.addtablemodellistener(new tablemodellistener() { @override public void tablechanged(tablemodelevent e) { if (e.gettype() == tablemodelevent.update) { demosharedtablemodel.this.applyfilteronsecondtable(); } } }); table1 = new jtable(model); table1.setautocreaterowsorter(true); table1.setselectionmode(listselectionmodel.single_selection); table2 = new jtable(model); table2.setautocreaterowsorter(true); table2.setselectionmode(listselectionmodel.single_selection); // create 3rd column not visible tablecolumnmodel columnmodel = table2.getcolumnmodel(); columnmodel.removecolumn(columnmodel.getcolumn(2)); applyfilteronsecondtable(); addaction = new abstractaction("+") { @override public void actionperformed(actionevent e) { model.addrow(new vehicle("new", "default text", true)); } }; removeaction = new abstractaction("-") { @override public void actionperformed(actionevent e) { int viewindex = table1.getselectedrow(); if (viewindex != -1) { int modelindex = table1.convertrowindextomodel(viewindex); model.deleterow(modelindex); } setenabled(model.getrowcount() > 0); } }; jpanel buttonspanel = new jpanel(); buttonspanel.add(new jbutton(addaction)); buttonspanel.add(new jbutton(removeaction)); jpanel content = new jpanel(new borderlayout(8, 8)); content.add(new jscrollpane(table1), borderlayout.west); content.add(buttonspanel, borderlayout.center); content.add(new jscrollpane(table2), borderlayout.east); jframe frame = new jframe("demo"); frame.setdefaultcloseoperation(jframe.dispose_on_close); frame.add(content); frame.pack(); frame.setlocationbyplatform(true); frame.setvisible(true); } private void applyfilteronsecondtable() { defaultrowsorter sorter = (defaultrowsorter)table2.getrowsorter(); sorter.setrowfilter(new rowfilter() { @override public boolean include(rowfilter.entry entry) { vehicle vehicle = model.getdataobject((integer)entry.getidentifier()); homecoming vehicle.isavailable(); } }); } class vehicle { private string plates, description; private boolean available; public vehicle(string plates, string description, boolean available) { this.plates = plates; this.description = description; this.available = available; } public string getplates() { homecoming plates; } public void setplates(string plates) { this.plates = plates; } public string getdescription() { homecoming description; } public void setdescription(string description) { this.description = description; } public boolean isavailable() { homecoming available; } public void setavailable(boolean available) { this.available = available; } } public static void main(string[] args) { swingutilities.invokelater(new runnable() { @override public void run() { new demosharedtablemodel().createandshowgui(); } }); } } screenshot

note in sec table available vehicles displayed.

java swing jtable tablemodel

No comments:

Post a Comment