The Object Inspector-A Final Example


To help demonstrate how to use dialogs, I'll present one final example-an object inspector. The object inspector accepts an optional argument that identifies the object to inspect. If the object is a UNO object, then the object contains debug properties that list the supported properties, methods , services, and interfaces. The primary purpose of this dialog is to inspect an object to determine what you can do with it.

In this final example, a dialog is created without using the Basic IDE. The inspection information is stored in a multi-line text edit control. I chose to use a text edit control rather than a list box, because the text control allows me to copy the contained text to the clipboard and the list box control does not. I frequently inspect an object and then copy the supported properties, methods, and interfaces to the clipboard so that I can use them as a reference.

Radio buttons dictate what is displayed in the text control. The text control can display the supported properties, methods, interfaces and services, and general inspection information such as the object's data type (see Figure 8 ).

click to expand
Figure 8: The Object Inspector dialog.

Utility Subroutines and Functions

Numerous utility methods are required to generically inspect an object in OOo. The utility methods do not control, access, or update the dialog or any controls so I have separated them from the common functionality. The techniques and methods used here should be somewhat familiar, so I haven't discussed them in depth. All of the routines are contained in the Inspector module in this chapter's source code.

Identifying and Removing White Space From a String

A text character that appears as open space is called a "white space character." For example, in this book there is a single space between each word. The new-line character can create white space between lines. While inspecting an object, the resulting strings frequently contain leading and trailing white space of different types that must be removed. The functions in Listing 18 identify the different types of white space and remove them from a string.

Listing 18: Identify and remove white space from a string.
start example
 '************************************************************************* '** Is the specified character white space? The answer is true if the '** character is a tab, CR, LF, space, or a non-breaking space character! '** These correspond to the ASCII values 9, 10, 13, 32, and 160 '************************************************************************* Function IsWhiteSpace(iChar As Integer) As Boolean   Select Case iChar   Case 9, 10, 13, 32, 160     IsWhiteSpace = True   Case Else     IsWhiteSpace = False   End Select End Function '************************************************************************* '** Find the first character starting at location i% that is not white space. '** If there are none, then the return value is greater than the '** length of the string. ************************************************************************* Function FirstNonWhiteSpace(ByVal i%, s$) As Integer   If i <= Len(s) Then     Do While IsWhiteSpace(Asc(Mid$(s, i, 1)))       i = i + 1       If i > Len(s) Then         Exit Do       End If     Loop   End If   FirstNonWhiteSpace = i End Function '************************************************************************* '** Remove white space text from both the front and the end of a string. '** This modifies the argument string AND returns the modified string. '** This removes all types of white space, not just a regular space. '************************************************************************* Function TrimWhite(s As String) As String   s = Trim(s)   Do While Len(s) > 0     If Not IsWhiteSpace(ASC(s)) Then Exit Do     s = Right(s, Len(s) - 1)   Loop   Do While Len(s) > 0     If Not IsWhiteSpace(ASC(Right(s, 1))) Then Exit Do     s = Left(s, Len(s) - 1)   Loop   TrimWhite = s End Function 
end example
 

Convert a Simple Object to a String

The CStr() method converts most standard data types to a string. Not all variables are easily converted to a string using CStr(), so the ObjToString() method attempts to make an intelligent conversion. The most troublesome variable types are Object and Variant, which can contain the value Empty or Null-these two cases are identified and printed in Listing 19 . Variables of type Object cannot be trivially converted to a String, so they are not. The standard types are converted to a String by Listing 19. The ObjToString() method truncates the string at 100 characters .

Listing 19: ObjToString is in the Inspector module in this chapter's source code.
start example
 Function ObjToString(oInsObj) As String   Dim s As String   Select Case VarType(oInsObj)     Case 0       s = "Empty"     Case 1       s = "Null"     Case 2, 3, 4, 5, 7, 8, 11       s = CStr(oInsObj)     Case Else       s = "[Cannot convert " & TypeName(oInsObj) & " to a string]"   End Select   If Len(s) > 100 Then s = Left(s, 100) & "...."   ObjToString = s End Function 
