Close

Relief for ODA Team in Ukraine

Learn more
ODA Drawings SDK
Example of Working with the Layout Dictionary Object

This example demonstrates working with the layout dictionary object for adding, renaming, deleting, modifying, and printing layout objects. This example uses a console menu that represents the following operations: printing a list of the accessible layouts [L], printing specific properties of all layouts contained in the dictionary [P], printing a list of the associated blocks [B], exposing predefined layouts [E], creating a new layout and adding it in the dictionary [A], renaming an existing layout [N], setting a layout to be active [S], switching the current layout tab [T], displaying the system variable information [V], deleting an existing layout [D], and modifying the specific properties of a selected layout [M].


#include "..\Common\DemoMain.h"
#include "DbLayout.h"
#include "DbDictionary.h"
#include "DbBlockTable.h"
#include "DbBlockTableRecord.h"
#include <iostream>
using namespace std;

// Example of Working with the Layout Object
void PrintLayoutProperties(OdDbLayout* pLayout);
void ModifyLayoutProperties(OdDbLayout* pLayout);

// Example of Entering and Displaying 3D Point Objects
bool EntryPoint3d(OdGePoint3d& point);
OdString AboutPoint3d(const OdGePoint3d& point);

// Example of Entering and Displaying 2D Point Objects
bool EntryPoint2d(OdGePoint2d& point);
OdString AboutPoint2d(const OdGePoint2d& point);

// Example of Using the Record-Table and Dictionary Interfaces for Entering Names
OdString EntryRecordName(OdDbObject* pCollection, enum enumCheckName isExist = kDontCheck);

// Example of Using the Record–Table and Dictionary Interfaces for Getting Information about Objects
OdString AboutDbObject(OdDbObjectId idObject);

void PrintObjectProperties(OdDbObject* pObject);

The ModifyLayouts() function requires a pointer to the database object in which it manipulates the layouts. The function organizes a loop that inquires about the operation code and uses the switch statement to select whether the case must be performed. The function processes user actions in the loop until the user selects the [Q] operation. The function uses the PrintLayoutProperties() function for displaying the specific properties of a layout object, the ModifyLayoutProperties() function for modifying the properties of an existing layout object, and the EntryRecordName() function for entering the layout name and checking whether this name is correct and exists or is absent from the dictionary. The function obtains the smart pointer to the layout dictionary using the getLayoutDictionaryId() method of the passed database object. Before the loop, the function declares a smart pointer to the iterator and a smart pointer to the layout object.

When the user selects [L], the function displays a list of the layout names that are accessible in the dictionary. The function uses an iterator for traversing through the dictionary and the name() method of the iterator for displaying the name of each layout object to which the iterator refers.

When the user selects [P], the function prints the specific properties of all layout objects contained in the dictionary. The function uses an iterator for traversing through the dictionary and the getObject() method of the iterator for getting the smart pointer of the layout object to which the iterator refers. The function displays the common object properties using the PrintObjectProperties() function and the specific layout properties using the PrintLayoutProperties() function. To traverse through the dictionary, the function creates a new iterator for each traverse using the newIterator() method, organizes a loop if the traverse is not completed using the done() method, moves the iterator to the next layout object using the next() method, and gets the pointer to each layout instance using the getObject() method of the iterator and get() method of the smart pointer.

When the user selects [B], the function displays a list of the block names that are associated with layouts. The function obtains the smart pointer to the block table object in which blocks are stored using the getBlockTableId() method of the passed database object. The function uses an iterator for traversing through the block table, the getRecord() method of the iterator for getting a smart pointer of the block record object from the block table, the isLayout() method of the block record object for checking whether this block is associated with a layout, the getName() method of the block record object for displaying its name, and the getLayoutId() method of the block record object for getting the ID of the associated layout object. The function uses the AboutDbObject() function for displaying information about associated objects using its object IDs.

