java - JTextField input fails to update output in TextView in MVC -
i'm studying advanced java , trying write program utilizes mvc design pattern. program needs draw string can modified user's input in jtextfield
. user can adjust color , font size of text through jcombobox
, jspinner
respectively.
here have far:
public class mvcdemo extends japplet { private jbutton jbtncontroller = new jbutton("show controller"); private jbutton jbtnview = new jbutton("show view"); private textmodel model = new textmodel(); //constructor public mvcdemo(){ //set layout , add buttons setlayout(new flowlayout()); add(jbtncontroller); add(jbtnview); jbtncontroller.addactionlistener(new actionlistener(){ @override public void actionperformed(actionevent e){ jframe frame = new jframe("controllor"); textcontroller controller = new textcontroller(); controller.setmodel(model); frame.add(controller); frame.setsize(200, 100); frame.setlocationrelativeto(null); frame.setvisible(true); } }); jbtnview.addactionlistener(new actionlistener(){ @override public void actionperformed(actionevent e){ jframe frame = new jframe("view"); textview view = new textview(); view.setmodel(model); frame.add(view); frame.setsize(500, 200); frame.setlocation(200, 200); frame.setvisible(true); } }); } public static void main(string[] args){ mvcdemo applet = new mvcdemo(); jframe frame = new jframe(); frame.setdefaultcloseoperation(jframe.exit_on_close); frame.settitle("mvcdemo"); frame.getcontentpane().add(applet, borderlayout.center); frame.setsize(400, 90); frame.setlocationrelativeto(null); frame.setvisible(true); } } public class textmodel { private string text = "your student id #"; //utility field used event firing mechanism private arraylist<actionlistener> actionlistenerlist; public void settext(string text){ this.text = text; //notify listener change on text processevent(new actionevent(this, actionevent.action_performed, "text")); } public string gettext(){ return text; } //register action event listener public synchronized void addactionlistener(actionlistener l){ if (actionlistenerlist == null) actionlistenerlist = new arraylist<actionlistener>(); } //remove action event listener public synchronized void removeactionlistener(actionlistener l){ if (actionlistenerlist != null && actionlistenerlist.contains(l)) actionlistenerlist.remove(l); } //fire tickevent private void processevent(actionevent e){ arraylist<actionlistener> list; synchronized (this){ if (actionlistenerlist == null) return; list = (arraylist<actionlistener>)(actionlistenerlist.clone()); } } } public class textview extends jpanel{ private textmodel model; //set model public void setmodel(textmodel model){ this.model = model; if (model != null) //register view listener model model.addactionlistener(new actionlistener(){ @override public void actionperformed(actionevent e){ repaint(); } }); } public textmodel getmodel(){ return model; } @override public void paintcomponent(graphics g){ if (model != null){ super.paintcomponent(g); //g.setcolor(model.getcolor()); g.drawstring(model.gettext(), 190, 90); } } } public class textcontroller extends jpanel { string[] colorstrings = { "black", "blue", "red" }; private textmodel model; private jtextfield jtftext = new jtextfield(); private jcombobox jcbocolorlist = new jcombobox(colorstrings); //constructor public textcontroller(){ //panel group labels jpanel panel1 = new jpanel(); panel1.setlayout(new gridlayout(3, 1)); panel1.add(new jlabel("text")); panel1.add(new jlabel("color")); panel1.add(new jlabel("size")); //panel group text field, combo box , spinner jpanel panel2 = new jpanel(); panel2.setlayout(new gridlayout(3, 1)); panel2.add(jtftext); panel2.add(jcbocolorlist); setlayout(new borderlayout()); add(panel1, borderlayout.west); add(panel2, borderlayout.center); //register listeners jtftext.addactionlistener(new actionlistener(){ @override public void actionperformed(actionevent e){ if (model != null) model.settext(jtftext.gettext()); } }); /*jcbocolorlist.addactionlistener(new actionlistener(){ @override public void actionperformed(actionevent e){ if (model != null) model.set } });*/ } public void setmodel(textmodel model){ this.model = model; } public textmodel getmodel(){ return model; } }
at moment i've implemented jtextfield
component (yet figure out how jcombobox
, jspinner
properly), , hardly perfect.
when first launch program , turn on both view , controller panels, default string of "your student id #" shown correctly in view. when type other string jtextfield
, hit enter, output string in textview
not update unless close view panel , reopen it. point out causing behavior?
i suspect has event handling part of program. i'm still quite new gui programming , have basic understanding of how events triggered , handled. grateful if explain underlying cause of problem in beginner-friendly fashion.
your mixing layers, both model , controller non visual entities. i'm in phone i've not inspected code in depth, but, view should notify controller (directly or in directly) when values change, controller update model accordingly notify controller further notify view
in formal mvc, model , view should never know each other, controller used bridge them together. swing doesn't follow strict mvc (it's more of mv-c) , trying wrap strict mvc around can cause no end of headaches.
instead, do, wrap mvc around swing, means view doesn't need expose ui elements, instead, relies on contract between controller , view determine each party can do
let's start example.
start defining contracts. these should interfaces, allows decouple code in way allows physical implementation change without affecting other parts of api, maybe like...
public interface textmodel { public void settext(string text); public string gettext(); public void addchangelistener(changelistener listener); public void removechangelistener(changelistener listener); } public interface textcontroller { public string gettext(); public void settext(string text); } public interface textview { public textcontroller getcontroller(); public void setcontroller(textcontroller controller); public void settext(string text); }
now, normally, i'd consider making abstract
versions, wrap common functionality, sake of example, i've jumped straight default implementations...
public class defaulttextmodel implements textmodel { private string text; private set<changelistener> listeners; public defaulttextmodel() { listeners = new hashset<>(25); } @override public string gettext() { return text; } @override public void settext(string value) { if (text == null ? value != null : !text.equals(value)) { this.text = value; firestatechanged(); } } @override public void addchangelistener(changelistener listener) { listeners.add(listener); } @override public void removechangelistener(changelistener listener) { listeners.remove(listener); } protected void firestatechanged() { changelistener[] changelisteners = listeners.toarray(new changelistener[0]); if (changelisteners != null && changelisteners.length > 0) { changeevent evt = new changeevent(this); (changelistener listener : changelisteners) { listener.statechanged(evt); } } } } public class defaulttextcontroller implements textcontroller { private textmodel model; private textview view; public defaulttextcontroller(textmodel model, textview view) { this.model = model; this.view = view; this.view.setcontroller(this); this.model.addchangelistener(new changelistener() { @override public void statechanged(changeevent e) { // make "textwaschanged" method on view // , make view ask controller value, where's // fun in :p getview().settext(gettext()); } }); } public textmodel getmodel() { return model; } public textview getview() { return view; } @override public string gettext() { return getmodel().gettext(); } @override public void settext(string text) { getmodel().settext(text); } }
now, should asking yourself, how going work, have input , output view. reality is, work really, well, first, need 2 different views...
public class inputtextview extends jpanel implements textview { private textcontroller controller; public inputtextview() { setlayout(new gridbaglayout()); jtextfield field = new jtextfield(10); add(field); field.addactionlistener(new actionlistener() { @override public void actionperformed(actionevent e) { getcontroller().settext(field.gettext()); } }); } @override public textcontroller getcontroller() { return controller; } @override public void setcontroller(textcontroller controller) { this.controller = controller; } @override public void settext(string text) { // kind of don't care, because we're responsible changing // text anyway :p } } public class outputtextview extends jpanel implements textview { private textcontroller controller; public outputtextview() { } @override public textcontroller getcontroller() { return controller; } @override public void setcontroller(textcontroller controller) { this.controller = controller; } @override public void settext(string text) { revalidate(); repaint(); } @override public dimension getpreferredsize() { dimension size = new dimension(200, 40); textcontroller controller = getcontroller(); if (controller != null) { string text = controller.gettext(); fontmetrics fm = getfontmetrics(getfont()); if (text == null || text.trim().isempty()) { size.width = fm.stringwidth("m") * 10; } else { size.width = fm.stringwidth(text); } size.height = fm.getheight(); } return size; } @override protected void paintcomponent(graphics g) { super.paintcomponent(g); textcontroller controller = getcontroller(); string text = ""; if (controller != null) { text = controller.gettext(); } if (text == null) { text = ""; } fontmetrics fm = g.getfontmetrics(); int x = (getwidth() - fm.stringwidth(text)) / 2; int y = ((getheight() - fm.getheight()) / 2) + fm.getascent(); g.drawstring(text, x, y); } }
these both implementations of textview
, difference is, 1 view (the input) sets text , ignores changes text , 1 responds changes in text , never sets it...
brain still not coping? let me demonstrate....
inputtextview inputview = new inputtextview(); outputtextview outputview = new outputtextview(); textmodel model = new defaulttextmodel(); // shared model!! textcontroller inputcontroller = new defaulttextcontroller(model, inputview); textcontroller outputcontroller = new defaulttextcontroller(model, outputview);
basically, here, 1 2 views, 2 controllers , one, shared, model. when input side of things changes text, model notifies output side of things , updated
and because know fun copy separate pieces of code , stich them together...
import java.awt.dimension; import java.awt.eventqueue; import java.awt.fontmetrics; import java.awt.graphics; import java.awt.gridbaglayout; import java.awt.gridlayout; import java.awt.event.actionevent; import java.awt.event.actionlistener; import java.util.hashset; import java.util.set; import javax.swing.jframe; import javax.swing.jpanel; import javax.swing.jtextfield; import javax.swing.uimanager; import javax.swing.unsupportedlookandfeelexception; import javax.swing.event.changeevent; import javax.swing.event.changelistener; public class test { public static void main(string[] args) { new test(); } public test() { eventqueue.invokelater(new runnable() { @override public void run() { try { uimanager.setlookandfeel(uimanager.getsystemlookandfeelclassname()); } catch (classnotfoundexception | instantiationexception | illegalaccessexception | unsupportedlookandfeelexception ex) { ex.printstacktrace(); } inputtextview inputview = new inputtextview(); outputtextview outputview = new outputtextview(); textmodel model = new defaulttextmodel(); // shared model!! textcontroller inputcontroller = new defaulttextcontroller(model, inputview); textcontroller outputcontroller = new defaulttextcontroller(model, outputview); jframe frame = new jframe("testing"); frame.setdefaultcloseoperation(jframe.exit_on_close); frame.setlayout(new gridlayout(2, 0)); frame.add(inputview); frame.add(outputview); frame.pack(); frame.setlocationrelativeto(null); frame.setvisible(true); } }); } public interface textmodel { public void settext(string text); public string gettext(); public void addchangelistener(changelistener listener); public void removechangelistener(changelistener listener); } public interface textcontroller { public string gettext(); public void settext(string text); } public interface textview { public textcontroller getcontroller(); public void setcontroller(textcontroller controller); public void settext(string text); } public class defaulttextmodel implements textmodel { private string text; private set<changelistener> listeners; public defaulttextmodel() { listeners = new hashset<>(25); } @override public string gettext() { return text; } @override public void settext(string value) { if (text == null ? value != null : !text.equals(value)) { this.text = value; firestatechanged(); } } @override public void addchangelistener(changelistener listener) { listeners.add(listener); } @override public void removechangelistener(changelistener listener) { listeners.remove(listener); } protected void firestatechanged() { changelistener[] changelisteners = listeners.toarray(new changelistener[0]); if (changelisteners != null && changelisteners.length > 0) { changeevent evt = new changeevent(this); (changelistener listener : changelisteners) { listener.statechanged(evt); } } } } public class defaulttextcontroller implements textcontroller { private textmodel model; private textview view; public defaulttextcontroller(textmodel model, textview view) { this.model = model; this.view = view; this.view.setcontroller(this); this.model.addchangelistener(new changelistener() { @override public void statechanged(changeevent e) { // make "textwaschanged" method on view // , make view ask controller value, where's // fun in :p getview().settext(gettext()); } }); } public textmodel getmodel() { return model; } public textview getview() { return view; } @override public string gettext() { return getmodel().gettext(); } @override public void settext(string text) { getmodel().settext(text); } } public class inputtextview extends jpanel implements textview { private textcontroller controller; public inputtextview() { setlayout(new gridbaglayout()); jtextfield field = new jtextfield(10); add(field); field.addactionlistener(new actionlistener() { @override public void actionperformed(actionevent e) { getcontroller().settext(field.gettext()); } }); } @override public textcontroller getcontroller() { return controller; } @override public void setcontroller(textcontroller controller) { this.controller = controller; } @override public void settext(string text) { // kind of don't care, because we're responsible changing // text anyway :p } } public class outputtextview extends jpanel implements textview { private textcontroller controller; public outputtextview() { } @override public textcontroller getcontroller() { return controller; } @override public void setcontroller(textcontroller controller) { this.controller = controller; } @override public void settext(string text) { revalidate(); repaint(); } @override public dimension getpreferredsize() { dimension size = new dimension(200, 40); textcontroller controller = getcontroller(); if (controller != null) { string text = controller.gettext(); fontmetrics fm = getfontmetrics(getfont()); if (text == null || text.trim().isempty()) { size.width = fm.stringwidth("m") * 10; } else { size.width = fm.stringwidth(text); } size.height = fm.getheight(); } return size; } @override protected void paintcomponent(graphics g) { super.paintcomponent(g); textcontroller controller = getcontroller(); string text = ""; if (controller != null) { text = controller.gettext(); } if (text == null) { text = ""; } fontmetrics fm = g.getfontmetrics(); int x = (getwidth() - fm.stringwidth(text)) / 2; int y = ((getheight() - fm.getheight()) / 2) + fm.getascent(); g.drawstring(text, x, y); } } }
Comments
Post a Comment