end example
 

Object Inspection using Basic Methods

The ObjInfoString() method (shown in Listing 20 ) creates a string that describes an object. This string includes detailed information about the object type. If the object is an array, the array dimensions are listed. If possible, even the UNO implementation name is obtained. No other UNO- related information-such as supported properties or methods-is obtained.

Listing 20: ObjInfoString is in the Inspector module in this chapter's source code.
start example
 Function ObjInfoString(oInsObj) As String   Dim s As String   REM We can always get the type name and variable type.   s = "TypeName = " & TypeName(oInsObj) & CHR$(10) &_     "VarType = " & VarType(oInsObj) & CHR$(10)   s = s & "Value = " & ObjToString(oInsObj) & CHR$(10)   REM Check for NULL and EMPTY   If IsNull(oInsObj) Then     s = s & "IsNull = True"   ElseIf IsEmpty(oInsObj) Then     s = s & "IsEmpty = True"   Else     If IsObject(oInsObj) Then       On Local Error GoTo DebugNoSet       s = s & "Implementation Name = "       Dim oTmpObj       oTmpObj = oInsObj       s = s & oTmpObj.getImplementationName()       DebugNoSet:       On Local Error Goto 0       s = s & CHR$(10) & "IsObject = True" & CHR$(10)     End If     If IsUnoStruct(oInsObj) Then s = s & "IsUnoStruct = True" & CHR$(10)     If IsDate(oInsObj) Then s = s & "IsDate = True" & CHR$(10)     If IsNumeric(oInsObj) Then s = s & "IsNumeric = True" & CHR$(10)     If IsArray(oInsObj) Then       On Local Error Goto DebugBoundsError:       Dim i%, sTemp$       s = s & "IsArray = True" & CHR$(10) & "range = ("       Do While (i% >= 0)         i% = i% + 1         sTemp$ = LBound(oInsObj, i%) & " To " & UBound(oInsObj, i%)         If i% > 1 Then s = s & ", "         s = s & sTemp$       Loop       DebugBoundsError:       On Local Error Goto 0       s = s & ")" & CHR$(10)     End If   End If   ObjInfoString = s End Function 
end example
 

An interesting extension to the ObjInfoString() method would be to allow it to recognize an array and to display array contents as well. This is left as an exercise for the reader.

Sort an Array

Many objects contain so many properties and implement so many methods, that when they are displayed in the text box, it is difficult to find a specific entry. Sorting the output makes it easier to find individual entries.

Assume that I have two arrays. The first array contains a list of names and the second array contains a list of ages. The third item in the age array contains the age of the third item in the name array. In computer science, these are referred to as "parallel arrays." Parallel arrays are used in the object browser. When dealing with properties, the property type is stored in the first array, and the property name is stored in the second array. The same thing is done while dealing with the supported methods.

While creating the data for inspection, parallel arrays are created and maintained . I want to sort the information, but I can't sort one array unless I sort all of the arrays. A typical solution to this problem is to create an index array that contains integers, which are used to index into the array. The index array is sorted rather than the actual data. For example, sort the array oItems()=("B", "C", "A"). The sorted index array is (2, 0, 1), implying that oltems(2) is the first item, oItems(0) is the second item, and oItems(1) is the last item. The function SortMyArray() in Listing 21 sorts an array by using an index array.

Listing 21: SortMyArray is in the Inspector module in this chapter's source code.
start example
 Sub SortMyArray(oItems(), iIdx() As Integer)   Dim i As Integer        'Outer index variable   Dim j As Integer        'Inner index variable   Dim temp As Integer     'Temporary variable to swap two values.   Dim bChanged As Boolean 'Becomes True when something changes   For i = LBound(oItems()) To UBound(oItems()) - 1     bChanged = False     For j = UBound(oItems()) To i+1 Step -1       If oItems(iIdx(j)) < oItems(iIdx(j-1)) Then         temp = iIdx(j)         iIdx(j) = iIdx(j-1)         iIdx(j-1) = temp         bChanged = True       End If     Next     If Not bChanged Then Exit For   Next End Sub 
end example
 

Creating a Dialog at Run Time

The Object Inspector uses Private variables for the dialog and frequently referenced objects. The dialog contains a progress bar that is updated while information is retrieved to fill the text control. The inspected object is also stored as a global variable because it is used in event handlers that have no other way to obtain the object. See Listing 22 .

Listing 22: Global variables defined in the Inspector module.
start example
 Option Explicit Private oDlg         'Displayed dialog Private oProgress    'Progress control model Private oTextEdit    'Text edit control model that displays information Private oObj         'Currently inspected object 
end example
 

Unfortunately, dialogs created in the IDE can only be created and run from within Basic. It is possible, however, to create and use a dialog at run time in any programming language that can use the OOo UNO API. The dialog functionality is scheduled to be improved in OOo 2.0 so that you can design dialogs in an IDE and display them using languages other than Basic. The Inspect() method in Listing 23 creates a dialog and all of its contained controls without using the IDE. Follow these steps to create and display the dialog:

  1. Use CreateUnoService("com.sun.star.awt.UnoControlDialogModel") to create a dialog model and then set the model's properties.

  2. Use the createInstance(name) method in the dialog model to create a model for each control that is inserted into the dialog model. Set the properties on the control's model and then use the insertByName(name, oModel) method-defined by the dialog model-to insert the control's model into the dialog's model. This step is important! To insert a control into a dialog, you must use the dialog model to create a control model, which is then inserted into the dialog model. You don't create or manipulate controls, only models.

  3. Use CreateUnoService("com.sun.star.awt.UnoControlDialog") to create a dialog.

  4. Use the setModel() method on the dialog to set the dialog's model.

  5. Use CreateUnoListener(name) to create any desired listeners. Add the listener to the correct object; the correct object is usually a control.

  6. Use CreateUnoService("com.sun.star.awt.Toolkit") to create a window toolkit object.

  7. Use the createPeer(oWindow, null) method on the dialog to tell the dialog to create an appropriate window to use.

  8. Execute the dialog.