When the user selects [A], the function inquires about the layout name using the EntryRecordName() function. The name must be absent from the dictionary because the dictionary cannot contain duplicate names. The function passes the kMustAbsent value as an argument for checking the new name. If the entered name is correct and is absent, the function suggests two variants for creating a new layout:

  • When the operation is [a], the function calls the static pseudo-constructor of the layout object, assigns the specified name using its setLayoutName() method, adds the created layout object in the dictionary, and specifies the same name for it as the key using the setAt() method of the dictionary object. Then the function calls the static pseudo-constructor of the block record object to be associated with the new layout object, assigns the specified name using the setName() method of the block, and adds the created block record in the block table using the add() method of the block table object. Next the function sets the tab order for the new layout using its setTabOrder() method and countLayouts() method of the database object and associates the new layout object with the new block record object using the setBlockTableRecordId() method of the layout object, passing to it the ID of the new block record object and the setLayoutId() method of the block record object passing to it the ID of the new layout object.
  • When the operation is [A], the function uses the createLayout() method of the database object, passing to it the entered name. The createLayout() method creates the new layout and new block and associates them together. The function uses the try…catch statement to catch exceptions when the object is added. If the addition is successful, the function goes to the [L] operation for printing the layout list. If an exception occurs, the function displays an error message and breaks to the console menu.

When the user selects [N], the function inquires about the name of the existing layout object to be renamed and prompts for a new name using the EntryRecordName() function. The first name must exist in the dictionary; the function passes the kMustExist value as an argument for checking the entered name. The second name must be absent from the dictionary because it cannot contain duplicates; the function passes the kMustAbsent value as an argument for checking the new name. If both names are correct, the function suggests two variants for renaming:

  • When the operation is [n], the function calls the setLayoutName() method, passing to it the new name as an argument. This method also changes the dictionary key of the layout object.
  • When the operation is [N], the function calls the renameLayout() of the database object, passing to it the old name and new name as arguments. The function uses the try…catch statement to catch exceptions when the object changes names. If an exception occurs, the function displays an error message and breaks to the console menu.

When the user selects [T], the function requests the layout type and prompts: M-model or P-paper. The function calls the setTILEMODE() method of the database object and passes to it a True value if the user enters 'M' or a False value if the user enters 'P'. After setting, the function checks and displays the current layout type using the getTILEMODE() method of the database.

When the user selects [E], the function displays the current active layout object using the currentLayoutId() method and the block record object using the getActiveLayoutBTRId() method of the database object. These methods return the object IDs. The function gets smart pointers using the safeOpenObject() method and displays the names using the getName() and getLayoutName() methods.

When the user selects [S], the function inquires about the name of the existing layout object which should be active using the EntryRecordName() function and passes to it the kMustExist value as an argument for checking the name. If the entered name is correct, the function gets the ID of the layout object using the getAt() method, calls the setCurrentLayout() method of the database object, passing to it the ID as an argument, and goes to the [E] operation. The setCurrentLayout() method makes the specified layout active and switches the layout type for TILEMODE if it is necessary.

When the user selects [D], the function inquires about the name of the existing layout object to be deleted using the EntryRecordName() function. The name must exist in the dictionary; the function passes the kMustExist value as an argument for checking the entered name. If the entered name is correct and exists, the function suggests two variants for deleting:

  • When the operation is [d], the function calls the remove() method of the dictionary object and passes to it the name as an argument to remove the layout object from the dictionary. The removed layout object continues to exist outside of the dictionary. To delete the removed object, the function uses the safeOpenObject() method to get a smart pointer to it and calls the erase() method of the object, passing to it a True value as an argument. After deletion, the function checks the result returned by the erase() method. If the result is non-zero, the function displays an error message using the getErrorDescription() method of the host object.
  • When the operation is [D], the function calls the deleteLayout() method of the database object, passing to it the layout name and uses the try…catch statement to catch exceptions when the object is deleted.

When the user selects [M], the function inquires about the name of the existing layout object to be modified using the EntryRecordName() function. The name must exist in the dictionary; the function passes the kMustExist value as an argument for checking the entered name. If the entered name is correct and exists, the function gets a smart pointer to the layout object specified by the name using the getAt() method and opens it in write mode. Then, the function calls the ModifyLayoutProperties() function that organizes its own console menu for modifying the specific properties of the specified layout object.

When the user selects [V], the function displays the predefined layout settings using methods of the database object:

  • The getModelSpaceId() method returns the ID of the model space block record object.
  • The getPaperSpaceId() method returns the ID of the paper space block record object.
  • The getEXTMIN() and getEXTMAX() methods return the minimum and maximum extents of the current model layout.
  • The getPEXTMIN() and getPEXTMAX() methods return the minimum and maximum extents of the current paper layout.
  • The getLIMMIN() and getLIMMAX() methods return the lower-left and upper-right limits of the current model layout.
  • The getPLIMMIN() and getPLIMMAX() methods return the lower-left and upper-right limits of the current paper layout.
  • The getLIMCHECK() method returns the outside limits status for current model layout.
  • The getPLIMCHECK() method returns the outside limits status for current paper layout.

