除了 ALL_FIELD_VALUES 內容之外,現在 view(CqRecord) 方法還可以讀取該記錄的 LEGAL_ACTIONS 內容。 LEGAL_ACTIONS 內容的值是動作的 Action Proxy 清單,這些動作可合法套用至現行狀態的記錄。 此清單是用來移入組合框控制項,使用者在按一下編輯按鈕之前,可從中選取動作。 此範例不支援某些類型的動作,例如 SUBMIT 及 RECORD_SCRIPT_ALIAS,因此,它們不會新增至組合框控制項,即使它們出現在合法動作集內也一樣。
public JFrame view(CqRecord selected) { try { final CqRecord record = (CqRecord)selected.doReadProperties(RECORD_PROPERTIES); final JButton start = new JButton("Edit"); final JComboBox choices = new JComboBox(); for (CqAction a: record.getLegalActions()) { if (a.getType() != CqAction.Type.IMPORT && a.getType() != CqAction.Type.SUBMIT && a.getType() != CqAction.Type.BASE && a.getType() != CqAction.Type.RECORD_SCRIPT_ALIAS) choices.addItem(a); } final JButton add = new JButton("Attach"); final JButton remove = new JButton("Detach"); final ViewRecord.RecordFrame frame = showRecord("View: ", record, choices.getItemCount()==0? null: new JComponent[]{choices, start, add, remove});
EditView.Viewer 也支援附加及分開按鈕,以新增及移除附件欄位的附件檔。
附加按鈕向使用者呈現標準 Swing 檔案選擇器對話框,讓使用者可以選取要附加的檔案。 為了將檔案附加至記錄,會使用 CqAttachment.doCreateAttachment(),並將使用者選取的檔案名稱傳給它。 在呼叫此方法之前,必須先建構定址至適當位置的 CqAttachment Proxy。 即將存放附件資源的資料夾,即是要附加之目標的欄位值 - 這是目前在記錄視圖中選取的欄位,因此它是從 ViewRecord.showRecord 傳回的頁框物件中取得的。 附件資源的唯一名稱是從當天的現行時間產生的。 此名稱只是一個位置保留元,因為 doCreateAttachment() 可能隨時變更資源名稱以符合其需求。
add.addActionListener(new ActionListener(){ /** * 提示使用者輸入要附加至所選取欄位的檔案名稱。 * 如果有提供, * 則檔案的內容會以附件讀入 ClearQuest 資料庫中。 */ public void actionPerformed(ActionEvent arg0) { if (!ViewRecord.isAttachmentList(frame.m_fields, frame.m_table.getSelectedRow())) return; JFileChooser chooser = new JFileChooser(); if (chooser.showOpenDialog(frame) == JFileChooser.APPROVE_OPTION) { try { String filename = chooser.getSelectedFile().getAbsolutePath(); CqFieldValue field = frame.m_fields.get(frame.m_table.getSelectedRow()); StpLocation aLoc = (StpLocation) ((StpFolder)field.getValue()).location() .child("new" + System.currentTimeMillis()); CqAttachment attachment = record.cqProvider().cqAttachment(aLoc); attachment = attachment.doCreateAttachment(filename, null, CqProvider.DELIVER); JOptionPane.showMessageDialog(frame, "Added '" + filename + "' as " + attachment); frame.dispose(); view(record); } catch(Throwable t) { Utilities.exception(frame, "Add Attachment", t);} }}});
分開按鈕利用 ViewRecord.selectAttachment,從使用者取得要以 CqAttachment Proxy 形式移除之附件的身分。然後,會呼叫這個 Proxy 的 doUnbindAll 方法,從資料庫中移除該附件。
remove.addActionListener(new ActionListener(){ /** * 可讓使用者選取與所選欄位相關聯的附件。 * 如果使用者選取此附件, * 則會刪除它。 */ public void actionPerformed(ActionEvent arg0) { if (!ViewRecord.isAttachmentList(frame.m_fields, frame.m_table.getSelectedRow())) return; try { CqAttachment attachment = ViewRecord.selectAttachment (frame, frame.m_fields, frame.m_table.getSelectedRow(), "Remove"); if (attachment != null) { attachment.doUnbindAll(null); frame.dispose(); view(record); } } catch(Throwable t) { Utilities.exception(frame, "Remove Attachment", t);} } } );
編輯按鈕從組合框提取所選取的 CqAction Proxy,並將它及現行檢視器的記錄 Proxy 傳遞至編輯方法,該方法會實際起始記錄的編輯。 如果所選取動作的 CqAction.Type 是 DUPLICATE,則必須在動作中提供所複製記錄的 ID。 此值是向使用者要求來的,並放在 Action 的引數對映中,作為 original 引數的值。
start.addActionListener(new ActionListener(){ /** * 使用檢視對話框上的組合框目前選取的動作, * 開始編輯記錄。 */ public void actionPerformed(ActionEvent arg0) { CqAction action = (CqAction)choices.getSelectedItem(); try { if (action.getType() == CqAction.Type.DUPLICATE) { String id = JOptionPane.showInputDialog (frame, "Enter ID of duplicated record"); if (id == null) return; action.argumentMap(new Hashtable<String>()); action.argumentMap().put("original", record.cqProvider().cqRecord((StpLocation)record .location().parent().child(id))); } edit(record, action); frame.dispose(); } catch (Exception ex) { Utilities.exception(frame, "Duplicate Action", ex); } } }); return frame; } catch (WvcmException ex){ ex.printStackTrace(); } return null; }
當使用者按一下記錄檢視器中的編輯按鈕時,會呼叫編輯方法。 會傳給它所要編輯記錄的 Proxy 及要執行編輯的動作的 Proxy。
請注意,用來啟動 ClearQuest® 動作的 Action Proxy 不需要定義任何內容 - 只需要定義其位置及其引數對映(必要的話)。若要形成動作的位置,您需要知道它的 <name>、它要使用的記錄的 <record-type> 及記錄所在的<database> 和 <db-set>。
則此位置為cq.action:<record-type>/<name>@<db-set>/<database>;例如,cq.action:Defect/Assign@7.0.0/SAMPL
使用新的 Proxy,呼叫 CqRecord.doWriteProperties 來開始編輯作業。 要寫入的唯一內容是要編輯記錄的動作。 doWriteProperties 方法傳回已開啟要編輯之記錄的 Proxy,此 Proxy 將包含 RECORD_PROPERTIES PropertyRequest 所要求的內容。在 doWriteProperties 傳回之後,也可以使用 record.doReadProperties(具有相同的 PropertyRequest)取得內容,但這樣比較沒有效率,因為它需要再次來回儲存庫,才能取得相同資料。
public void edit(CqRecord selected, CqAction action) { try { CqRecord record = m_provider.cqRecord(selected.stpLocation()); record = (CqRecord) record.setAction(action) .doWriteProperties(RECORD_PROPERTIES, CqProvider.HOLD); editRecord("Edit: ", record, selected); } catch (WvcmException ex){ Utilities.exception(null, "Start Action", ex); } }
Viewer.editRecord 類似 showRecord。主要差異在於它定義 TableModel.isCellEditable 和 TableModel.setValue。
JFrame editRecord(String title, final CqRecord record, final CqRecord selected) throws WvcmException { final JFrame frame = new JFrame(title + record.getUserFriendlyLocation()); JPanel panel = new JPanel(new BorderLayout()); JPanel buttons = new JPanel(new FlowLayout()); final JButton show = new JButton("View"); final JButton add = new JButton("Add"); final JButton remove = new JButton("Remove"); final JButton cancel = new JButton("Cancel"); final StpProperty.List<CqFieldValue>> fields = record.getAllFieldValues(); TableModel dataModel = new AbstractTableModel() { public int getColumnCount() { return fieldMetaProperties.length; } public int getRowCount() { return fields.size();} public String getColumnName(int col) { return fieldMetaProperties[col].getRoot().getName(); } public Object getValueAt(int row, int col) { try { return fields.get(row).getMetaProperty((MetaPropertyName<?>) fieldMetaProperties[col].getRoot()); } catch(Throwable ex) { if (ex instanceof StpException) { return ((StpException)ex).getStpReasonCode(); } else { String name = ex.getClass().getName(); return name.substring(name.lastIndexOf(".")+1); } } }
TableModel.isCellEditable 只對 VALUE 直欄傳回 True,而且列必須只屬於其 REQUIREDNESS 不是 READ_ONLY 的欄位。
public boolean isCellEditable(int row, int col) { if (fieldMetaProperties[col].getRoot().equals(StpProperty.VALUE)) { CqFieldValue field = fields.get(row); try { return field.getRequiredness() != Requiredness.READ_ONLY; } catch (WvcmException ex) { Utilities.exception(frame, "Field Requiredness", ex); } } return false; }
TableModel.setValueAt 在與顯示畫面相關聯的 CqRecord Proxy 中設定新欄位值。首先,會使用其 initialize() 方法更新 CqFieldValue 結構。此方法接受大部分欄位類型的字串表示法,並且將字串轉換成適當的欄位資料類型。
在更新 CqFieldValue 之後,它會設定到與該欄位的 PropertyName 相關聯的 CqRecord Proxy 中。 此步驟是必要的,這樣當記錄確定時,新欄位值才能寫入資料庫中。 若缺少此步驟,新欄位值只會留在屬於 ALL_FIELD_VALUES 內容一部分的 CqFieldValue 物件中。 ALL_FIELD_VALUES 內容是不可寫入的,因此變更絕不會寫入資料庫中。 透過直接將修改過的 CqFieldValue 複製到該欄位的 Proxy 項目,該欄位即變成更新的內容,且更新的值將由 Proxy 中所執行的下一個 do 方法寫入資料庫中。
public void setValueAt(Object aValue, int row, int col) { if (fieldMetaProperties[col].getRoot().equals(StpProperty.VALUE)) { CqFieldValue<Object> field = fields.get(row); field.initialize(aValue); record.setFieldInfo(field.getFieldName(), field); } } private static final long serialVersionUID = 1L; }; final JTable table = new JTable(dataModel); table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); show.setEnabled(false); add.setEnabled(false); remove.setEnabled(false); // 要求收到選擇變更的通知。 ListSelectionModel rowSM = table.getSelectionModel(); rowSM.addListSelectionListener(new ListSelectionListener() { /** * 根據表格中所選的欄位類型, * 在對話框中啟用按鈕。 */ public void valueChanged(ListSelectionEvent e) { if (!e.getValueIsAdjusting()){ int[] selected = table.getSelectedRows(); show.setEnabled(false); add.setEnabled(false); remove.setEnabled(false); for (int i=0; i <selected.length; ++i) if (ViewRecord.getRecordReferencedAt(fields, selected[i]) != null) { show.setEnabled(true); } else if (ViewRecord.isAttachmentList(fields, selected[i])) { show.setEnabled(true); add.setEnabled(true); remove.setEnabled(true); } } } });
檢視按鈕與 ViewRecord.Viewer 中的檢視按鈕一樣。
buttons.add(show); show.addActionListener(new ActionListener(){ /** * 對表格中所選取的欄位值呼叫檢視方法。 * */ public void actionPerformed(ActionEvent arg0) { int[] selected = table.getSelectedRows(); for (int i =0; i < selected.length; ++i) { int row = selected[i]; CqRecord record = ViewRecord.getRecordReferencedAt(fields, row); if (record != null) { view(record); } else if (ViewRecord.isAttachmentList(fields, row)) { view(ViewRecord.selectAttachment(frame, fields, row, "View")); } } } });
遞送按鈕會使用 CqProvider.DELIVER 選項呼叫 doWriteProperties(),在資料庫中確定已修改的記錄。在遞送成功之後,Viewer 會關閉,然後新的 Viewer 會在原始 Proxy 上啟動。 由於 view() 方法會從資料庫重新讀取內容,因此這個新視圖將顯示已更新的值。
JButton deliver = new JButton("Deliver"); buttons.add(deliver); deliver.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent arg0) { try { int mode = frame.getDefaultCloseOperation(); record.doWriteProperties(null, CqProvider.DELIVER); frame.dispose(); view(selected).setDefaultCloseOperation(mode); } catch (WvcmException ex) { Utilities.exception(frame, "Deliver failed", ex); } } });
取消按鈕使用記錄 Proxy 的 doRevert 方法來放棄編輯作業。 和遞送按鈕一樣,會關閉 Viewer,然後為原始 Proxy 實例化一個新的 Viewer。
buttons.add(cancel); cancel.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent arg0) { try { int mode = frame.getDefaultCloseOperation(); record.doRevert(null); frame.dispose(); if (mode == JFrame.EXIT_ON_CLOSE) System.exit(0); view(selected).setDefaultCloseOperation(mode); } catch (WvcmException ex) { Utilities.exception(frame, "Cancel failed", ex); } } });
附加和分開按鈕與 ViewRecord.Viewer 的那些按鈕相同,只不過遞送順序引數是 HOLD 而不是 DELIVER。這會延遲確定附件的新增或刪除,直到整個記錄都由 遞送按鈕確定為止。
buttons.add(add); add.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent arg0) { JFileChooser chooser = new JFileChooser(); int returnVal = chooser.showOpenDialog(frame); if (returnVal == JFileChooser.APPROVE_OPTION) { try { String filename = chooser.getSelectedFile().getAbsolutePath(); CqFieldValue field = fields.get(table.getSelectedRow()); StpLocation aLoc = (StpLocation) ((StpFolder)field.getValue()).location() .child("new" + System.currentTimeMillis()); CqAttachment attachment = record.cqProvider().cqAttachment(aLoc); attachment = attachment.doCreateAttachment(filename, null, CqProvider.HOLD); JOptionPane.showMessageDialog(frame, "Added '" + filename + "' as " + attachment + " (pending delivery)"); } catch(Throwable t) { Utilities.exception(frame, "Add Attachment", t);} }}}); buttons.add(remove); remove.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent arg0) { try { CqAttachment attachment = ViewRecord.selectAttachment (frame, fields, table.getSelectedRow(), "Remove"); if (attachment != null) attachment.doUnbindAll(null); } catch(Throwable t) { Utilities.exception(frame, "Remove Attachment", t);} } } ); panel.add(new JScrollPane(table), BorderLayout.CENTER); panel.add(buttons, BorderLayout.SOUTH); frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); frame.setContentPane(panel); frame.setBounds(300, 300, 600, 300); frame.setVisible(true); return frame; }
每次修改欄位時,GUI 可呼叫 record.doWriteProperties,以提供使用者即時回應,但也可能效率極差, 這端視用戶端及伺服器之間的通訊協定而定。 GUI 也可以提供更新按鈕,使所有累計更新寫入伺服器中,而非實際遞送它們。 這使綱目有機會檢查值並回報錯誤。 這些修改留給讀者,至於要採取什麼方法,則視此應用程式的預定用途而定。