Listing 23: Create the dialog, the controls, and the listeners.
start example
 Sub Inspect(Optional oInsObj)   Dim oDlgModel            'The dialog's model   Dim oModel               'Model for a control   Dim oListener            'A created listener object   Dim oControl             'References a control   Dim iTabIndex As Integer 'The current tab index while creating controls   Dim iDlgHeight As Long   'The dialog's height   Dim iDlgWidth As Long    'The dialog's width   REM If no object is passed in, then use ThisComponent.   If IsMissing(oInsObj) Then     oObj = ThisComponent   Else     oObj = oInsObj   End If   iDlgHeight = 300   iDlgWidth  = 350   REM Create the dialog's model   oDlgModel = CreateUnoService("com.sun.star.awt.UnoControlDialogModel")   setProperties(oDlgModel, Array("PositionX", 50, "PositionY", 50,_      "Width", iDlgWidth, "Height", iDlgHeight, "Title", "Object Inspector"))   createInsertControl(oDlgModel, iTabIndex, "PropButton",_      "com.sun.star.awt.UnoControlRadioButtonModel",_      Array("PositionX", 10, "PositionY", 10, "Width", 50, "Height", 15,_        "Label", "Properties"))   createInsertControl(oDlgModel, iTabIndex, "MethodButton",_      "com.sun.star.awt.UnoControlRadioButtonModel",_      Array("PositionX", 65, "PositionY", 10, "Width", 50, "Height", 15,_        "Label", "Methods"))   createInsertControl(oDlgModel, iTabIndex, "ServiceButton",_      "com.sun.star.awt.UnoControlRadioButtonModel",_      Array("PositionX", 120, "PositionY", 10, "Width", 50, "Height", 15,_        "Label", "Services"))   createInsertControl(oDlgModel, iTabIndex, "ObjectButton",_      "com.sun.star.awt.UnoControlRadioButtonModel",_      Array("PositionX", 175, "PositionY", 10, "Width", 50, "Height", 15,_        "Label", "Object"))   createInsertControl(oDlgModel, iTabIndex, "EditControl",_      "com.sun.star.awt.UnoControlEditModel",_      Array("PositionX", 10, "PositionY", 25, "Width", iDlgWidth - 20,_        "Height", (iDlgHeight - 75), "HScroll", True, "VScroll", True,_        "MultiLine", True, "HardLineBreaks", True))   REM Store the edit control's model in a global variable.   oTextEdit = oDlgModel.getByName("EditControl")   createInsertControl(oDlgModel, iTabIndex, "Progress",_      "com.sun.star.awt.UnoControlProgressBarModel",_      Array("PositionX", 10, "PositionY", (iDlgHeight - 45) ,_        "Width", iDlgWidth - 20, "Height", 15, "ProgressValueMin", 0,_        "ProgressValueMax", 100))   REM Store a reference to the progress bar   oProgress = oDlgModel.getByName("Progress")   REM Notice that I set the type to OK so I do not require an action   REM listener to close the dialog.   createInsertControl(oDlgModel, iTabIndex, "OKButton",_      "com.sun.star.awt.UnoControlButtonModel",_      Array("PositionX", Clng(iDlgWidth / 2 - 25), "PositionY", iDlgHeight-20,_        "Width", 50, "Height", 15, "Label", "OK",_        "PushButtonType", com.sun.star.awt.PushButtonType.OK))   REM Create the dialog and set the model   oDlg = CreateUnoService("com.sun.star.awt.UnoControlDialog")   oDlg.setModel(oDlgModel)   REM The item listener for all of the radio buttons   oListener = CreateUnoListener("radio_", "com.sun.star.awt.XItemListener")   oControl = oDlg.getControl("PropButton")   ocontrol.addItemListener(oListener)   oControl = oDlg.getControl("MethodButton")   ocontrol.addItemListener(oListener)   oControl = oDlg.getControl("ServiceButton")   ocontrol.addItemListener(oListener)   oControl = oDlg.getControl("ObjectButton")   ocontrol.addItemListener(oListener)   oControl.getModel().State = 1   REM Now, set the dialog to contain the standard dialog information   DisplayNewObject()   REM Create a window and then tell the dialog to use the created window   Dim oWindow   oWindow = CreateUnoService("com.sun.star.awt.Toolkit")   oDlg.createPeer(oWindow, null)   REM Finally, execute the dialog   oDlg.execute() End Sub 
end example
 

To shorten the code in Listing 23, try using two utility methods to set properties, create a control model, and insert the control model into the dialog model. Notice that the control itself is never created-only the model is created. Listing 24 contains the methods to create the control models and to set the properties. Although properties are set using the setPropertyValue method, in Basic you can set the properties directly if desired.

Listing 24: Create a control model and insert it into the dialog's model.
start example
 Sub createInsertControl(oDlgModel, index%, sName$, sType$, props())   Dim oModel   oModel = oDlgModel.createInstance(sType$)   setProperties(oModel, props())   setProperties(oModel, Array("Name", sName$, "TabIndex", index%))   oDlgModel.insertByName(sName$, oModel)   REM This changes the value because it is not passed by value.   index% = index% + 1 End Sub REM Generically set properties based on an array of name/value pairs. Sub setProperties(oModel, props())   Dim i As Integer   For i=LBound(props()) To UBound(props()) Step 2     oModel.setPropertyValue(props(i), props(i+1))   Next End sub 
end example
 

Listeners

The only required listener is for the radio buttons. Each time a new radio button is selected, the method radio_itemStateChanged() is called (the listener is set up in Listing 23). The event handler calls the DisplayNewObject() subroutine, which handles the actual work when a new property type is requested . See Listing 25 . I wrote a separate routine so that it can be used apart from the event handler. For example, it calls DisplayNewObject() before the dialog is displayed so that the dialog contains useful information when it first appears.