To get the current active layout, this operation uses the getSysVar() method, passing to it the "CTAB" name. The function uses the AboutDbObject() function for printing information about objects obtained as an object ID.

When the user selects [Q], the function ends the loop and returns to the calling function.

The ModifyLayouts() function has the following implementation:


void ModifyLayouts(OdDbDatabase* pDb)
{
  wchar_t ch = L'L';
  OdString sName, sNewName;
  OdResult result;

  OdDbDictionaryPtr pLayouts = pDb->getLayoutDictionaryId().safeOpenObject(OdDb::kForWrite);
  OdDbDictionaryIteratorPtr itLayout;
  OdDbLayoutPtr pLayout;
  OdDbBlockTablePtr pBlocks = pDb->getBlockTableId().safeOpenObject(OdDb::kForWrite);
  OdDbSymbolTableIteratorPtr itBlock = pBlocks->newIterator();
  OdDbBlockTableRecordPtr pBlock;

  wcout << L"\n\nStart testing of layouts";
  wcout << L"\nLayout Class: " << OdDbLayout::desc()->name() 
        << L", Container: " << pLayouts->handle().ascii()
        << L", " << pLayouts->isA()->name() << L"\n";

  do {
    switch(ch)
    {
      case L'A':          // Add a new layout
      case L'a':
        wcout << L"\nEntry the name of a new layout:>";
        if((sName = EntryRecordName(pLayouts, kMustAbsent)).isEmpty()) break;

        try {
          if(ch == L'a')  // Creating through dictionary interface
          {
            pLayout = OdDbLayout::createObject();
            pLayout->setLayoutName(sName);   
            pLayouts->setAt(sName, pLayout);

            pBlock = OdDbBlockTableRecord::createObject();
            pBlock->setName(sName);     
            pBlocks->add(pBlock);
  
            pLayout->setTabOrder( pDb->countLayouts() );
            pLayout->setBlockTableRecordId( pBlock->objectId() );
            pBlock->setLayoutId( pLayout->objectId() );
          }
          else            // Creating through database interface
            pDb->createLayout(sName);    
        }
        catch(OdError& e)
        {
          wcout << L"\nError: " << e.code() << L" - " << e.description() << L"\n";
          pLayout->erase();
          pBlock->erase();
          break;
        }


      case L'L':          // List the accessible layouts
      case L'l':
        wcout << L"\nList of layouts:\n"; 
        for(itLayout = pLayouts->newIterator() ; !itLayout->done() ; itLayout->next())
          wcout << L"\"" << itLayout->name() << L"\"\n";
        break;


      case L'P':          // Print the specific properties of all layouts
      case L'p':
        wcout << L"\nList of layouts:"; 
        for(itLayout = pLayouts->newIterator() ; !itLayout->done() ; itLayout->next())
        {
          pLayout = itLayout->getObject();
          wcout << L"\nH=" << pLayout->handle().ascii();
          PrintObjectProperties(pLayout);
          wcout << L"\n  ------ Specific Properties";
          PrintLayoutProperties(pLayout.get());
        }
        wcout << L"\n";        
        break;


      case L'B':          // List the associated blocks
      case L'b':
        wcout << L"\nList of associated blocks:\n"; 
        for(itBlock->start() ; !itBlock->done() ; itBlock->step())
        {
          pBlock = itBlock->getRecord();
          if(pBlock->isLayout())
          wcout << L"\"" << pBlock->getName() << L"\", " 
                << AboutDbObject( pBlock->getLayoutId() ) << "\n";
        }
        break;


      case L'N':          // Rename the selected layout
      case L'n':
        wcout << L"\nEntry the name of the layout to be renamed:>" ;
        if((sName = EntryRecordName(pLayouts, kMustExist)).isEmpty()) break;

        pLayout = pLayouts->getAt(sName, OdDb::kForWrite);

        wcout << L"\nEntry the new name of the layout:>";
        if((sNewName = EntryRecordName(pLayouts, kMustAbsent)).isEmpty()) break;

        try {
          if(ch == L'n')  // Renaming through dictionary interface
            pLayout->setLayoutName(sNewName);
          else            // Renaming through database interface
            pDb->renameLayout(sName, sNewName);
        }
        catch(OdError& e) { wcout << L"\nError: " << e.code() << L" - " << e.description() << L"\n"; }
        break;


      case L'D':          // Delete the selected layout
      case L'd':
        wcout << L"\nEntry the name of the layout to be deleted:>" ;
        if((sName = EntryRecordName(pLayouts, kMustExist)).isEmpty()) break;

        if(ch == L'd')    // Deleting through dictionary interface
        {
          pLayout = pLayouts->remove(sName).safeOpenObject(OdDb::kForWrite);

          result = pLayout->erase(true);

          if(result != eOk) wcout << L"\nError: " << result << L" - " 
                                  << pDb->appServices()->getErrorDescription(result) << L"\n"; 
        }
        else              // Deleting through database interface
        { try {
            pDb->deleteLayout(sName);
          }
          catch(OdError& e) { wcout << L"\nError: " << e.code() << L" - " << e.description() << L"\n"; }
        }
        break;


      case L'M':          // Modify the selected layout
      case L'm':
       	wcout << L"\nEntry the name of the layout to be modified:>" ;
        if((sName = EntryRecordName(pLayouts, kMustExist)).isEmpty()) break;

        pLayout = pLayouts->getAt(sName, OdDb::kForWrite);

        ModifyLayoutProperties(pLayout.get());
        break;


      case L'T':          // Switch the current tab 
      case L't':
        wcout << L"\nSwitch the current tab [M-model|P-paper]:>";
        wcin >> ch;

        if(ch == L'M' || ch == L'm') pDb->setTILEMODE(true);
        else if(ch == L'P' || ch == L'p') pDb->setTILEMODE(false);
        wcout << L"Current tab is set to " << (pDb->getTILEMODE() ? L"Model" : L"Layout") << L"\n";
        break; 


      case L'S':          // Set the current active layout
      case L's':
        wcout << L"\nEntry the layout name to be set active:>";
        if((sName = EntryRecordName(pLayouts, kMustExist)).isEmpty()) break;

        pDb->setCurrentLayout(pLayouts->getAt(sName));


      case L'E':          // Expose the predefined layouts
      case L'e':
        pLayout = pDb->currentLayoutId().safeOpenObject();
        pBlock = pDb->getActiveLayoutBTRId().safeOpenObject();
        wcout << L"\nCurrent block record: \"" << pBlock->getName() << L"\"";
        wcout << L"\nCurrent active layout: \"" << pLayout->getLayoutName() << L"\"";
        break;


      case L'V':          // Print the system variable information 
      case L'v':
        wcout << L"\nSystem variable information:"
              << L"\nCurrent tab type: " << (pDb->getTILEMODE() ? L"Model" : L"Layout")
              << L"\nCurrent tab object: " << AboutDbObject(pDb->getSysVar(L"CTAB")->getObjectId(pDb))
              << L"\nModel space object: " << AboutDbObject(pDb->getModelSpaceId())
              << L"\nPaper space object: " << AboutDbObject(pDb->getPaperSpaceId())
              << L"\nCurrent model minimum extents: " << AboutPoint3d(pDb->getEXTMIN())
              << L"\nCurrent model maximum extents: " << AboutPoint3d(pDb->getEXTMAX())
              << L"\nCurrent model lower-left limits: " << AboutPoint2d(pDb->getLIMMIN())
              << L"\nCurrent model upper-right limits: " << AboutPoint2d(pDb->getLIMMAX())
              << L"\nObjects outside model limits are " << ((pDb->getLIMCHECK()) ? L"disallowed" : L"allowed")
              << L"\nDefault elevation in model space: " << pDb->getELEVATION()
              << L"\nCurrent paper minimum extents: " << AboutPoint3d(pDb->getPEXTMIN())
              << L"\nCurrent paper maximum extents: " << AboutPoint3d(pDb->getPEXTMAX())
              << L"\nCurrent paper lower-left limits: " << AboutPoint2d(pDb->getPLIMMIN())
              << L"\nCurrent paper upper-right limits: " << AboutPoint2d(pDb->getPLIMMAX())
              << L"\nObjects outside paper limits are " << ((pDb->getPLIMCHECK()) ? L"disallowed" : L"allowed")
              << L"\nDefault elevation in paper space: " << pDb->getPELEVATION()
              << L"\n";
        break; 


      case L'\0':         // Skip an action
        break;

      default:
        wcout << L"Incorrect operation\n";
    }
    wcout << L"\nA. Add a new layout"
          << L"\nT. Switch the current tab"
          << L"\nN. Rename the selected layout"
          << L"\nM. Modify the selected layout"
          << L"\nD. Delete the selected layout"
          << L"\nB. List the associated blocks"
          << L"\nL. List the accessible layouts"
          << L"\nE. Expose the predefined layouts"
          << L"\nS. Set the current active layout"
          << L"\nP. Print the properties of all layouts"
          << L"\nV. Print the system variable information"
          << L"\nQ. Quit";
    wcout << L"\nSelect operation:>";
    wcin >> ch;
  }
  while(ch != L'Q' && ch != L'q');

  wcout << L"Stop testing of layouts\n";
}

