|
Doing GUI's Properly - MVP for Dummies - Printable Version +- The Bytecode Club - Reverse Engineering Forum (https://the.bytecode.club) +-- Forum: Lobby (https://the.bytecode.club/forumdisplay.php?fid=1) +--- Forum: Programming (https://the.bytecode.club/forumdisplay.php?fid=86) +--- Thread: Doing GUI's Properly - MVP for Dummies (/showthread.php?tid=205) |
Doing GUI's Properly - MVP for Dummies - Cov - 01-03-2015 As a first proper post I'll post this tutorial I posted elsewhere. Enjoy this post, post, posting, posted, post. Been a while since I've posted a tutorial on a site, so as a pre-warning I can ramble a bit, but I'll try to keep it concise and to the point. Now why did I write this? Basically going through the few open source scripts, various bot sources, etc I've noticed that UI's are not that well designed and structured. If the developer wanted to refactor or redesign their UI they'd have to change more code than they should. This is where using a design pattern can really help. What is MVP? MVP or Model-View-Presenter is a UI design pattern designed to keep data and the view separate. Why is this useful? Well it allows you to change logic, data representation or how data is stored with minimal changes in the code making small and large projects easier to maintain. Sound useful yet? It also allows for testing, mainly unit testing. I won't cover unit testing because that's a whole other thing in itself, but undeniably useful, have a look here for some more info on it. But splitting your code into the Model, the View and the Presenter allows you to test how the data is stored and manipulating it separately. This makes it easier to maintain as you don't have to search through a clusterfuck of code to find the problem, if you change something you can locate it easier. So lets look at the different parts of the MVP model:
So how do you implement it? Being the upstanding scholar that I am, and the fact that I already have a base knocking around on my github, I've done some of the leg work for you. I'll break down each base class and try to explain how it works. Model Code: package io.aiecee.mvp.model.base;
import java.util.LinkedList;
import java.util.List;
/**
* Date: 04/09/2014
* Time: 10:26
*
* @author Matt Collinge
*/
public abstract class ModelBase {
private List<IModelChangedListener> listeners = new LinkedList<IModelChangedListener>();
public void addModelChangedListener(IModelChangedListener listener) {
listeners.add(listener);
}
public void removeModelChangedListener(IModelChangedListener listener) {
listeners.remove(listener);
}
protected void onModelChanged(String variable) {
ModelChangedEvent event = new ModelChangedEvent(this, variable);
listeners.stream().forEach((listener) -> listener.onModelChanged(event));
}
}View Code: package io.aiecee.mvp.view.base;
import io.aiecee.mvp.presenter.base.PresenterBase;
/**
* Date: 04/09/2014
* Time: 10:40
*
* @author Matt Collinge
*/
public abstract class ViewBase<P extends PresenterBase> {
private P presenter;
public P getPresenter() {
return presenter;
}
public void setPresenter(P presenter) {
this.presenter = presenter;
}
public abstract void show();
public abstract void hide();
public abstract void dispose();
}Presenter Code: package io.aiecee.mvp.presenter.base;
import io.aiecee.mvp.model.base.IModelChangedListener;
import io.aiecee.mvp.model.base.ModelBase;
import io.aiecee.mvp.view.base.ViewBase;
/**
* Date: 04/09/2014
* Time: 10:39
*
* @author Matt Collinge
*/
public abstract class PresenterBase<V extends ViewBase, M extends ModelBase> implements IModelChangedListener {
private V view;
private M model;
public PresenterBase(V view, M model) {
this.view = view;
this.model = model;
model.addModelChangedListener(this);
}
public V getView() {
return view;
}
public M getModel() {
return model;
}
}Putting this to use! Using this framework I've made a very simple UI to show you how it works. It's based around user info where you enter in first name, last name and the age and this is shown as full name and age elsewhere in the UI. It's again made of 3 parts, the User, the UserView and the UserPresenter. User Code: package io.aiecee.mvp.model.impl;
import io.aiecee.mvp.model.base.ModelBase;
/**
* Date: 04/09/2014
* Time: 10:34
*
* @author Matt Collinge
*/
public class User extends ModelBase {
private String firstName, lastName;
int age;
public User(String firstName, String lastName, int age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
public User() {
firstName = "";
lastName = "";
age = 0;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
onModelChanged("firstName");
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
onModelChanged("lastName");
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
onModelChanged("age");
}
}UserView Code: package io.aiecee.mvp.view.impl;
import io.aiecee.mvp.presenter.impl.UserPresenter;
import io.aiecee.mvp.view.base.ViewBase;
import javax.swing.*;
import javax.swing.border.TitledBorder;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
/**
* Date: 04/09/2014
* Time: 10:53
*
* @author Matt Collinge
*/
public class UserView extends ViewBase<UserPresenter> {
private JFrame frame;
// Setter
private JTextField firstName, lastName;
private JSpinner ageSpinner;
// Getter
private JLabel fullName;
private JLabel ageLabel;
public UserView() {
frame = new JFrame("User Example");
frame.setLayout(new BorderLayout());
// Setter
{
JPanel setterPanel = new JPanel();
setterPanel.setBorder(new TitledBorder("Setter"));
setterPanel.setLayout(new BoxLayout(setterPanel, BoxLayout.Y_AXIS));
firstName = new JTextField();
firstName.setColumns(25);
firstName.addKeyListener(new KeyListener() {
@Override
public void keyTyped(KeyEvent e) {
getPresenter().setFirstName(((JTextField) e.getSource()).getText());
}
@Override
public void keyPressed(KeyEvent e) {
}
@Override
public void keyReleased(KeyEvent e) {
}
});
setterPanel.add(firstName);
lastName = new JTextField();
lastName.setColumns(25);
lastName.addKeyListener(new KeyListener() {
@Override
public void keyTyped(KeyEvent e) {
getPresenter().setLastName(((JTextField) e.getSource()).getText());
}
@Override
public void keyPressed(KeyEvent e) {
}
@Override
public void keyReleased(KeyEvent e) {
}
});
setterPanel.add(lastName);
ageSpinner = new JSpinner();
ageSpinner.setValue(0);
ageSpinner.addChangeListener(e -> getPresenter().setAge((Integer) ((JSpinner) e.getSource()).getValue()));
setterPanel.add(ageSpinner);
frame.add(setterPanel, BorderLayout.LINE_START);
}
// Getter
{
JPanel getterPanel = new JPanel();
getterPanel.setBorder(new TitledBorder("Getter"));
getterPanel.setLayout(new BoxLayout(getterPanel, BoxLayout.Y_AXIS));
fullName = new JLabel();
getterPanel.add(fullName);
ageLabel = new JLabel();
getterPanel.add(ageLabel);
frame.add(getterPanel, BorderLayout.LINE_END);
}
}
public void setFullName(String fullName) {
this.fullName.setText(fullName);
}
public void setAge(int age) {
this.ageLabel.setText(age + "");
}
@Override
public void show() {
frame.pack();
frame.setVisible(true);
}
@Override
public void hide() {
frame.setVisible(false);
}
@Override
public void dispose() {
frame.dispose();
}
}UserPresenter Code: package io.aiecee.mvp.presenter.impl;
import io.aiecee.mvp.model.base.ModelChangedEvent;
import io.aiecee.mvp.model.impl.User;
import io.aiecee.mvp.presenter.base.PresenterBase;
import io.aiecee.mvp.view.impl.UserView;
/**
* Date: 04/09/2014
* Time: 10:53
*
* @author Matt Collinge
*/
public class UserPresenter extends PresenterBase<UserView, User> {
public UserPresenter() {
super(new UserView(), new User());
getView().setPresenter(this);
}
@Override
public void onModelChanged(ModelChangedEvent event) {
switch (event.getVariableName().toLowerCase()) {
case "firstname":
case "lastname":
updateFullName();
break;
case "age":
updateAge();
}
}
public void setFirstName(String firstName) {
getModel().setFirstName(firstName);
}
public void setLastName(String lastName) {
getModel().setLastName(lastName);
}
public void setAge(int age) {
getModel().setAge(age);
}
private void updateFullName() {
String fullName = getModel().getFirstName() + " " + getModel().getLastName();
getView().setFullName(fullName);
}
private void updateAge() {
getView().setAge(getModel().getAge());
}
}Conclusion This about wraps up the tutorial, we've made a nice MVP framework base that can be expanded on and implemented a very simple application using it. This can be used in scripts to help setup the script by using the Model as a kind of settings store, you can also make the Model serialisable so it can be re-loaded next run. The whole project can be found here for you to read over at your leisure. Any problems with it let me know and I'll update it. I'll add an FAQ of any questions if anyone has any at the bottom of the post. RE: Doing GUI's Properly - MVP for Dummies - Konloch - 01-04-2015 Very good tutorial! Extremely in depth and explains pretty much everything needed, good job! |