Listing 25: The event handler is very simple; it calls DisplayNewObject().
start example
 Sub radio_itemStateChanged(oItemEvent)   DisplayNewObject() End Sub Sub DisplayNewObject()   REM Reset the progress bar!   oProgress.ProgressValue = 0   oTextEdit.Text = ""   On Local Error GoTo IgnoreError:   If IsNull(oObj) OR IsEmpty(oObj) Then     oTextEdit.Text = ObjInfoString(oObj)   ElseIf oDlg.getModel().getByName("PropButton").State = 1 Then     processStateChange(oObj, "p")   ElseIf oDlg.getModel().getByName("MethodButton").State = 1 Then     processStateChange(oObj, "m")   ElseIf oDlg.getModel().getByName("ServiceButton").State = 1 Then     processStateChange(oObj, "s")   Else     oTextEdit.Text = ObjInfoString(oObj)   End If   oProgress.ProgressValue = 100   IgnoreError: End Sub 
end example
 

Obtaining the Debug Information

In Listing 25, notice that if the properties, methods, or services are requested, then the processStateChange() subroutine (see Listing 26 ) is called. If properties, methods, or services are not requested, simple object information is added to the text control.

Listing 26: ProcessStateChange is in the Inspector module of this chapter's source code.
start example
 Sub processStateChange(oInsObj, sPropType$)   Dim oItems()   BuildItemArray(oInsObj, sPropType$, oItems())   If sPropType$ = "s" Then     Dim s As String     On Local Error Resume Next     s = "** INTERFACES **" & CHR$(10) & Join(oItems, CHR$(10))     s = s & CHR$(10) & CHR$(10) & "** SERVICES **" & CHR$(10) & _       Join(oInsObj.getSupportedServiceNames(), CHR$(10))     oTextEdit.Text = s   Else     oTextEdit.Text = Join(oItems, CHR$(10))   End If End Sub 
end example
 

Again, the processStateChange() subroutine contains very little logic. The primary purpose of Listing 26 is to add the supported services to the end of the text control after the interfaces are obtained from the dbg_SupportedInterfaces property.

The BuildItemArray() subroutine contains the majority of the important code for this dialog. First, the code inspects the dbg_ properties to obtain the list of properties, methods, or interfaces as a single string. It then separates the individual items from the single string and sorts them. If properties are displayed, the property set information object is used to obtain the value of each supported property. This allows you to see both the property name and its value. Although the property set information object does not support all of the properties returned by the property dbg_properties, it supports most of them. The individual data is returned as an array of strings in the oItems() array. See Listing 27 for an example.