Testing gives the following results:


Start testing of layouts
Layout Class: AcDbLayout, Container: 1A, AcDbDictionary

List of layouts:
"Model"
"Layout1"
"Layout2"

A. Add a new layout
T. Switch the current tab
N. Rename the selected layout
M. Modify the selected layout
D. Delete the selected layout
B. List the associated blocks
L. List the accessible layouts
E. Expose the predefined layouts
S. Set the current active layout
P. Print the properties of all layouts
V. Print the system variable information
Q. Quit
Select operation:>

When the operation is a-"Add a new layout" (first variant):


Entry the name of a new layout:>myLayout

List of layouts:
"Layout1"
"Model"
"Layout2"
"myLayout"

When the operation is A-"Add a new layout" (second variant):


Entry the name of a new layout:>Layout3

List of layouts:
"Model"
"Layout1"
"Layout2"
"Layout3"
"myLayout"

When the operation is B-"List the associated blocks":


List of associated blocks:
"*Model_Space", [22-"Model"-Layout]
"*Paper_Space", [1E-"Layout1"-Layout]
"*Paper_Space0", [26-"Layout2"-Layout]
"*Paper_Space2", [6E-"Layout3"-Layout]
"myLayout", [72-"myLayout"-Layout]

When the operation is E-"Expose the predefined layouts":


