Results 1 to 15 of 15
  1. #1
    711groove is offline Member
    Join Date
    Dec 2009
    Posts
    15
    Rep Power
    0

    Default subclasses and inheritance - how's it all work?

    I spent 4 days writing 600 lines, 50,000 characters of code for a program to put a GUI to a simple windows batch script. While I won't post that here, just because it's so ridiculously long (and poorly written), here's something I wrote in my first couple days of java with a similar structure, just waaaay shorter.

    EDIT: ignore this code, go to post 3

    PHP Code:
    import javax.swing.*;
    import java.awt.event.*;
    import java.util.*;
    
    public class guessingGame extends JFrame{
    public static void main(String[] args){new guessingGame();}
        
    public guessingGame(){
      answer = generator.nextInt(101);
      JPanel panel = new JPanel();
      panel.add(guessField);
          guessField.addActionListener(new goListener());
      panel.add(goButton);
          goButton.addActionListener(new goListener());
          goButton.setActionCommand("Guess It!");
      panel.add(status);add(panel);setVisible(true);setSize(180,90);
          setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);setTitle("GUESS!");}
    
    //1
    
    	int answer;
    	JTextField guessField = new JTextField(4);
    	JButton goButton = new JButton("Guess It!");
    	JLabel status = new JLabel("Guess the Number! 1- 100");
        Random generator = new Random();
        boolean won = false;
        
    //2
    
    private class goListener implements ActionListener{
          public void actionPerformed(ActionEvent e){
          		if (won & (e.getActionCommand()) == (goButton.getActionCommand())){
          			answer = generator.nextInt(101);goButton.setText("Guess It!");
          			won=false;status.setText("Guess the Number! 1-100");guessField.selectAll();
          			}else {check();}}}
    
    //3
    
    public void check(){
      int guess = Integer.parseInt(guessField.getText());
        if (guess < answer){status.setText(guess + " is too low!");guessField.selectAll();}
        if (guess > answer){status.setText(guess + " is too high!");guessField.selectAll();}
        if (guess == answer){status.setText("...~~~```YOU WIN```~~~...");
        goButton.setText("Play Again");won = true;}}}
    
    //4
    Now, what I want to do, for the sake of breaking 600 lines down into something more usable, is make this program into as many .java files as possible each as short as possible. So, for example, everything from //1 to //2 in one file, from //2 to //3 in another, and from //3 to //4 in yet another.

    It seems silly for a 40 line program like this, but when you have 200 components and a two different methods that update every single one of them, it makes sense to break it down.

    My problem is really that I don't know how to make the fields carry over from the 'master' class to each subclass so it can be manipulated from there.

    If you could provide an example of this using the above code, I'd be extremely grateful. And even if you don't want to do all that work, just pointing me to a decent example I can emulate would be helpful. I just don't understand how it works. Object-orientedness is escaping me.

    Thanks in advance!
    Last edited by 711groove; 12-15-2009 at 06:35 PM.

  2. #2
    Tolls is offline Moderator
    Join Date
    Apr 2009
    Posts
    11,450
    Rep Power
    18

    Default

    First off, generally there's no need to extend JFrame. A JFrame is something that holds stuff, and all your extended thing is doing is adding stuff to the JFrame parent. Which implies it's not really a reasonable extension. However, since this happens a lot you may as well stay as you are.

    When designing your classes you need to sit back and think what fits naturally together. Simply picking out all the attributes and saying "I'll stick them in one class" isn't the right way to do it. Break things down by functionality.

    In this case you have a frame that holds other containers and labels and the like, and also has to deal with input from the user (that'll be the ActionListener). These are all part of the functionality of this frame, so breaking them out really makes no sense. However, you do have what might be called business logic in here. That is the guess() functionality.

    Look up MVC (Model, View, Controller). Your frame is the view, your guess() is the controller...not sure what your model would be, but I suspect the guess data itself (an int?).

    This is a simple case you have here, but may as well learn Good Practice from the start.

    Also, you'll want the guess checking done on a worker thread.

  3. #3
    711groove is offline Member
    Join Date
    Dec 2009
    Posts
    15
    Rep Power
    0

    Default

    okay, maybe the above example wasn't as applicable as I'd hoped... this is basically what I have in my application (except with comments instead of 100's of lines of code)

    PHP Code:
    //[[[imports]]]
    class myapp extends JFrame implements ActionListener{
    public static void main( String args[] ){
    	myapp show = new myapp();
    }
    public myapp(){
    	//[[[MASSIVE section of code to build the GUI and add actionlisteners(this)]]]
    }
    public void actionPerformed(ActionEvent e){
        if (e.getSource() == buttonConnect){establishConnection();}
        if (e.getSource() == buttonLookup){executeLookup();}
        if (e.getSource() == buttonImport){executeImport();}
        if (e.getSource() == buttonExport){executeExport();}
    }
    public void establishConnection(){
    	//[[[obvious]]]
    }
    public void executeLookup(){
    	//[[[lovely bit of code to select more specifically a single database row using gui]]]
    }
    public void executeImport(){
    	//[[[100 lines of retreiving values from database and populating gui elements with data]]]
    }
    public void executeExport(){
    	//[[[100 lines of retreiving every gui element's value and exporting to database]]]
    }
    //[[[200 lines of defining JTextFields/JComboBoxes/JCheckBoxes/JLabels/JPanels]]]
    }
    I mean, it works perfectly, I just feel like it's not the way things are supposed to be done, with all these gigantic methods all stuffed in one class.

    And I looked into MVC, and spent like an hour reading about MVVM and PAC and HVMC, but to put it frankly, I'm just way too new to java, and not totally familiar with all the programming concepts just yet, so a lot of it went over my head. Java's my first programming language, and while I'm good at looking at someone else's code and learning how to use what it does to meet my own needs, I don't always understand WHY it works, just HOW. Know what I mean? I only really picked up java in preparation for an intro programming course I take next semester, and discovered that it was easy enough to use to make myself a little application I can actually use in my daily life. I guess I just figured all the big hat hypnogyromethods and prograspatial-matrices would make sense later on. All I wanna do is take a really big drawer with 5 or 6 100lb methods and put each one in it's own drawer so the dresser doesn't break while I shuffle it all around.

    Please excuse the metaphor.
    Last edited by 711groove; 12-15-2009 at 07:20 PM.

  4. #4
    Tolls is offline Moderator
    Join Date
    Apr 2009
    Posts
    11,450
    Rep Power
    18

    Default

    OK, don't implement ActionListener like that. If you find yourself writing this:

    Java Code:
        if (e.getSource() == buttonConnect){establishConnection();}
        if (e.getSource() == buttonLookup){executeLookup();}
        if (e.getSource() == buttonImport){executeImport();}
        if (e.getSource() == buttonExport){executeExport();}
    it implies you've not broken things down correctly. Usually you would either have an anonymous class (that is define the listener where it is used) or, if there is a lot of code for the listener to process (bit bulky to do as an anonymous), have a named inner class that implements the listener.

    However, as I said above, you shouldn't be doing lots of processing in the form. That's not its job. The form is for displaying stuff, and informing the other layers that stuff has changed (ie buttons have been pushed).

    These other layers will do the work of interacting with the database (for example).

    I think you ought to run through the Swing tutorial at Sun. It will help you. Especially take note of the concurrency stuff.

  5. #5
    711groove is offline Member
    Join Date
    Dec 2009
    Posts
    15
    Rep Power
    0

    Default

    Quote Originally Posted by Tolls View Post
    ...Usually you would either have an anonymous class ...

    ...The form is for displaying stuff, and informing the other layers that stuff has changed (ie buttons have been pushed).

    These other layers will do the work ...

    ...Especially take note of the concurrency stuff.
    I'm reading and reading, and when I say I'm new to java, I mean I'm VERY new. I don't know what pretty much any of the stuff you said above means, or at least I didn't when I read it. I still don't get what you're saying by layers.

    I guess my misunderstandings are more conceptual. I see how concurrency and threads work, and I could probably implement them if told, but I wouldn't know why I was doing it, or when (if I was all on my own) to decide to implement it. It just doesn't make a whole lot of sense to me when the process goes:
    1. I want a program that does XYZ
    2. I write
    3. I have a program that does XYZ just like I want it to
    4. It's wrong, because QRS isn't JKL

    I hear experienced programmers say things like "Don't write your program as all one method". I think "Why not? My program only does one thing. Maybe it does it in little steps, but it's never going to do those steps in any order but the order it's written in."

    Maybe this is a lot to ask, but could you show me some kind of example of how you'd solve the problem my application tries to solve? A breakdown of each class you'd write and it's specific functionss etc.

    The problem: need a GUI with two text fields, two drop down boxes, and a checkbox for each. User must be able to select a row from a database with which to populate the fields with data. The user then must be able to edit the data, and export each field selected by a checkbox back to the database.

    Starting from absolute scratch, what's the 'correct' way to design something like that? The answer just escapes me.

    It sounds like I'm just complaining, but I really am trying to learn. I do want to learn the answers, and I want to know what I'm doing wrong, but the java tutorials skip around ALOT between topics, and tend to be either really specific or really broad, and so even after reading chapters and chapters of information, the real concepts behind the api classes and programming techniques escape me.

    At the very least, if you could point me to something concrete that explains the way classes and methods are supposed to work together, I'd very much appreciate it.

  6. #6
    Tolls is offline Moderator
    Join Date
    Apr 2009
    Posts
    11,450
    Rep Power
    18

    Default

    Quote Originally Posted by 711groove View Post
    I'm reading and reading, and when I say I'm new to java, I mean I'm VERY new. I don't know what pretty much any of the stuff you said above means, or at least I didn't when I read it. I still don't get what you're saying by layers.
    Run through the tutorial I linked to above. It'll help you understand Swing, which'll help you understand some of the concepts I mentioned. In fact, you probably ought to start from the beginning of the tutorials. It would give you a better grounding. GUIs are not the place to start learning Java.

    Quote Originally Posted by 711groove View Post
    I hear experienced programmers say things like "Don't write your program as all one method". I think "Why not? My program only does one thing. Maybe it does it in little steps, but it's never going to do those steps in any order but the order it's written in."
    Because as soon as you discover the inevitable bug in your code, your nice block of logic suddenly requires another if-else in the middle somewhere, to fix that "little step" that doesn't work...but in order to do that you have to now shift things about because it is tied to the other little steps around it. It'll happen, so if you learn to code in a way that lessens the work involved when you invariably have to fix it all the better.

    Quote Originally Posted by 711groove View Post
    Maybe this is a lot to ask, but could you show me some kind of example of how you'd solve the problem my application tries to solve? A breakdown of each class you'd write and it's specific functionss etc.

    The problem: need a GUI with two text fields, two drop down boxes, and a checkbox for each. User must be able to select a row from a database with which to populate the fields with data. The user then must be able to edit the data, and export each field selected by a checkbox back to the database.

    Starting from absolute scratch, what's the 'correct' way to design something like that? The answer just escapes me.
    Break it down. I'd start from the db end, ignoring the GUI for now. Write a data class that contains the fields that can be saved (this is your model). Write a class for saving one of these to the database, and retrieving one (so two methods). Get that to work with a test class around it...a dummy class that has a main() method, and simply tries to get data from the db and save it.

    Quote Originally Posted by 711groove View Post
    It sounds like I'm just complaining, but I really am trying to learn. I do want to learn the answers, and I want to know what I'm doing wrong, but the java tutorials skip around ALOT between topics, and tend to be either really specific or really broad, and so even after reading chapters and chapters of information, the real concepts behind the api classes and programming techniques escape me.

    At the very least, if you could point me to something concrete that explains the way classes and methods are supposed to work together, I'd very much appreciate it.
    Stick to the Sun tutorials. Don't try any others until you're happy you have grasped what the Sun ones are saying.

  7. #7
    711groove is offline Member
    Join Date
    Dec 2009
    Posts
    15
    Rep Power
    0

    Default

    Quote Originally Posted by Tolls View Post
    Run through the tutorial I linked to above. It'll help you understand Swing
    I think I've got a pretty good handle on swing, or at least its implementation I read through most of that tutorial over the past two weeks as I've picked up the language. I definitely have a firm grip on "getting started with swing", i skipped "swing with netbeans" as I wanted to learn to code GUI's myself, I know how to use all manner of swing components, mostly thanks to class documentation and in-depth tutorials on nother websites, I'm pretty good at getting different kinds of layouts to do what I want them to, I've successfully changed the look and feel of my programs, and written ones that work well in every system L&F I've tried, and while I guess I'm doing my event listeners "wrong", they at least perform the function I intend them to. Like I said above, I can write a working program just fine, it just doesn't meet meet whatever standards are in place, because nothing I've read so far has, i guess, introduced me to them completely.

    The action listener I have above was written to make one simple method I can use for all possible events; it detects what called the event (and thereby what will need to be done in response), and then does it. Maybe the logic for running the specified methods isn't ideal (suppose there's a single, simple doWhatTheButtonSaystoDo() method I've never heard of, but my implementation is basically a direct copy for sun's own actionlistener tutorial:
    java.sun.com/docs/books/tutorial/uiswing/events/actionlistener.html
    (middle of the page)

    Quote Originally Posted by Tolls View Post
    ...you have to now shift things about because it is tied to the other little steps around it. ...
    That's something I don't get at all. My two biggest methods are Import and Export. All they do is individually go through each user input/output field and update the data, one field at a time. No step in those methods has anything to do with any other, it's a completely separate list of events like:
    1. get user input
    2. run query using input
    3. fetch "a" from result and place in field "1"
    4. fetch "b" from result and place in field "1"
    ...
    49. fetch "aq" from result and place in field "47"
    50. fetch "ar" from result and place in field "48"
    51. display message "there were no errors importing/exporting your data"

    Maybe that's what's wrong? It's just a big sequential list of instructions?

    Quote Originally Posted by Tolls View Post
    Break it down. I'd start from the db end, ignoring the GUI for now. Write a data class that contains the fields that can be saved (this is your model). Write a class for saving one of these to the database, and retrieving one (so two methods). Get that to work with a test class around it...a dummy class that has a main() method, and simply tries to get data from the db and save it.
    that worries me. I have a console version of this program that also works perfectly... the whole point of coding it in java is to have the GUI. I need to know right from the top how the GUI will be implemented, as that's where all the information has to come from; closely inter-related swing components (each database field needs a label, checkbox, and either a text field or a combo box). And the whole point of my thread here in the first place is to found out how I can "break" the program down into multiple classes withing breaking the functionality. Every single one of my methods relies on utilizing the swing components, because the entire program IS the swing components. This is a database editing program. I suppose I could (like I said earlier) arbitrarily break it down so that I have a single class for the gui elements that gets the values for the gui from strings, arrays, and booleans, but that feels like a complete waste of code when myComboBox.getSelectedIndex() returns the value of the array, myTextfield.getText() returns a string, and myCheckBox.isSelected() returns a boolean. My biggest fear is having to define or declare a field more than once, as I literally have closer to 250 than 200 UI components. I want the most efficient code possible, I don't want a lot of cruft around the edges obfuscating the real code.

    "Write a class for saving one of these to the database, and retrieving one (so two methods)." With JDBC and the mysql driver, you need to wrap every single database/resultset access in a try/catch, so the way I see it, it's better to fit every import action into one try/catch and the same for every export action, as, like I said, it's all-or-nothing... you're never going to just retrieve or update one single field.

    Quote Originally Posted by Tolls View Post
    Stick to the Sun tutorials. Don't try any others until you're happy you have grasped what the Sun ones are saying.
    There's something funny going on with those... I always feel like I'm not actually learning anything new when I read most of em... it's either re-iteration, or a brief overview of something I have no understanding of the underlying concepts, or something I can't ever see as being relevant to me. For example concurrency. I get why you'd want to have a threaded application when you have a general purpose program that runs all sorts of tasks at once. But with my program, it's got two major functions, an all-or-nothing import and an all-or-nothing export. Running these two event concurrently (or two exports concurrently) on the database might seriously screw up the data. In fact it's a good thing that the program requires the user to wait until the current operation is finished before continuing (at least the way I see it) and I don't see how implementing threads that utilize things like "wait for" would change the program for the better.

    Of course that's not to say I don't want to understand any of the things I just said I don't understand, if you can explain it, I certainly do want to hear it. I know I sound like I'm complaining a lot, but I just know what it is that I want to know, and know that it hasn't been explained sufficiently to me yet (by Sun, by you, by experience, by anyone else). If any of my assumptions here are wrong (like about the way I've written my program), PLEASE tell me what my mistake is! I /am/ here to learn!

  8. #8
    JosAH's Avatar
    JosAH is offline Moderator
    Join Date
    Sep 2008
    Location
    Voorschoten, the Netherlands
    Posts
    13,003
    Blog Entries
    7
    Rep Power
    20

    Default

    I have a lot of rules of thumb and one of them is: "a method should be explained in just one sentence (with enough detail in the sentence)". If somebody goes: "this method connects to a database and then displays a progress bar, loads all the stuff, but if a button is pressed it stops loading and draws a nice picture except if it's running on a small screen, it mails the root user of the system instead and if there's no mail available it dumps a log to a file". Those methods beg to be split into several methods; methods should do one thing and they should do them well. Another rule of thumb of mine is: "a method should be displayable on one VT100 screen". If a method can be used for wallpaper it also begs to be split. Both rules should help you a bit in writing readable methods.

    kind regards,

    Jos

  9. #9
    711groove is offline Member
    Join Date
    Dec 2009
    Posts
    15
    Rep Power
    0

    Default

    Quote Originally Posted by JosAH View Post
    "a method should be displayable on one VT100 screen".
    Unless you're using grouplayout.
    Java Code:
    GroupLayout layoutname = new GroupLayout(panelname);panelname.setLayout(layoutname);
    layoutname.setHorizontalGroup(layoutname.createSequentialGroup().addGroup(layoutname.createParallelGroup().addComponent(fdsh).addComponent(dfghgh).addComponent(gfhdfghsh).addComponent(dfsgdsfgd).addComponent(dsfgdsthdth).addComponent(hfk).addComponent(gfkh).addComponent(iuo;).addComponent(wqrf).addComponent(zxcvasd).addComponent(gfnh).addComponent(fghmuy).addComponent(gtvt).addComponent(hyjukim).addComponent(bhynjumki,ju).addComponent(nhygt).addComponent(jm).addComponent(nhbgvf).addComponent(tgvfr).addComponent(fgvbh)).addGroup(layoutname.createParallelGroup().addComponent(sghsdgh).addComponent(dhdghdg).addComponent(sghsghdfg).addComponent(sfgsdfgdfs).addComponent(dghdghdfghk).addComponent(gfk).addComponent(fgkh).addComponent(uluioluol).addComponent(wef).addComponent(vasdv).addComponent(gfnh).addComponent(gfhjm).addComponent(hyjuunhyb).addComponent(junhybgtgvt).addComponent(rfgt).addComponent(hynju).addComponent(vgbhn).addComponent(rtbhynj).addComponent(jnunyh).addComponent(rgvtbhy)).addGroup(layoutname.createParallelGroup().addComponent(gsjsfgj).addComponent(dfgdheth).addComponent(hdghdfghdfgh).addComponent(sgsrgsf).addComponent(sdrgdfhdh).addComponent(gfk).addComponent(gfkh).addComponent(uiol).addComponent(rgwrfwqerf).addComponent(asdv).addComponent(ghn).addComponent(dyj).addComponent(hybgtvgt).addComponent(hyjuki).addComponent(gtv).addComponent(kmi,i).addComponent(c).addComponent(ukm).addComponent(bhnyju).addComponent(yhbgtvfrc)).addGroup(layoutname.createParallelGroup().addComponent(jdfhgshj).addComponent(jdfghdf).addComponent(fghfghtyh).addComponent(sagsrg).addComponent(dgshsdfgrgdsf).addComponent(fkh).addComponent(gfk).addComponent(uiol).addComponent(rf).addComponent(sdvsd).addComponent(gn).addComponent(cb).addComponent(gtfrtghyj).addComponent(juhygt).addComponent(imjunhyb).addComponent(mjunhy).addComponent(nhbgvf).addComponent(junhyb).addComponent(cgvr).addComponent(umyhgtfr)).addGroup(layoutname.createParallelGroup().addComponent(sgjfhjdfh).addComponent(dfjdfhjdf).addComponent(dfyjyjyjyk).addComponent(esfserfs).addComponent(dfhdthd).addComponent(gfkghk).addComponent(hgk).addComponent(iuoliu).addComponent(fw).addComponent(vsd).addComponent(ghn).addComponent(rg).addComponent(gtthyujhy).addComponent(gthyjukibhynjumki).addComponent(bhynjumk).addComponent(bgtvfr).addComponent(bhnjm).addComponent(gtv).addComponent(yhbgtvfrce).addComponent(ynjunrgvtbhnyj)).addGroup(layoutname.createParallelGroup().addComponent(jdfhjs).addComponent(hfhfgjfj).addComponent(uioull).addComponent(fsdfsefsdf).addComponent(dhdghsd).addComponent(khgkhg).addComponent(kjhg).addComponent(oluio).addComponent(wrfw).addComponent(vsd).addComponent(nghn).addComponent(gh).addComponent(ghytgfhgkjujnhyb).addComponent(,kimjunhybgtvjunhy).addComponent(gtv).addComponent(c).addComponent(vg).addComponent(frcde).addComponent(gvfrcde).addComponent(cfcvgbh)).addGroup(layoutname.createParallelGroup().addComponent(gjfgjdfhj).addComponent(ghgfhfg).addComponent(iolloiul).addComponent(dfaefef).addComponent(hdfhdf).addComponent(hgjhg).addComponent(hgmgf).addComponent(liuo).addComponent(wrf).addComponent(vasd).addComponent(ghnfnhhh).addComponent(jerwhh).addComponent(tsfhgfdsdf).addComponent(gthynju).addComponent(hyjukimmjunhyb).addComponent(vg).addComponent(vfc).addComponent(xwe).addComponent(uhybt).addComponent(gfvrtbhynj)).addGroup(layoutname.createParallelGroup().addComponent(sgjfsgj).addComponent(kgdhjksfjsf).addComponent(hjlihjlil).addComponent(adeawdawdae).addComponent(hdfsh).addComponent(utryury).addComponent(uyru).addComponent(liuo).addComponent(wqef).addComponent(vasdv).addComponent(gfnhf).addComponent(yju).addComponent(fghdsfgj).addComponent(kim).addComponent(junhybgt).addComponent(bhnjm).addComponent(cefvgtbhnjmnhbg).addComponent(fcrvgtbh).addComponent(ecfgvbthynj).addComponent(bgv)).addGroup(layoutname.createParallelGroup().addComponent(dfghjsgfjf).addComponent(jghkjdh).addComponent(hjilhjlhjil).addComponent(khjtghs).addComponent(dsfhd).addComponent(yuyurty).addComponent(yuyu).addComponent(luol).addComponent(wqef).addComponent(sdv).addComponent(gf).addComponent(dgh).addComponent(dsfgf).addComponent(dgfh).addComponent(fhgff).addComponent(rghrherhetrgfhuynhbg).addComponent(rhfvd).addComponent(grjrghnjkmi).addComponent(erjcdex).addComponent(erwh)).addGroup(layoutname.createParallelGroup().addComponent(jhfj).addComponent(fhjfhjfgh).addComponent(hjklhjklk).addComponent(lhjklhjklhi).addComponent(fshdsfh).addComponent(hyrju).addComponent(yuytru).addComponent(uioliuol).addComponent(ew).addComponent(sadvs).addComponent(fnhfh).addComponent(kiy).addComponent(dfs).addComponent(rgh).addComponent(yj).addComponent(yiuo).addComponent(i).addComponent(sdh,lokimjunhyb).addComponent(trheryjgvf).addComponent(ethr)));
    layoutname.setVerticalGroup(layoutname.createSequentialGroup().addGroup(layoutname.createParallelGroup().addComponent(fdsh).addComponent(sghsdgh).addComponent(gsjsfgj).addComponent(jdfhgshj).addComponent(sgjfhjdfh).addComponent(jdfhjs).addComponent(gjfgjdfhj).addComponent(sgjfsgj).addComponent(dfghjsgfjf).addComponent(jhfj)).addGroup(layoutname.createParallelGroup().addComponent(dfghgh).addComponent(dhdghdg).addComponent(dfgdheth).addComponent(jdfghdf).addComponent(dfjdfhjdf).addComponent(hfhfgjfj).addComponent(ghgfhfg).addComponent(kgdhjksfjsf).addComponent(jghkjdh).addComponent(fhjfhjfgh)).addGroup(layoutname.createParallelGroup().addComponent(gfhdfghsh).addComponent(sghsghdfg).addComponent(hdghdfghdfgh).addComponent(fghfghtyh).addComponent(dfyjyjyjyk).addComponent(uioull).addComponent(iolloiul).addComponent(hjlihjlil).addComponent(hjilhjlhjil).addComponent(hjklhjklk)).addGroup(layoutname.createParallelGroup().addComponent(dfsgdsfgd).addComponent(sfgsdfgdfs).addComponent(sgsrgsf).addComponent(sagsrg).addComponent(esfserfs).addComponent(fsdfsefsdf).addComponent(dfaefef).addComponent(adeawdawdae).addComponent(khjtghs).addComponent(lhjklhjklhi)).addGroup(layoutname.createParallelGroup().addComponent(dsfgdsthdth).addComponent(dghdghdfghk).addComponent(sdrgdfhdh).addComponent(dgshsdfgrgdsf).addComponent(dfhdthd).addComponent(dhdghsd).addComponent(hdfhdf).addComponent(hdfsh).addComponent(dsfhd).addComponent(fshdsfh)).addGroup(layoutname.createParallelGroup().addComponent(hfk).addComponent(gfk).addComponent(gfk).addComponent(fkh).addComponent(gfkghk).addComponent(khgkhg).addComponent(hgjhg).addComponent(utryury).addComponent(yuyurty).addComponent(hyrju)).addGroup(layoutname.createParallelGroup().addComponent(gfkh).addComponent(fgkh).addComponent(gfkh).addComponent(gfk).addComponent(hgk).addComponent(kjhg).addComponent(hgmgf).addComponent(uyru).addComponent(yuyu).addComponent(yuytru)).addGroup(layoutname.createParallelGroup().addComponent(iuo;).addComponent(uluioluol).addComponent(uiol).addComponent(uiol).addComponent(iuoliu).addComponent(oluio).addComponent(liuo).addComponent(liuo).addComponent(luol).addComponent(uioliuol)).addGroup(layoutname.createParallelGroup().addComponent(wqrf).addComponent(wef).addComponent(rgwrfwqerf).addComponent(rf).addComponent(fw).addComponent(wrfw).addComponent(wrf).addComponent(wqef).addComponent(wqef).addComponent(ew)).addGroup(layoutname.createParallelGroup().addComponent(zxcvasd).addComponent(vasdv).addComponent(asdv).addComponent(sdvsd).addComponent(vsd).addComponent(vsd).addComponent(vasd).addComponent(vasdv).addComponent(sdv).addComponent(sadvs)).addGroup(layoutname.createParallelGroup().addComponent(gfnh).addComponent(gfnh).addComponent(ghn).addComponent(gn).addComponent(ghn).addComponent(nghn).addComponent(ghnfnhhh).addComponent(gfnhf).addComponent(gf).addComponent(fnhfh)).addGroup(layoutname.createParallelGroup().addComponent(fghmuy).addComponent(gfhjm).addComponent(dyj).addComponent(cb).addComponent(rg).addComponent(gh).addComponent(jerwhh).addComponent(yju).addComponent(dgh).addComponent(kiy)).addGroup(layoutname.createParallelGroup().addComponent(gtvt).addComponent(hyjuunhyb).addComponent(hybgtvgt).addComponent(gtfrtghyj).addComponent(gtthyujhy).addComponent(ghytgfhgkjujnhyb).addComponent(tsfhgfdsdf).addComponent(fghdsfgj).addComponent(dsfgf).addComponent(dfs)).addGroup(layoutname.createParallelGroup().addComponent(hyjukim).addComponent(junhybgtgvt).addComponent(hyjuki).addComponent(juhygt).addComponent(gthyjukibhynjumki).addComponent(,kimjunhybgtvjunhy).addComponent(gthynju).addComponent(kim).addComponent(dgfh).addComponent(rgh)).addGroup(layoutname.createParallelGroup().addComponent(bhynjumki,ju).addComponent(rfgt).addComponent(gtv).addComponent(imjunhyb).addComponent(bhynjumk).addComponent(gtv).addComponent(hyjukimmjunhyb).addComponent(junhybgt).addComponent(fhgff).addComponent(yj)).addGroup(layoutname.createParallelGroup().addComponent(nhygt).addComponent(hynju).addComponent(kmi,i).addComponent(mjunhy).addComponent(bgtvfr).addComponent(c).addComponent(vg).addComponent(bhnjm).addComponent(rghrherhetrgfhuynhbg).addComponent(yiuo)).addGroup(layoutname.createParallelGroup().addComponent(jm).addComponent(vgbhn).addComponent(c).addComponent(nhbgvf).addComponent(bhnjm).addComponent(vg).addComponent(vfc).addComponent(cefvgtbhnjmnhbg).addComponent(rhfvd).addComponent(i)).addGroup(layoutname.createParallelGroup().addComponent(nhbgvf).addComponent(rtbhynj).addComponent(ukm).addComponent(junhyb).addComponent(gtv).addComponent(frcde).addComponent(xwe).addComponent(fcrvgtbh).addComponent(grjrghnjkmi).addComponent(sdh,lokimjunhyb)).addGroup(layoutname.createParallelGroup().addComponent(tgvfr).addComponent(jnunyh).addComponent(bhnyju).addComponent(cgvr).addComponent(yhbgtvfrce).addComponent(gvfrcde).addComponent(uhybt).addComponent(ecfgvbthynj).addComponent(erjcdex).addComponent(trheryjgvf)).addGroup(layoutname.createParallelGroup().addComponent(fgvbh).addComponent(rgvtbhy).addComponent(yhbgtvfrc).addComponent(umyhgtfr).addComponent(ynjunrgvtbhnyj).addComponent(cfcvgbh).addComponent(gfvrtbhynj).addComponent(bgv).addComponent(erwh).addComponent(ethr))); // congrats if you scrolled all the way over here.  I counted 10007 characters.  Way more than a VT100 screen fits (24*70)
    ""a method should be explained in just one sentence (with enough detail in the sentence)""

    Okay, that actually kind of makes sense. Makes me think of a method as a single specific function of a program rather than a list of operations to be performed. I get how to write a decent method now, I think, but I'm still a little unclear as to why it's important. Does it effect the running of the application at all, or is does it only matter when you're looking at the source code?

    And I still want to know what better way there is to use listeners for 4 buttons that each should perform a specific task.



    EDIT:
    just re-read:
    Quote Originally Posted by Tolls
    Write a data class that contains the fields that can be saved (this is your model).
    Would this class just be strings and ints, or jtextfields and jcomboboxes? I'm not toally clear on how a data class interacts with gui established in another class.
    Last edited by 711groove; 12-17-2009 at 12:36 AM.

  10. #10
    Tolls is offline Moderator
    Join Date
    Apr 2009
    Posts
    11,450
    Rep Power
    18

    Default

    Quote Originally Posted by 711groove View Post
    Like I said above, I can write a working program just fine, it just doesn't meet meet whatever standards are in place, because nothing I've read so far has, i guess, introduced me to them completely.
    Ah, OK. And kudos on wanting to write the GUI by hand...I have an aversion to GUI builders. :) But the concurrency bit is probably the hold up then (which does tie in with the whole layers concept of writing stuff). I'll bring that up below.

    Quote Originally Posted by 711groove View Post
    The action listener I have above was written to make one simple method I can use for all possible events; it detects what called the event (and thereby what will need to be done in response), and then does it. Maybe the logic for running the specified methods isn't ideal (suppose there's a single, simple doWhatTheButtonSaystoDo() method I've never heard of, but my implementation is basically a direct copy for sun's own actionlistener tutorial:
    java.sun.com/docs/books/tutorial/uiswing/events/actionlistener.html
    (middle of the page)
    Well, not quite. I suppose they could have done a slightly better job, but that example was modelling something that does one thing, which is counting button presses. The introduction to event listeners one gives a better overview of how listeners are written. The How-to's are really basic descriptions of the API.

    Quote Originally Posted by 711groove View Post
    That's something I don't get at all. My two biggest methods are Import and Export. All they do is individually go through each user input/output field and update the data, one field at a time. No step in those methods has anything to do with any other, it's a completely separate list of events like:
    1. get user input
    2. run query using input
    3. fetch "a" from result and place in field "1"
    4. fetch "b" from result and place in field "1"
    ...
    49. fetch "aq" from result and place in field "47"
    50. fetch "ar" from result and place in field "48"
    51. display message "there were no errors importing/exporting your data"

    Maybe that's what's wrong? It's just a big sequential list of instructions?
    Sort of. However, that actually breaks down to:
    1. Get user input - you'd build your query object here, which has whatever attributes can be queried, based on the GUI fields.
    1a. Hand that query object over to your backend - that is the non-GUI logic (db access layer).
    2. Run query - this involves getting connections, building the statement, getting the resultset and closing. In which you would call...
    3. Build result object - which involves doing the various gets on the result set.
    3a. Return that to the GUI layer.
    5. GUI displays it.

    Quote Originally Posted by 711groove View Post
    that worries me. I have a console version of this program that also works perfectly... the whole point of coding it in java is to have the GUI. I need to know right from the top how the GUI will be implemented, as that's where all the information has to come from; closely inter-related swing components (each database field needs a label, checkbox, and either a text field or a combo box). And the whole point of my thread here in the first place is to found out how I can "break" the program down into multiple classes withing breaking the functionality. Every single one of my methods relies on utilizing the swing components, because the entire program IS the swing components. This is a database editing program. I suppose I could (like I said earlier) arbitrarily break it down so that I have a single class for the gui elements that gets the values for the gui from strings, arrays, and booleans, but that feels like a complete waste of code when myComboBox.getSelectedIndex() returns the value of the array, myTextfield.getText() returns a string, and myCheckBox.isSelected() returns a boolean. My biggest fear is having to define or declare a field more than once, as I literally have closer to 250 than 200 UI components. I want the most efficient code possible, I don't want a lot of cruft around the edges obfuscating the real code.
    But your program isn't the GUI. Your program accesses the database, retrieveing and saving data from/to it. This could have a web front end...a console front end...be a web service. The GUI is simply the way you have chosen to present this functionality. This is where the model comes in. Once you have a model of the data you are passing around, which presumably represents something, you can then build the GUI based on that.

    You have lots of fields...each of those, presumably, is to be sent to the db for saving? I don't know. But you really do need to think of this in terms of what it is you're saving...at a higher level than you are at the moment. Don't think in terms of Strings and ints and booleans, think in terms of (and I'm making up a model here) employees and departments.

    Quote Originally Posted by 711groove View Post
    "Write a class for saving one of these to the database, and retrieving one (so two methods)." With JDBC and the mysql driver, you need to wrap every single database/resultset access in a try/catch, so the way I see it, it's better to fit every import action into one try/catch and the same for every export action, as, like I said, it's all-or-nothing... you're never going to just retrieve or update one single field.
    Think objects. Think employees. Don't think fields.

    Quote Originally Posted by 711groove View Post
    For example concurrency. I get why you'd want to have a threaded application when you have a general purpose program that runs all sorts of tasks at once. But with my program, it's got two major functions, an all-or-nothing import and an all-or-nothing export. Running these two event concurrently (or two exports concurrently) on the database might seriously screw up the data. In fact it's a good thing that the program requires the user to wait until the current operation is finished before continuing (at least the way I see it) and I don't see how implementing threads that utilize things like "wait for" would change the program for the better.
    For Swing concurrency is important. It's because all Swing events occur on the EDT. This is the Swing thread, if you like. You should not do "business" logic on this thread. That is db access, data crunching, anything, in fact, that isn't simply about changing the value of a field, or painting. These things should be done in Swing Worker threads (which the Swing concurrency tutorial explains)...which themselves will use your db code.

    Quote Originally Posted by 711groove View Post
    Of course that's not to say I don't want to understand any of the things I just said I don't understand, if you can explain it, I certainly do want to hear it. I know I sound like I'm complaining a lot, but I just know what it is that I want to know, and know that it hasn't been explained sufficiently to me yet (by Sun, by you, by experience, by anyone else). If any of my assumptions here are wrong (like about the way I've written my program), PLEASE tell me what my mistake is! I /am/ here to learn!
    Coding in Java (and all OO languages) is all about modelling the problem. Presumably there's already been some modelling done to produce your database tables, so use that as a start point.

    The second thing is to think in terms of interfaces. That is the actions that need to be performed by certain bits of the code. So you'll probably have an interface for saving/retrieving class A of your model. And then you can do an implementation of that interface. This may seem strange, but it does help to break the problem down, and define the "contract" between parts of your system. The interfaces will also act as the interface between your layers (GUI, "business", db).

    Bit of a long winded one, I'm afraid, but hope that helps a bit.

  11. #11
    JosAH's Avatar
    JosAH is offline Moderator
    Join Date
    Sep 2008
    Location
    Voorschoten, the Netherlands
    Posts
    13,003
    Blog Entries
    7
    Rep Power
    20

    Default

    Quote Originally Posted by 711groove View Post
    Unless you're using grouplayout.
    That one (the VT100 analogy) was a bit tongue in cheek but if you look at beginners' code, they hardly understand their own code, keep on knitting complicated control flow together and when they don't get lost in their own loops, ifs, and what have you they end up with one (or just a few) huge methods; not just writng working code is important but also maintenance is important (the original author can be the maintenance programmer).

    Wallpaper sized methods are extremely difficult to understand and are (therefore) hardly maintainable; a method should do one thing and it should do it well; that can't take many lines of code and hence my little analogy ;-)

    kind regards,

    Jos

  12. #12
    711groove is offline Member
    Join Date
    Dec 2009
    Posts
    15
    Rep Power
    0

    Default

    Quote Originally Posted by Tolls View Post
    Sort of. However, that actually breaks down to:
    1. Get user input - you'd build your query object here, which has whatever attributes can be queried, based on the GUI fields.
    1a. Hand that query object over to your backend - that is the non-GUI logic (db access layer).
    2. Run query - this involves getting connections, building the statement, getting the resultset and closing. In which you would call...
    3. Build result object - which involves doing the various gets on the result set.
    3a. Return that to the GUI layer.
    5. GUI displays it.

    But your program isn't the GUI. Your program accesses the database, retrieveing and saving data from/to it. This could have a web front end...a console front end...be a web service. The GUI is simply the way you have chosen to present this functionality. This is where the model comes in. Once you have a model of the data you are passing around, which presumably represents something, you can then build the GUI based on that.

    You have lots of fields...each of those, presumably, is to be sent to the db for saving? I don't know. But you really do need to think of this in terms of what it is you're saving...at a higher level than you are at the moment. Don't think in terms of Strings and ints and booleans, think in terms of (and I'm making up a model here) employees and departments.

    Think objects. Think employees. Don't think fields.
    Okay tied these 3 quotes together because they all are kind on the same topic... I think I get what you're saying. Write the logic (db acess), THEN worry about user interface. That's 100% the opposite of what I did.

    the steps I followed to write my app:
    1. hand-draw a layout of the GUI with all kinds of boxes drawn on it representing component grids and gui subpanels for layout purposes
    2. start defining gui elements
    3. write GL Helper ( mediafire.com/?mnnkynojm2y )
    4. complete the GUI
    5. add actionlistener and then write each sub-method of it (db access).

    I guess that is kind of backwards.

    But you talk a lot about thinking in terms of the model, but I don't quite see how to do that for what I'm trying to accomplish. the exact specific DB interaction is as follows:
    -connect
    -run a query from user input:
    SELECT entry, name FROM table WHERE name LIKE '%[[user input]]%'
    -display matches to user
    -user selects a match to edit
    -run a query based on user choice of 'employee':
    SELECT * FROM table WHERE entry=[[user's choice]]

    and from there, it's just

    NAME ENTRY A B C D E F G H I J K L M N O P

    of a single db row, and the user can then change whatever field they want, but only for that row. So there's no methods for changing rows, or finding similar rows, or comparing rows, or anything like that. Beyond what I described above, there's basically 2 methods, import and export (take field from resultset, display) (take input from field, execute query:
    UPDATE table SET [[field]]=[[input]] WHERE ENTRY=[[constant defined earlier]]

    Quote Originally Posted by Tolls View Post
    For Swing concurrency is important. It's because all Swing events occur on the EDT. This is the Swing thread, if you like. You should not do "business" logic on this thread. That is db access, data crunching, anything, in fact, that isn't simply about changing the value of a field, or painting. These things should be done in Swing Worker threads (which the Swing concurrency tutorial explains)...which themselves will use your db code.
    This is exactly the kind of specifics I was hoping for when you first said "break it down" (and instead of doing this, I just put on some MC Hammer).

    Quote Originally Posted by Tolls View Post
    Coding in Java (and all OO languages) is all about modelling the problem. Presumably there's already been some modelling done to produce your database tables, so use that as a start point.
    Well no, not to "produce your database tables". This app is based 100% on the assumption that those tables already exist, and already have tons of data. If you have a blank DB, this program is totally worthless, because it's meant only to EDIT specific fields of EXISITNG entries. So all this app needs to do in terms on db acess (like I said above) is connect and query. And there's only really 4 kinds of queries (minus some minor additions like "create temp table", "copy row from table to temp table" "copy row from temp table to table' "delete temp table" (just to be sure that if export fails, you don't have a half completed export specifying that the employee is made of steel and weighs 2500 kg, instead of saying the car is made of steel and weighs 2500 kg, for a totally irrelevant example)

    Quote Originally Posted by Tolls View Post
    The second thing is to think in terms of interfaces. That is the actions that need to be performed by certain bits of the code. So you'll probably have an interface for saving/retrieving class A of your model. And then you can do an implementation of that interface. This may seem strange, but it does help to break the problem down, and define the "contract" between parts of your system. The interfaces will also act as the interface between your layers (GUI, "business", db).

    Bit of a long winded one, I'm afraid, but hope that helps a bit.
    It does help! But there are more questions to come.

    Even if I did write it from the ground up again (starting with the 'logic' and db access, when I started to write the gui to rest on top of the logic, does that belong to the same class (this is where we get into java specifics) as the logic? Or does the gui get its own class? And how do they interface if it is it's own class? I think that's what my original question was before I ever actually started learning anything. Everything I've read on sun's tutorials about interfacing between classes seems to show that all fields (sorry, the employee) is defined distinctly in each class, and retrieved by set/get methods (whereas I'd much rather prefer to use something like import.all.fields.from.my.other.class(), otherclass.field11 = something. that seems to make more sense.

    In any case, thank you both for your input to this point! At this point I genuinely feel like I've learned something about program design, so I'm happy!

  13. #13
    Tolls is offline Moderator
    Join Date
    Apr 2009
    Posts
    11,450
    Rep Power
    18

    Default

    Quote Originally Posted by 711groove View Post
    But you talk a lot about thinking in terms of the model, but I don't quite see how to do that for what I'm trying to accomplish. the exact specific DB interaction is as follows:
    -connect
    -run a query from user input:
    SELECT entry, name FROM table WHERE name LIKE '%[[user input]]%'
    -display matches to user
    -user selects a match to edit
    -run a query based on user choice of 'employee':
    SELECT * FROM table WHERE entry=[[user's choice]]

    and from there, it's just

    NAME ENTRY A B C D E F G H I J K L M N O P

    of a single db row, and the user can then change whatever field they want, but only for that row. So there's no methods for changing rows, or finding similar rows, or comparing rows, or anything like that. Beyond what I described above, there's basically 2 methods, import and export (take field from resultset, display) (take input from field, execute query:
    UPDATE table SET [[field]]=[[input]] WHERE ENTRY=[[constant defined earlier]]
    But that's a model. That table represents something...that row represents something, unless the db is just a mass of mush. The user does a search, passing a search object to the db layer. The database layer returns a List of SearchResult objects, which have a name and entry attributes. These are handed to the GUI for display on the screen. The user selects one. The db layer returns a <table-whatever> object, which has attributes for name, entry, a-p. This is handed over to the display layer for display.

    The db layer therefore doesn't care about how the stuff it returns is displayed. All it does is take search criteria and return stuff. Two methods, one for getting a List of stuff, and the other for getting a single instance of stuff. And a third for saving stuff, of course.

    Quote Originally Posted by 711groove View Post
    Well no, not to "produce your database tables". This app is based 100% on the assumption that those tables already exist, and already have tons of data. If you have a blank DB, this program is totally worthless, because it's meant only to EDIT specific fields of EXISITNG entries. So all this app needs to do in terms on db acess (like I said above) is connect and query. And there's only really 4 kinds of queries (minus some minor additions like "create temp table", "copy row from table to temp table" "copy row from temp table to table' "delete temp table" (just to be sure that if export fails, you don't have a half completed export specifying that the employee is made of steel and weighs 2500 kg, instead of saying the car is made of steel and weighs 2500 kg, for a totally irrelevant example)
    Well, someone must have modelled the tables. As I say, they're just a start point. You may not need everything in them...just cull the attributes you don't require from the model. Remember I have no idea of exactly what it is you're doing.

    Quote Originally Posted by 711groove View Post
    It does help! But there are more questions to come.

    Even if I did write it from the ground up again (starting with the 'logic' and db access, when I started to write the gui to rest on top of the logic, does that belong to the same class (this is where we get into java specifics) as the logic? Or does the gui get its own class? And how do they interface if it is it's own class? I think that's what my original question was before I ever actually started learning anything. Everything I've read on sun's tutorials about interfacing between classes seems to show that all fields (sorry, the employee) is defined distinctly in each class, and retrieved by set/get methods (whereas I'd much rather prefer to use something like import.all.fields.from.my.other.class(), otherclass.field11 = something. that seems to make more sense.

    In any case, thank you both for your input to this point! At this point I genuinely feel like I've learned something about program design, so I'm happy!
    If you write them as layers (a separate package would help), you'll have lots of classes. That's a Good Thing. Make the model (in a model package), that's the simple bit. Say 2 classes, maybe more...I don't know what you;re working with, so can't say. Make the db stuff in a db package, probably just the single class containing methods for searching and selecting and saving. And then a gui package containing however many classes needed to do that.

    And I'm off home...:)

  14. #14
    711groove is offline Member
    Join Date
    Dec 2009
    Posts
    15
    Rep Power
    0

    Default

    Quote Originally Posted by Tolls View Post
    a mass of mush
    You know what? I think I'm starting to really understand this, and how it affects how my app should be designed. What I was doing was just stirring everything into one big pot because that's what I know how to do, and it was easy. But you're right, it is a big mess.

    Compartmentalization, I think is what you're trying to illustrate. All in one statement I'm asking "does this field need to be exported?" then getting the field value from the ui, then getting the field name in the db from an array, then executing a sql query. On one line. For each and every editable field.

    I guess what i should be doing is have a gui class with a couple methods, one to return textfield text, one to return combobox index, one to return checkbox state, and then have a db 'logic' class with a method for fetching values from the ui class and one for performing an export using arguments specified in another method called 'doexport' or something.

    I don't know, this is pretty confusing to me, I think I have a semisolid jello-like idea of what I need to do, I'm just having a hard time getting it straight and putting it to words. I need to think some more of how it'll be done, but I think I'll wind up with something much cleaner than what I have currently.

    And about those actionlisteners... I still don't know what I could differently for what I have except use a different listener for each button/event... but that seems wasteful to me still. I mean what I have right now is what makes the most sense to me until it's proven to not make sense.

    There's a listener. When a button is pushed, it 'hears' it. It says "what button did that?". Once it knows, it picks an action and does it. I really would have to see some code that does it better to really get what you're saying about the listener. That link you provided doesn't seem like the right solution for my app.

    PS. I'm renaming this thread to "the ultimate wall of text"

    EDIT: Okay, yeah, I think I have the hang of this. I've never worked with multiple related classes or packages before, and after LOTS or reading and experimenting, I have have successfully split establishconnection and executelookup into related methods in two different classes, one to handle the GUI stuff, one to handle the DB stuff. I'm a little concerned that I might have done it 'backwards', though... the gui class imports the db class, and sends it 'questions' which the db class 'answers'. It sounded like you were saying that the db class should be the main class and it would implement the gui class. I dunno. :O
    Last edited by 711groove; 12-17-2009 at 08:39 PM.

  15. #15
    Tolls is offline Moderator
    Join Date
    Apr 2009
    Posts
    11,450
    Rep Power
    18

    Default

    Sounds like you're on the right road. It takes time, and I wish I'd thought of compartmentalisation! :)

    As for the db and gui, the db should not know about the gui (and vice versa). There's normally a bit in between the two that decides where things go...a control layer. I'll try and come up with a better description when I don't have a grumpy son sat on my lap.

Similar Threads

  1. Generics and subclasses
    By Sven in forum Advanced Java
    Replies: 1
    Last Post: 12-22-2008, 06:06 PM
  2. Main class and subclasses
    By rosh72851 in forum New To Java
    Replies: 5
    Last Post: 11-19-2008, 06:16 PM
  3. Replies: 0
    Last Post: 01-02-2008, 04:24 AM
  4. help needed with methods in subclasses
    By uncopywritable in forum New To Java
    Replies: 4
    Last Post: 08-01-2007, 01:47 PM
  5. Hibernate subclasses
    By Ed in forum JDBC
    Replies: 2
    Last Post: 07-02-2007, 04:42 PM

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •