倔强的客户

引用 “The Elements of User Experience” 一书中的一段形容再不为过了:

无论如何,拥有更多的“产品特性”,被证明只能保持短时间的竞争优势,随着功能的不断膨胀,网站变得越来越复杂,越来越笨重,越来越难以使用,最后就失去了对初次访问者应有的吸引力,同时,企业仍然很少去关心用户真正喜欢什么,很少去发现有价值,或真正可以使用的东西。

总是想着一股脑的加东西,从而获取用户更多的信息(包括隐私信息),更多的从商业角度考虑了,而不是从一个普通用户的角度考虑,如何简化用户的使用负担?如何加强用户的使用体验? 如何让用户很快的融入网站的使用?

增加需求,然后在发布的时候隐藏功能

连着两个项目是web相关的项目,最近发现了这两个项目比较相似的一处:

客户A(一期刚完)说:先把XX功能,XXX功能,XXXX功能,… 隐藏了,一期我们先不考虑放上去。

客户B(二期需求)说:把XX隐藏,把XXX隐藏,把XXXX隐藏,把XXXXX改名为…。

这个时候心情很复杂,基本上这些被隐藏或者暂时屏蔽的功能,或许此刻起,就已经半die掉了,但是已经付出,增长的只有经验。

[09/49周主题] – Swing Tips

择这个主题,是因为上周公司刚好做了一个有关“Beginning Java AWT and Swing” 的培训,借此机会正好总结一下这方面的使用技巧。对于Swing界面方面的研究,我仅仅是应用而已,公司里有几位同事在这方面的研究比较深,这块的应用和使用技巧分两部分, 一部分是Swing 使用本身的,另一部分是Design工具NetBeans的,如果是刚入门建议从这里看起:Creating a GUI With JFC/Swing

本期主题:Swing Tips

一、性能问题

随着Java 6对于Swing性能的改进,Swing的运行速度已经开始得到了大大的提高,看看NetBeans就知道了,NetBeans就是Swing开发的,如果你跑Swing很慢,很耗资源,那么应该从自己的程序上找找问题,推荐使用NetBeans自带的Profile查找原因,教程在这里 – Profile Introduce

二、LookAndFeel

用Swing做企业应用时,LookAndFeel的选择和使用是决定这个项目能否被客户接受的一个很大因素,即要好看,又要考虑跨平台的兼容性,必要时自己还得设计部分LookAndFeel, 可以看看这里提供的一些开源LookAndFeel:http://www.open-open.com/61.htm 和http://www.javootoo.com/。
切换LookAndFeel:

UIManager.setLookAndFeel(LookAndFeelName);
SwingUtilities.updateComponentTreeUI(frame);
frame.pack();

三、合理的控制初始化组件和组件初始化的顺序可以很大的提高性能

举个例子:之前我们项目中有一个地方,当打开程序时,会初始化几十个甚至成百个JPanel,这显然成为程序启动时慢的一个因素,也导致了用户体验的降低,这些JPanel完全可以在程序启动后再根据用户的需要去初始化,因为用户打开程序时这些Panel不是必须看到的。

四、要有统一的UI规范

比如Button的高度,进度条的高宽等,也可以通过UIManager给系统组件设置统一属性,比如统一设定Button的间距和字体:

UIManager.put("Button.margin", new Insets(2, 5, 2, 5));
UIManager.put("Button.font", new Font("宋体", Font.PLAIN, 13));

五、多线程的使用

用Swing做的都是界面的东西,如果界面假死或者用户等待事件太长,那么用户体验必然是不好的,这里就需要用到多线程的使用了,当界面处理一个请求时,不能让界面假死了,需要后台另一个线程去做处理,然后将结果返回到Swing线程,这块可以看看SwingWorker的介绍。

六、布局管理器

布局管理器的使用在Swing里面是比较重要的,它直接决定了你界面的显示效果,也是比较难用的一块,不好举例子,建议多了解每个布局管理器的使用场景。

七、JTable & JTree

在Swing组件的使用中除了布局管理器,估计就数JTable和JTree的使用稍微有点麻烦了,下面我就分享一些实际项目中JTable的一些实例,关于JTree,可以点这里:JTree 经验 总结

JTable相关

1、自定义表头排序
TableRowSorter rs = (TableRowSorter) table.getRowSorter();
Comparator<Integer> intComparator = new Comparator<Integer>() {

            public int compare(Integer o1, Integer o2) {
                return o1.compareTo(o2);
            }
        };
rs.setComparator(3, intComparator);
 
2、自定义Table Renderer
public class CommonTableCellRenderer extends DefaultTableCellRenderer {

        @Override
        public Component getTableCellRendererComponent(JTable table, Object value,
                boolean isSelected, boolean cellHasFocus, int row, int column) {
            JComponent comp = (JComponent) super.getTableCellRendererComponent(table, value,
isSelected, cellHasFocus, row, column);
            //表格的奇数偶数行交叉颜色显示
            if (!isSelected) {
                if (row % 2 == 0) {
                    comp.setBackground(UIConsts.HIGHLIGHTER_COLOR);
                } else {
                    comp.setBackground(Color.white);
                }
            }

            //table column里面显示图标和对齐方式
            switch (column) {
                case PaperTableModel.STATUS_COLUMN:
                    switch ((EntityStatus) value) {
                        case VALID:
                            comp.setIcon(ENABLED_ICON);
                            setHorizontalAlignment(JLabel.LEADING);
                            break;
                        case DISABLED:
                            comp.setIcon(DISABLED_ICON);
                            setHorizontalAlignment(JLabel.CENTER);
                            break;
                    }
                    break;
                default:
                    comp.setIcon(null);
                    break;
            }

            return comp;
        }
    }
 
3、自定义Table列宽
TableColumnModel colModel = table.getColumnModel();
colModel.getColumn(0).setPreferredWidth(70);
colModel.getColumn(1).setPreferredWidth(55);
colModel.getColumn(2).setPreferredWidth(120);
4、禁止Table列拖动
 table.getTableHeader().setReorderingAllowed(false);  
5、单选表格设置
 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);   
6、设置表头默认支持排序
 table.setAutoCreateRowSorter(true);  
7、设置列不可随容器组件大小变化自动调整宽度
 table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);  
8、固定Table上的某些列不滚动

这个需求是这样的,比如Table上左边有部分数据,是后面数据所共有的属性,那么当后面数据很多时,显示不下会出现滚动条,但是滚动时又不想让左侧的共有属性动,只滚动右侧的数据部分。
实现原理是:scrollPane里面放置一个表格,然后在scrollPane的左上角放置以共有属性的部分为Model的表格,剩下的右侧就是剩余的纯数据表格。
最终效果就是表格左侧的列锁定了,右侧数据出现滚动条时,可以滚动,但左侧不动。
核心代码:比如有HeaderTable和ReportTable, 其中ReportTable是放置在一个ScrollPanel里面,Model是所有数据的Model,将左侧的数据和右侧的数据分开

//找到主表所在的scrollPane
JScrollPane scrollPane = (JScrollPane) SwingUtilities.
   getAncestorOfClass(JScrollPane.class,
    reportTable.getTable());

//中间处理headerTable的数据和reportTable剩余的数据

//将新表HeaderTable放在scrollPane的左上角
scrollPane.setRowHeaderView(headerTable.getTable());
scrollPane.setCorner(JScrollPane.UPPER_LEFT_CORNER,
                    headerTable.getTable().getTableHeader());
9、Table上的直接编辑功能

两点:
1、重写 public boolean isCellEditable(int row, int columnIndex) 方法,定义可编辑的行列。
2、重写 public void setValueAt(Object obj, int rowIndex, int columnIndex) 方法,拿到原来的对象,设置新的对象值。

10、Table的Excel导出功能

表格上的Excel导出功能还是比较实用的功能,企业应用一般都会用到,这里提供相关代码

try {
   WritableCellFormat titleFormat = new WritableCellFormat(
   new WritableFont(WritableFont.createFont("黑体"), 16,
   WritableFont.NO_BOLD));
   titleFormat.setVerticalAlignment(VerticalAlignment.CENTRE); // 垂直对齐
   titleFormat.setAlignment(Alignment.CENTRE); // 水平对齐
   titleFormat.setWrap(true); // 是否换行

   WritableCellFormat headerFormat = new WritableCellFormat();
   headerFormat.setBorder(Border.ALL, BorderLineStyle.THIN); // 线条
   headerFormat.setVerticalAlignment(VerticalAlignment.CENTRE); // 垂直对齐
   headerFormat.setAlignment(Alignment.CENTRE); // 水平对齐
   headerFormat.setWrap(true); // 是否换行

   WritableCellFormat countFormat = new WritableCellFormat(
   new NumberFormat("0.000"));
   countFormat.setBorder(Border.ALL, BorderLineStyle.THIN); // 线条
   countFormat.setVerticalAlignment(VerticalAlignment.CENTRE); // 垂直对齐
   countFormat.setAlignment(Alignment.RIGHT); // 水平对齐
   countFormat.setWrap(true); // 是否换行

   WritableCellFormat moneyFormat = new WritableCellFormat(
   new NumberFormat("0.00"));
   moneyFormat.setBorder(Border.ALL, BorderLineStyle.THIN); // 线条
   moneyFormat.setVerticalAlignment(VerticalAlignment.CENTRE); // 垂直对齐
   moneyFormat.setAlignment(Alignment.RIGHT); // 水平对齐
   moneyFormat.setWrap(true); // 是否换行

   WritableCellFormat intFormat = new WritableCellFormat(
   new NumberFormat("0"));
   intFormat.setBorder(Border.ALL, BorderLineStyle.THIN); // 线条
   intFormat.setVerticalAlignment(VerticalAlignment.CENTRE); // 垂直对齐
   intFormat.setAlignment(Alignment.RIGHT); // 水平对齐
   intFormat.setWrap(true); // 是否换行

   WritableWorkbook book = Workbook.createWorkbook(exportFile);
   WritableSheet sheet = book.createSheet("综合统计报表", 0);
   int titleRow = 0;
   int headerRow = 3;
   int dataRow = 5;
   sheet.mergeCells(0, titleRow, model.getColumnCount() - 1,
   titleRow);
   Label titleLab = new Label(0, titleRow, tableTitleTxfd.getText(), titleFormat);
   sheet.addCell(titleLab);

   //生成表头
   for (int j = 0; j < model.getColumnCount(); j++) {
       sheet.mergeCells(j, headerRow, j, headerRow + 1);
       Label lab = new Label(j, headerRow, model.getColumnName(
      j), headerFormat);
       sheet.addCell(lab);
   }

   for (int row = 0; row < model.getRowCount(); row++) {
       for (int col = 0; col < model.getColumnCount(); col++) {
          Object obj = model.getValueAt(row, col);
          if (obj instanceof String) {
             Label lab = new Label(col, dataRow + row,
               (String) obj, headerFormat);
            sheet.addCell(lab);
          } else if (obj instanceof Integer) {
         Number labelN = new Number(col, dataRow
         + row, (Integer) obj, intFormat);
         sheet.addCell(labelN);
         } else {
         Label lab =
         new Label(col, dataRow + row, "",
         headerFormat);
         sheet.addCell(lab);
         }
      }
   }

   //生成表尾
   int footerRow = dataRow + model.getRowCount() + 1;
   int step =
   (int) (((double) (model.getColumnCount() - 2) / 3)
   + 1);
   Label footerLab = new Label(0, footerRow, "部门负责人:");
   sheet.addCell(footerLab);
   footerLab = new Label(step, footerRow, "制表:" + creatorTxfd.getText());
   sheet.addCell(footerLab);
   footerLab = new Label(model.getColumnCount() - 2, footerRow,
   NazcaFormater.getSimpleDateString(new Date()));
   sheet.addCell(footerLab);

   sheet.setColumnView(0, 16);
   sheet.setRowView(titleRow, 600);

   book.write();
   book.close();
   //导出成功
   } catch (Throwable ex) {
   //导出报表失败
   ex.printStackTrace();
}
11、Table的打印功能

打印这块,之前已经提过,可以参考之前的文章 JTable Print

12、在Table上选择多行
int rowcounts=table.getSelectedRows().length;
if(rowcounts>1)
  int[] rows=table.getSelectedRows();
    for(int i=0;i<rows.length;i++){
    String value=(String) tableModel.getValueAt(i, 1);
  }
}

NetBeans 6+ Tips

这里说NetBeans,主要是因为目前Swing开发方面,还没有哪个IDE能胜过NetBeans。

一、NetBeans的配置

1、配置为英文,大部分时间我们下载的都是中文的版本,可以通过在/$NetBeans_HOME/etc/netbeans.conf中添加 –locale en_US, 让启动时显示为英文,这个之前也有文章介绍:Netbeans 英文界面最简单的Netbeans中英文切换
2、优化配置可以看之前介绍的这篇文章,就不重复了:Netbeans 6.5 优化建议

二、经验分享

1、Swing的Debug虽然被很多人说不好用,但是在用NetBeans时,多用Debug可以提高效率,因为Debug模式下的修改,大部分只要点击应用,就可以不用重启项目而看到效果。
2、很好用的快捷键和快速补齐(限Windows + Linux下,如果在Mac下改成 ⌘ 试试),如:

快捷键:
Ctrl+R          Rename
Alt+Enter       Fix Error(Eclipse Ctrl+1)
Alt+Shift+F    Quick Format
Alt+Shift+I     Fix Import
F9                Build File
F6                Run Main Project
Shift+F6        Run File
Ctrl+|           Insert Code

快速补齐(英文输入状态下,输入完后按Tab键,也可以自己配置为其他键,在Options - Editor - 
Code Templates下):
psvm            public static void main
sout             System.out.println
im               implements
Psfs             public static final String
psfi             private static final String
fore            for($ : $){}
fori             for(int i = 0; i < arr.length; i++){}
forl             for(int i = 0; i < list.size(); i++){}

三、插件分享
我们都知道NetBeans上的插件很多,可以说NetBeans正是因为这些插件才强大起来,支持的功能也更多了。分享的这几个插件是平时工作时,可以显著提高效率的,不好的不推荐,你如果有好的也别忘记分享下。

1、Path Tools – 可以直接查找到类或者文件夹所在的磁盘位置,基本是我每次装完NetBeans的后第一个装的插件。
2、SQE(Software Quality Environment) – 是最近同事刚刚推荐的一个插件,看名字就知道了,是一个类似Firebug的插件,可以发现程序中存在的一些显著的错误,很不错。
3、UUID Generator – 同事写的一个快速生成UUID的插件。
4、SwingX 插件 – 用来添加一些SwingX组件的。
5、iReport – 打印报表用的插件,结合JasperReport使用。

项目越到尾声越怕改动

目接近尾声,最怕这样那样的修改,今天一个电话,快烦死了。

是关于JTable的打印,之前就因为嫌麻烦没搞彻底,简单表格的打印功能提供了,复杂点的就没弄,和业务员交流,导出Excel也可以,于是就直接让导出Excel再打印了,结果今天一个电话,和业务流程使用不相关的一个小头让全部统一提供打印功能,无语了!

唉,看看明天去摆扯的结果了,弄不好还得把剩下的打印功能加上 🙁

Bug bug Bug bug

看到这样的标题, 估计大概也知道本文要谈论的内容了, 是的, 有关bug的. 这里不得不感慨下, 最近修bug修出感悟来了, 这里总结下为什么.

1. 最重要的归根结底的是由于开发人员没有考虑全面, 大体功能完成了, 但是没有完善好, 各式各样的小bug, 能把人烦死.

2. 需求不明确导致的排第2位, 开发人员拿到需求, 开始做了, 但是不知道的是这个需求不完整, 于是乎做完了, 给测试, 给客户看了, 反馈回来改了, 测了, 又给客户看, 反馈又回来, 可能到头来, 整个功能绕了个圈, 脾气不好的绝对能气炸了.

3. 客户不把需求讲明白, 挤牙膏似的, 一点点给你挤, 需求从头到尾, 客户一直在给你添盐加醋, 到头来可以把他们自己绕进去, 之前说A, 现在说B, 后面又说C, 搞不死也差不多了.

4. 项目进入后期的bug修复阶段, 一般不是全员上的, 可能就留几个人来修复bug, 这下好了, 非自己bug的就开始踢皮球了, 一个bug可以被Assigh好多次, 无语.

5. 情绪 – 到了项目后期, 基本上大家都做皮了, 不想做了, 巴不得赶紧结束, 换个环境换个心情, 于是当面对各类bug时, 能有个好心情好情绪实属难得了, 所以请不要再添盐加醋.

6. 情绪后该做的还得好好做的, 不然修正一个bug, 不小心引入另一个bug或者几个bug, 会更抓狂的, 事实是这样的情况很普遍.