除了 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 也可以提供更新按鈕,使所有累計更新寫入伺服器中,而非實際遞送它們。 這使綱目有機會檢查值並回報錯誤。 這些修改留給讀者,至於要採取什麼方法,則視此應用程式的預定用途而定。