它呼叫 ExecuteQuery.run,讓使用者能選取及執行查詢。在這個範例中,提供給 ExecuteQuerty.run 方法一個名稱為 ViewRecord.Viewer 的 ExeuteQuerty.Viewer 實例。這使得結果集畫面中出現開啟按鈕,按一下該按鈕即可呼叫 Viewer.view 方法。對於與結果集的所選取列相關聯的資源,會傳遞一個 Proxy 給 Viewer.view 方法(定義如下)。此 Proxy 是使用 CqRowData.getRecord() 方法取得的。
因此,本範例的內容包含在 Viewer.view 方法中:
static class Viewer implements ExecuteQuery.Viewer { Viewer(CqProvider provider) { m_provider = provider; } /** * @see com.ibm.rational.stp.client.samples.ExecuteQuery.Viewer#view(com.ibm.rational.wvcm.stp.cq.CqRecord) */ public JFrame view(CqRecord record) { if (record != null) try { record = (CqRecord)record.doReadProperties(RECORD_PROPERTIES); return showRecord("View: ", record, null); } catch (WvcmException ex){ ex.printStackTrace(); } return null; } /** * 在文字視窗顯示 Attachment 資源的內容。 * * @param attachment 要顯示的附件之 Attachment * Proxy。 */ public void view(CqAttachment attachment) { if (attachment != null) try{ File file = File.createTempFile("attach", "tmp"); attachment.doReadContent(file.getAbsolutePath(), null); BrowserDataModel.showFile(attachment.getDisplayName(), file); } catch(Throwable ex) { Utilities.exception(null, "View Attachment", ex); } } /** * 在表格中顯示記錄資源的 ALL_FIELD_VALUES 內容。 * 由 {@link #fieldMetaProperties} 陣列的內容決定表格的直欄。 * 由物件自己的 toString() 方法 * 實作大部分物件的顯示。 * * @param title 包含表格的視窗之標題字串 * @param record 要顯示的記錄之 Record Proxy。必須 * 定義 ALL_FIELD_VALUES 內容,而且該內容中的 FieldValues * 必須定義 {@link #fieldMetaProperties} 陣列中所列示的 * meta 內容。 * @param future 要與內容表一起顯示的 * 其他視窗元件。(由本範例的延伸規格使用)。 * @return 包含這個方法建立的 GUI 元件的 * RecordFrame 結構。 * @throws WvcmException */ RecordFrame showRecord(String title, CqRecord record, JComponent[] future) throws WvcmException { final StpProperty.List<CqFieldValue<?>> fields = record.getAllFieldValues(); // 定義表格模型,其中每一列都是記錄資源的內容, // 而且每一欄都是此內容的 meta 內容, // 例如其名稱、類型及值; TableModel dataModel = new AbstractTableModel() { private static final long serialVersionUID = 1L; public int getColumnCount() { return fieldMetaProperties.length; } public int getRowCount() { return fields.size();} public Object getValueAt(int row, int col) { try { Object val = fields.get(row) .getMetaProperty((MetaPropertyName<?>) fieldMetaProperties[col].getRoot()); if (val instanceof CqRecord) return ((CqRecord)val).getUserFriendlyLocation() .getName(); else if (val instanceof CqAttachmentFolder) return ((CqAttachmentFolder)val) .getAttachmentList().size() + " attachments"; else return val; } catch(Throwable ex) { if (ex instanceof StpException) { return ((StpException)ex).getStpReasonCode(); } else { String name = ex.getClass().getName(); return name.substring(name.lastIndexOf(".")+1); } } } public String getColumnName(int col) { return fieldMetaProperties[col].getRoot().getName(); } }; // 定義顯示畫面佈置 final JTable table = new JTable(dataModel); final JPanel panel = new JPanel(new BorderLayout()); final JPanel buttons = new JPanel(new FlowLayout()); final JButton button = new JButton("View"); final RecordFrame frame = new RecordFrame(title + record.getUserFriendlyLocation().toString(), table, fields); // 新增按鈕來檢視所選取記錄或附件欄位 buttons.add(button, BorderLayout.SOUTH); button.setEnabled(false); button.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent arg0) { int[] selected = table.getSelectedRows(); for (int i =0; i < selected.length; ++i) { int row = selected[i]; if (isAttachmentList(fields, row)) { view(selectAttachment(frame, fields, row, "View")); } else { view(getRecordReferencedAt(fields, row)); } } } }); // 新增其他按鈕(供後續範例使用) if (future != null) for(int i = 0; i < future.length; ++i) buttons.add(future[i]); table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); // 要求收到選擇變更的通知,並唯有選取了 // 記錄值欄位或附件欄位時才啟用檢視按鈕 ListSelectionModel rowSM = table.getSelectionModel(); rowSM.addListSelectionListener(new ListSelectionListener() { public void valueChanged(ListSelectionEvent e) { if (!e.getValueIsAdjusting()){ int[] selected = table.getSelectedRows(); button.setEnabled(false); for (int i=0; i <selected.length; ++i) if (getRecordReferencedAt(fields, selected[i]) != null || isAttachmentList(fields, selected[i])) { button.setEnabled(true); break; } } } }); panel.add(new JScrollPane(table), BorderLayout.CENTER); panel.add(buttons, BorderLayout.SOUTH); frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); frame.setContentPane(panel); frame.setBounds(g_windowX += 10, g_windowY += 10, 600, 300); frame.setVisible(true); return frame; } protected CqProvider m_provider; } /** * 向每一個記錄欄位值要求的內容,包括 * 附件的其他特定資訊 */ static final PropertyRequest VALUE_PROPERTIES = new PropertyRequest(StpResource.USER_FRIENDLY_LOCATION, CqAttachmentFolder.ATTACHMENT_LIST .nest(CqAttachment.DISPLAY_NAME, CqAttachment.FILE_NAME, CqAttachment.FILE_SIZE, CqAttachment.DESCRIPTION)); /** 要求並顯示的欄位 meta 內容 */ static final NestedPropertyName[] fieldMetaProperties = new PropertyRequest(CqFieldValue.NAME, CqFieldValue.REQUIREDNESS, CqFieldValue.TYPE, CqFieldValue.VALUE.nest(VALUE_PROPERTIES)).toArray(); /** * 從此檢視器要顯示的記錄中讀取資料時所使用的 PropertyRequest。 * 記下用來要求 ALL_FIELD_VALUES 清單中的欄位的 meta 內容的間接層次, * 而不是 ALL_FIELD_VALUES 內容本身的那些 meta 內容。 * */ final static PropertyRequest RECORD_PROPERTIES = new PropertyRequest(CqRecord.USER_FRIENDLY_LOCATION, CqRecord.ALL_FIELD_VALUES .nest(StpProperty.VALUE.nest(fieldMetaProperties))); /** * 檢查欄位的內容值,若該值參照某記錄, * 則傳回所參照記錄的 Proxy。否則傳回空值。 * @param fields 要檢查的 Property.List。 * @param row 清單中要檢查的元素之索引。 * @return 如果欄位參照記錄,則傳回 Record Proxy;否則傳回空值 */ static CqRecord getRecordReferencedAt(StpProperty.List<CqFieldValue<?>> fields, int row) { try { CqFieldValue field = fields.get(row); if (field.getFieldType() == ValueType.RESOURCE && field.getValue() instanceof CqRecord) return (CqRecord)field.getValue(); } catch (WvcmException ex) { ex.printStackTrace(); } return null; } /** * 指示的欄位是否為附件欄位。 * @param fields 要檢查的 Property.List。 * @param row 清單中要檢查的元素之索引。 * @return true 如果位於給定索引的欄位是附件欄位的話 */ static boolean isAttachmentList(StpProperty.List<CqFieldValue<?>> fields, int row) { if (row >= 0) try { CqFieldValue field = fields.get(row); return field.getFieldType() == ValueType.ATTACHMENT_LIST; } catch (WvcmException ex) { ex.printStackTrace(); } return false; } /** * 向使用者呈現與記錄的指定欄位相關聯的附件清單, * 讓使用者能夠選取其中之一。 * * @param frame 這個方法產生的對話框之母框。 * @param fields 要檢查的 Property.List。 * @param row 清單中要檢查的元素之索引。 * @param op 識別會對所選取的附件 * 執行之作業的字串。 * @return 所選取附件的 Attachment Proxy;如果使用者決定不做任何選擇, * 則傳回空值。 */ static CqAttachment selectAttachment(Component frame, StpProperty.List<CqFieldValue<?>> fields, int row, String op) { CqFieldValue field = fields.get(row); try { CqAttachmentFolder folder = (CqAttachmentFolder)field.getValue(); ResourceList<CqAttachment> attachments = setUserFriendlyLocation (folder.doReadProperties(ATTACHMENT_PROPERTIES) .getProperty(CqAttachmentFolder.ATTACHMENT_LIST)); if (attachments.size() > 0) { CqAttachment attachment = (CqAttachment) JOptionPane .showInputDialog(frame, "Choose an Attachment to " + op, op + " Attachment", JOptionPane.INFORMATION_MESSAGE, null, attachments.toArray(), attachments.get(0)); return attachment; } } catch(Throwable t) { Utilities.exception(frame, op + " Attachment", t);} return null; } /** * 要在 {@link #selectAttachment} 所產生的附件選擇清單中顯示的 * 附件內容。 */ final static PropertyRequest ATTACHMENT_PROPERTIES = new PropertyRequest(CqAttachmentFolder.ATTACHMENT_LIST .nest(CqAttachment.DISPLAY_NAME, CqAttachment.FILE_NAME, CqAttachment.FILE_SIZE, CqAttachment.DESCRIPTION, CqAttachment.USER_FRIENDLY_LOCATION)); /** * ViewRecord 範例的主程式。實例化 Provider, * 然後呼叫 ExecuteQuery 範例,傳入會顯示 ClearQuest 記錄之 * 欄位的某一版 Viewer。 * @param args 不使用。 */ public static void main(String[] args) { try { CqProvider provider = Utilities.getProvider().cqProvider(); ExecuteQuery.run("View Record", provider, new Viewer(provider)); } catch(Throwable ex) { Utilities.exception(null, "View Record", ex); System.exit(0); } } /** * 記錄欄位顯示的 JFrame 延伸規格, * 向用戶端顯示頁框的 JTable 元件 * 以及表格中所顯示的欄位清單。 */ static class RecordFrame extends JFrame { RecordFrame(String title, JTable table, StpProperty.List fields) { super(title); m_table = table; m_fields = fields; } JTable m_table; StpProperty.List m_fields; private static final long serialVersionUID = 1L; } /** 要顯示的下一個視窗之 X 偏移 */ private static int g_windowX = 200; /** 要顯示的下一個視窗之 Y 偏移 */ private static int g_windowY = 200;
在這個範例中,ViewRecord.Viewer 不僅用來顯示查詢傳回的記錄,亦可檢視記錄的選擇欄位參照的記錄,以及檢視附加至記錄欄位的檔案。使用者可使用此功能來瀏覽各個記錄的參照。
ViewRecord.view(CqRecord) 會從資料庫中讀取該記錄的所有欄位,及檢視器使用的每個欄位的 meta 內容,並將移入的 Proxy 傳給 showRecord 方法。檢視器使用 ClearQuest® 記錄的 ALL_FIELD_VALUES 內容取得記錄中所有欄位的清單。就各個欄位而言,皆需要 NAME、REQUIREDNESS、TYPE 及 VALUE meta 內容。請注意,這些 meta 內容要求在另一個 VALUE meta 內容要求之下形成巢狀,因此是為 ALL_FIELD_VALUES 內容的值取得這些 meta 內容,而不是為 ALL_FIELD_VALUES 內容本身取得。(請參閱 RECORD_PROPERTIES、VALUE_PROPERTIES 及 fieldMetaProperties 的宣告。)
如果欄位是附件欄位,則也需要該值的 ATTACHMENT_LIST 內容。(如果欄位不是附件欄位,這個內容要求會失敗,但因為唯有欄位是附件時才會存取此內容,所以這種失敗不會導致異常。)
ViewRecord.showRecord 使用與 ExecuteQuery.showResults 相同的 Swing GUI 元件和結構,僅表格的列和欄內容有差異。在此情況下,每一列是記錄的一個欄位,並以 CqFieldValue 物件來表示。每一欄是欄位的 meta 內容。通用 StpProperty.getMetaProperty 介面是用來從每個欄位的 CqFieldValue/StpProperty 結構提取每一個 meta 內容值。但有兩種例外情形,要在記錄視圖中產生 meta 內容的影像,需仰賴每一個 meta 內容值的 toString() 方法。以記錄資源而言,僅顯示 USER_FRIENDLY_LOCATION 內容的名稱欄位,以減少輸出的雜亂情況。以附件欄位而言,僅顯示附件數目,但不顯示每一個附件名稱。
欄位類型 RESOURCE_LIST、JOURNAL、STRING_LIST 及 STRING 的顯示也要特別留意。這留待讀者自行練習。
選取附件欄位並按一下檢視按鈕後,便會呼叫 selectAttachment,且其結果會傳給 ViewRecord.view(CqAttachment)。selectAttachment 方法再次使用 JOptionPane.showInputDialog,向使用者呈現與所選取欄位相關聯的附件清單。附件欄位的值就是附件資料夾資源。與此欄位相關聯的附件是該附件資料夾的連結成員。
ViewRecord.view(CqAttachment) 使用 CqAttachment.doReadContent,將資料庫中的附件檔讀入暫存檔,然後呼叫公用程式方法(全部都是 Swing 程式碼),對使用者顯示該檔案。
ViewRecord.Viewer 也應支援在個別視窗中顯示 RESOURCE_LIST 值。但同樣地,這也留待讀者自行練習。