Current block record: "*Model_Space"
Current active layout: "Model"

When the operation is T-"Switch the current tab":


Switch the current tab [M-model|P-paper]:>P
Current tab is set to Layout

When the operation is E-"Expose the predefined layouts":


Current block record: "*Paper_Space"
Current active layout: "Layout1"

When the operation is S-"Set the current active layout":


Entry the layout name to be set active:>layout3
Current block record: "*Paper_Space"
Current active layout: "Layout3"

When the operation is B-"List the associated blocks":


List of associated blocks:
"*Model_Space", [22-"Model"-Layout]
"*Paper_Space", [6E-"Layout3"-Layout]
"*Paper_Space0", [26-"Layout2"-Layout]
"*Paper_Space2", [1E-"Layout1"-Layout]
"myLayout", [72-"myLayout"-Layout]

When the operation is a-"Add a new layout" and the name exists in the dictionary:


Entry the name of a new layout:>layout1
Error: the name "layout1" - already exists

When the operation is a-"Add a new layout" and the name is incorrect:


Entry the name of a new layout:>A:0?
Error: the name contains inadmissible letters: <>/\":;?,|=`

When the operation is n-"Rename the selected layout" (first variant):


Entry the name of the layout to be renamed:>myLayout
Entry the new name of the layout:>Sheet

When the operation is N-"Rename the selected layout" (second variant):


Entry the name of the layout to be renamed:>layout2
Entry the new name of the layout:>Section A-A

When the operation is N-"Rename the selected layout" and the name is absent:


Entry the name of the layout to be renamed:>AAA
Error: the name "AAA" - is not found

When the operation is N-"Rename the selected layout" and the name is predefined:


Entry the new name of the layout:>Screen
Error: 234 - Model Space Layout can't be renamed

When the operation is L-"List the accessible layouts":


List of layouts:
"Layout1"
"Model"
"Section A-A"
"layout3"
"Sheet"

When the operation is d-"Delete the selected layout" (first variant):


Entry the name of the layout to be deleted:>layout1

