我有4个自定义微调器,可以修改一个选定小部件的宽度,高度,X位置和Y位置。我的小部件可以在屏幕上拖动,并且想法是使用这些微调器来更改某些属性,例如宽度或高度,并立即看到对更改的影响。 是否有一个模式可以用来替换所有这些类(XSpinnerListener,YSpinnerListener ...)只有一个,并指示我当前对象(JButton)的哪个属性需要更改?这是一个很好的设计方法吗?
public void init(){
widthSpinner.setListener(new WidthSpinnerListener());
heightSpinner.setListener(new HeightSpinnerListener());
xSpinner.setListener(new XSpinnerListener());
ySpinner.setListener(new YSpinnerListener());
}
public class XSpinnerListener implements SpinnerListener {
@Override
public void spinnerValueChanged() {
current.setLocation(xSpinner.getValue(), current.getY());
}
}
public class YSpinnerListener implements SpinnerListener {
@Override
public void spinnerValueChanged() {
current.setLocation(current.getX(), ySpinner.getValue());
}
}
public class WidthSpinnerListener implements SpinnerListener {
@Override
public void spinnerValueChanged() {
current.setSize(widthSpinner.getValue(), current.getHeight());
}
}
public class HeightSpinnerListener implements SpinnerListener {
@Override
public void spinnerValueChanged() {
current.setSize(current.getWidth(), heightSpinner.getValue());
}
}
答案 0 :(得分:6)
有些想法......
您可以通过为SpinnerListener spinnerValueChanged(...)
方法提供SpinnerEvent参数来模拟Swing的设计,该参数指示正在更改哪个轴。 Axis可以用enum封装,......
enum Axis {
X("X"), Y("Y"), WIDTH("Width"), HEIGHT("Height");
private String name;
private Axis(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
SpinnerEvent参数类可能类似于:
class SpinnerEvent {
private Object source;
private Axis axis;
private int value;
public SpinnerEvent(Object source, Axis axis, int value) {
this.source = source;
this.axis = axis;
this.value = value;
}
public Object getSource() {
return source;
}
public Axis getAxis() {
return axis;
}
public int getValue() {
return value;
}
}
您的SpinnerListener界面(您不会向我们展示)必须更改:
interface SpinnerListener {
public void SpinnerValueChanged(SpinnerEvent e);
}
也许你的具体实现可以在实现Movable接口的对象上工作:
interface Movable {
public abstract int getX();
public abstract void setX(int x);
public abstract int getY();
public abstract void setY(int y);
public abstract int getWidth();
public abstract void setWidth(int width);
public abstract int getHeight();
public abstract void setHeight(int height);
public abstract void move(Axis axis, int value);
}
使用密钥方法,可以像下面那样实现移动:
@Override
public void move(Axis axis, int value) {
switch (axis) {
case X:
x += value;
break;
case Y:
y += value;
break;
case WIDTH:
width += value;
break;
case HEIGHT:
height += value;
default:
break;
}
}
小具体实施
class ConcreteSpinnerListener implements SpinnerListener {
private Movable movable;
public ConcreteSpinnerListener(Movable movable) {
this.movable = movable;
}
@Override
public void SpinnerValueChanged(SpinnerEvent e) {
movable.move(e.getAxis(), e.getValue());
}
}
答案 1 :(得分:3)
在类似情况下,如果没有性能损失,我总会在发生变化时执行所有操作:
public void init(){
SpinnerListener spinnerListener = new MySpinnerListener();
widthSpinner.setListener(spinnerListener);
heightSpinner.setListener(spinnerListener);
xSpinner.setListener(spinnerListener);
ySpinner.setListener(spinnerListener);
}
public class MySpinnerListener implements SpinnerListener {
@Override
public void spinnerValueChanged() {
updateLocationAndSize();
}
}
void updateLocationAndSize() {
current.setLocation(xSpinner.getValue(), ySpinner.getValue());
current.setSize(widthSpinner.getValue(), heightSpinner.getValue());
}
这样,代码更短,其他读者的意图也很明确。
此外,此方法的另一个优点是可以更轻松地将初始状态触发器(微调器)与操作使用者(current
组件)同步。
在您的示例中,它可能适用也可能不适用,但假设您使用current
的已保存大小和位置属性打开屏幕。您使用保存的值初始化微调器,然后调用updateLocationAndSize
以使current
的大小和位置与微调器同步,然后注册侦听器以通知增量更改。< / p>
答案 2 :(得分:2)
假设Java 8并且接受了你的微调器实际上有setListener()
方法的那一刻,我只会将SpinnerListener
实现为利用lambdas的单个具体类。注意到你的每个SpinnerListener
调用一个带有两个int
参数的当前方法,可以在SpinnerListener中设想这样的东西:
BiConsumer<Integer, Integer> currentSetter;
Supplier<Integer> firstArg;
Supplier<Integer> secondArg;
public void spinnerValueChanged() {
currentSetter.accept(firstArg.get(), secondArg.get());
}
可以在init中调用它:
public void init() {
widthSpinner.setListener(
new SpinnerListener(
{ a, b -> current.setSize(a, b) },
{ widthSpinner.getValue() },
{ current.getHeight() }));
// ... etc.
}
答案 3 :(得分:0)
在设计方面,您的主要问题似乎是从控件中隔离视图。您正在寻找模型视图控制器。
该模型是OP中所谓的coord_cartesian
。
如果设置X位置是主模型操作,则当前API中应该有d <- data.frame(a = rnorm(n = 100, mean = 0, sd = 1),
b = rnorm(n = 100, mean = 0, sd = 1))
ggplot(d, aes(a,b)) +
geom_point(position=position_jitter(width=0.3, height=.3), size=2) +
theme(panel.background=element_blank()) +
coord_cartesian(xlim=c(-3,3), ylim=c(-3,3))
。
如果这是真的,那么所有四个动作都是一个将滑块与相应动作相关联的衬垫,使用接口作为传递函数指针的Java&lt; = 1.7方式,所以你很好。我可能会使监听器匿名(如果没有重用,请将实际代码保持为接近Slider定义)但这只是风格。
基本上,我们将视图(滑块本身)和控件(听众代码)混合了一下,但在我看来,在大多数情况下它都可以(在我的例子中,我会这样做,并坚持你当前的解决方案)。可读性仍然相当不错,尽管它很冗长。那只是Java,在我看来它会运行良好并且可以维护(就像任何GUI一样)。
如果你有很多这样的属性需要编辑,或者模型的API已经很好并且不需要这些精细控件,你可能想要使用一个显式控制器,即一个解释细粒度操作的类{ {1}}并在实际当前对象public class FormPanel extends JPanel {
private JLabel usernameLabel, passwordLabel;
private JTextField usernameField;
private JPasswordField passwordField;
private JButton submitButton, clearButton;
private Collection<FormPanelListener> formPanelListeners = new ArrayList<>();
public FormPanel() {
...
submitButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String username = usernameField.getText().trim();
char[] password = passwordField.getPassword();
FormPanelEvent e = new FormPanelEvent(this or submitButton)
}
});
...
}
上适当地委托他们。
在Hovercraft的答案中已经提出了该方向的完整方案:使您的命令显式(根据DP),使视图元素生成命令,以及控制器来解释它们。你需要一个非常繁重的用例来走这条路,尽管最终它更强大和可扩展。如果你没有做任何愚蠢的事情,GUI就不会有性能问题。所以德拉甘的回答也让我感到务实和明智。在某种程度上,它已经部分地将控制代码与实际的setLocationAndSize方法中的视图隔离开来。
总而言之,如果您在听众中添加的代码是单行代码,那么从设计的角度来看,您就可以了。
如果听众代码很复杂,则不应该隐藏&#34;在一个监听器中:这会产生代码碎片,在各种监听器中有很多重复,重要的代码隐藏在GUI代码中,而它永远不应该是这种情况。
对于多个单行,请让侦听器将实际操作委托给更复杂的控制器,或者考虑扩展模型API以将此操作包含为它提供的高级服务。