Listing 27: BuildItemArray is in the Inspector module of this chapter's source code.
start example
 Sub BuildItemArray(oInsObj, sType$, oItems())   On Error Goto BadErrorHere   Dim s As String           'Primary list to parse   Dim sSep As String        'Separates the items in the string   Dim iCount%               '   Dim iPos%                 '   Dim sNew$                 '   Dim i%                    '   Dim j%                    '   Dim sFront() As String    'When each piece is parsed, this is the front   Dim sMid() As String      'When each piece is parsed, this is the middle   Dim iIdx() As Integer     'Used to sort parallel arrays.   Dim nFrontMax As Integer  'Maximum length of front section   nFrontMax = 0   REM First, see what should be inspected.   If sType$ = "s" Then     REM Dbg_SupportedInterfaces returns interfaces and     REM getSupportedServiceNames() returns services.     s = oInsObj.Dbg_SupportedInterfaces     's = s & Join(oInsObj.getSupportedServiceNames(), CHR$(10))     sSep = CHR$(10)   ElseIf sType$ = "m" Then     s = oInsObj.DBG_Methods     sSep = ";"   ElseIf sType$ = "p" Then     s = oInsObj.DBG_Properties     sSep = ";"   Else     s = ""     sSep = ""   End If   REM The dbg_ variables have some introductory information that   REM we do not want, so remove it.   REM We only care about what is after the colon.   iPos = InStr(1, s, ":") + 1   If iPos > 0 Then s = TrimWhite(Right(s, Len(s) - iPos))   REM All data types are prefixed with the text Sbx.   REM Remove all of the "Sbx" charcters   s = Join(Split(s, "Sbx"), "")   REM If the separator is NOT CHR$(10), then remove   REM all instances of CHR$(10).   If ASC(sSep) <> 10 Then s = Join(Split(s, CHR$(10)), "")   REM Split on the separator character and update the progress bar.   oItems() = Split(s, sSep)   oProgress.ProgressValue = 20   Rem Create arrays to hold the different portions of the text.   Rem The string usually contains text similar to "SbxString getName()"   Rem sFront() holds the data type if it exists and "" if it does not.   Rem sMid() holds the rest.   ReDim sFront(UBound(oItems)) As String   ReDim sMid(UBound(oItems))   As String   ReDim iIdx(UBound(oItems))   As Integer   REM Initialize the index array and remove leading and trailing   REM spaces from each string.   For i=LBound(oItems()) To UBound(oItems())     oItems(i) = Trim(oItems(i))     iIdx(i) = i     j = InStr(1, oItems(i), " ")     If (j > 0) Then       REM If the string contains more than one word, the first word is stored       REM in sFront() and the rest of the string is stored in sMid().       sFront(i) = Mid$(oItems(i), 1, j)       sMid(i) = Mid$(oItems(i), j+1)       If j > nFrontMax Then nFrontMax = j     Else       REM If the string contains only one word, sFront() is empty       REM and the string is stored in sMid().       sFront(i) = ""       sMid(i) = oItems(i)     End If   Next   oProgress.ProgressValue = 40   Rem Sort the primary names. The array is left unchanged, but the   Rem iIdx() array contains index values that allow a sorted traversal.   SortMyArray(sMid(), iIdx())   oProgress.ProgressValue = 50   REM Dealing with properties so attempt to find the value   REM of each property.   If sType$ = "p" Then     Dim oPropInfo  'PropertySetInfo object     Dim oProps     'Array of properties     Dim oProp      'com.sun.star.beans.Property     Dim v          'Value of a single property     Dim ss As String     On Error Goto NoPropertySetInfo     oPropInfo = oInsObj.getPropertySetInfo()     For i=LBound(sMid()) To UBound(sMid())       If oPropInfo.hasPropertyByName(sMid(i)) Then         v = oInsObj.getPropertyValue(sMid(i))         sMid(i) = sMid(i) & " = " & ObjToString(v)       End If     Next     NoPropertySetInfo:   End If   oProgress.ProgressValue = 60   nFrontMax = nFrontMax + 1   iCount = LBound(oItems())   REM Now build the array of the items in sorted order.   REM Sometimes, a service is listed more than once.   REM This routine removes multiple instances of the same service.   For i = LBound(oItems()) To UBound(oItems())     sNew = sFront(iIdx(i)) & " " & sMid(iIdx(i))     'Uncomment these lines if you want to add uniform space     'between front and mid. This is only useful if the font is fixed in width.     'sNew = sFront(iIdx(i))     'sNew = sNew & Space(nFrontMax - Len(sNew)) & sMid(iIdx(i))     If i = LBound(oItems()) Then       oItems(iCount) = sNew     ElseIf oItems(iCount) <> sNew Then       iCount = iCount + 1       oItems(iCount) = sNew     End If   Next   oProgress.ProgressValue = 75   ReDim Preserve oItems(iCount)   Exit Sub BadErrorHere:   MsgBox "Error " & err & ": " & error$ + chr(13) + "In line : " + Erl End Sub 
end example
 

You can use the object inspector to investigate objects when you aren't certain exactly what you can do with them. You might want to copy this to an application-level library so that you can access the macro from all of your other macros.




OpenOffice.org Macros Explained
OpenOffice.org Macros Explained
ISBN: 1930919514
EAN: 2147483647
Year: 2004
Pages: 203

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net