< 上一個課程 | 下一個課程 >

檢視記錄

在本課程中,您將藉由使用 PropertyRequest 介面,瞭解如何檢視由查詢傳回的記錄之所有欄位。
下列程式碼範例延伸前一個模組中的範例(用於執行查詢),不僅可檢視查詢的顯示欄位中的欄位,還增加可以檢視該查詢所傳回記錄的所有欄位的能力。

它呼叫 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 值。但同樣地,這也留待讀者自行練習。

您可以使用 Rational® CM API 來撰寫用戶端應用程式或公用程式,以便檢視記錄以及其欄位和欄位值。

課程檢查點

Rational CM API 可讓您執行 Rational ClearQuest 特有的許多作業,例如執行查詢、反覆使用結果集、檢視記錄及其欄位。既然您已瞭解如何使用 Rational CM API 來檢視記錄,下一步是要學習如何修改記錄。
在這一課,您學到下列各項:
  • 關於 Rational ClearQuest 資源特有的數個 Rational CM API 介面。
  • 如何撰寫 Rational CM API 用戶端應用程式碼,以擷取記錄、要求內容及檢視欄位值。

意見
< 上一個課程 | 下一個課程 >