When the operation is D-"Delete the selected layout" (second variant):


Entry the name of the layout to be deleted:>Section A-A

When the operation is D-"Delete the selected layout" and the name is absent or the layout was already deleted:


Entry the name of the layout to be deleted:>Section A-A
Error: the name "Section A-A" - is not found

When the operation is D-"Delete the selected layout" and the name is predefined:


Entry the name of the layout to be deleted:>model
Error: 229 - Model Space Layout can't be deleted

When the operation is M-"Modify the selected layout":


Entry the name of the layout to be modified:>Sheet

Start modifying of the layout properties
Selected layout: "Sheet"
V. Active viewport
G. Append geometry
N. Set the tab order
I. Set the insertion base
M. Set the minimum extents
H. Set the maximum extents
L. Set the lower-left limits
U. Set the upper-right limits
E. Print the geometry extents
P. Print the whole properties
p. Print the specific properties
T. Switch the tab selected status
O. Switch the outside limits status
S. Switch the linetype scale status
A. Switch the annotative display status
B. Associate with the block record object
D. Disassociate with the block record object
Q. Quit
Select operation:>Q
Stop modifying of the layout properties

When the operation is P-"Print the specific properties of all layouts":


List of layouts:
H=22
  Type: AcDbLayout
  Status: unerased,resident,forRead
  Database:
  ------ Specific Properties
  Key = <Model>
  Name = "Model"
  Type = Model
  Block = [1F-"*Model_Space"-Block]
  Active Viewport = [29-"*Active"-Viewport]
  Overall Viewport = [Db:kNull]
  Tab Order = 0
  Insertion base = (0,0,0)
  Minimum extents = (1e+020,1e+020,1e+020)
  Maximum extents = (-1e+020,-1e+020,-1e+020)
  Lower-left limits = (0,0)
  Upper-right limits = (12,9)
  Tab selected status = Off
  Outside limits status = Off
  Linetype scale status = On
  Annotative display status = Off
H=6F
  Type: AcDbLayout
  Status: unerased,resident,forRead
  Database:
  ------ Specific Properties
  Key = <layout3>
  Name = "layout3"
  Type = Paper
  Block = [6E-"*Paper_Space"-Block]
  Active Viewport = [74-Viewport]
  Overall Viewport = [74-Viewport]
  Tab Order = 2
  Insertion base = (0,0,0)
  Minimum extents = (0,0,0)
  Maximum extents = (0,0,0)
  Lower-left limits = (-0.25,-0.25)
  Upper-right limits = (10.75,8.25)
  Tab selected status = Off
  Outside limits status = Off
  Linetype scale status = On
  Annotative display status = Off
H=72
  Type: AcDbLayout
  Status: unerased,resident,forRead
  Database:
  ------ Specific Properties
  Key = <Sheet>
  Name = "Sheet"
  Type = Paper
  Block = [73-"myLayout"-Block]
  Active Viewport = [Db:kNull]
  Overall Viewport = [Db:kNull]
  Tab Order = 4
  Insertion base = (0,0,0)
  Minimum extents = (0,0,0)
  Maximum extents = (0,0,0)
  Lower-left limits = (0,0)
  Upper-right limits = (0,0)
  Tab selected status = Off
  Outside limits status = Off
  Linetype scale status = On
  Annotative display status = Off

When the operation is V-"Print the system variable information":


System variable information:
Current tab type: Layout
Current tab object: [6F-"layout3"-Layout]
Model space object: [1F-"*Model_Space"-Block]
Paper space object: [6E-"*Paper_Space"-Block]
Current model minimum extents: (1e+020,1e+020,1e+020)
Current model maximum extents: (-1e+020,-1e+020,-1e+020)
Current model lower-left limits: (0,0)
Current model upper-right limits: (12,9)
Objects outside model limits are allowed
Default elevation in model space: 0
Current paper minimum extents: (0,0,0)
Current paper maximum extents: (0,0,0)
Current paper lower-left limits: (-0.25,-0.25)
Current paper upper-right limits: (10.75,8.25)
Objects outside paper limits are allowed
Default elevation in paper space: 0

Use the Q-"Quit" operation to exit:


Stop testing of layouts

See Also

Working with Layouts

Example of Working with the Layout Object

Copyright © 2002 – 2022. Open Design Alliance. All rights reserved.