Programming Guide : Creating a Frame at Runtime : How You Can Build a Frame Dynamically : How You Can Create the FrameSource Object
 
Share this page          
How You Can Create the FrameSource Object
The first step in the construction of a frame is the creation of its FrameSource object, which contains the source definition of a frame. The attributes of the FrameSource object represent the initial settings of the frame when it is first called or opened. The example application creates the FrameSource with the following code:
/* No frame found. Generate a new one. */
fieldtop = 200;        /* Starting field on frame */
test_frame = FrameSource.Create();
The Create method creates a new instance of an object of the specified class. In this case, it returns a new instance of a FrameSource object to the test_frame reference variable.
The fieldtop variable designates the starting position of the top of each field on the form. It is incremented by the size of each field and some predetermined amount (identifying the amount of space between fields) in each iteration of the select loop. The example frame sets this variable at this point so that if the user selects the Go menu item more than once, to create more than one frame, the top field on each new frame starts in the correct position.
How You Can Set the FrameSource Attributes
After the starting frame's Go operation creates the FrameSource object, it sets the following frame attributes:
Name
Specifies the name of the frame
Datatype
Specifies the data type of the frame's return value
IsNullable
Specifies whether the return value is nullable
WindowTitle
Defines the title that appears on the frame's window
CurMode
Defines the frame's mode
WindowPlacement
Defines the location of the frame's window when the frame is called or opened
The sample application assigns the following values to these attributes:
/* Fill in the Frame Information */
test_frame_name = table_choices + '_frame';
test_frame.Name = test_frame_name;
test_frame.DataType = 'integer4';
        /* This is default */
test_frame.IsNullable = TRUE;
        /* This is default */
test_frame.WindowTitle = 'Edit Data For Table: ' +
        table_choices;
test_frame.CurMode = FM_UPDATE;
test_frame.WindowPlacement = WP_FLOATING;
Some frame attributes, such as StartMenu, are set later in the program (after the code creates the objects contained by the attributes). Other frame attributes are left at their default values. For an example of creating the StartMenu object, see How You Can Create the Frame Menu Items.
How You Can Create the FrameForm Object
The first step in creating the frame's form is creating a FrameForm object, which is a subform that contains the fields on a form. The only step involved in creating a FrameForm object is invoking the Create method of the FrameForm system class and assigning the result to the generated frame's TopForm attribute. There are no FrameForm attributes to set.
The following statement from the example application creates a FrameForm object and assigns it to the TopForm attribute of the FrameSource object represented by test_frame:
test_frame.TopForm = FrameForm.Create();
How You Can Construct the Fields
In the DynamicFrame frame, all of the steps involved in constructing the form's fields occur inside a select loop. A select statement retrieves the column names of the chosen table. As each is returned, the statements in the select loop generate its corresponding field, including the field's title and data type, and attach it to the form.
Moreover, because the script of the generated frame in the DynamicFrame application contains SQL statements (the select statement and cursor statements) that include the column names, the select loop also contains code that builds these SQL statements.
The following code includes the select statement that retrieves the column names:
/* Get the list of columns in the form */
numcolumns = 0;
repeated select column_name as :columnname,
    column_datatype as :columndatatype,
    column_length as :columnlength
from iicolumns
where table_name = :table_choices
The preceding select statement retrieves the name, data type, and length of each column in the table selected by the user into variables.
The DynamicFrame frame uses a select loop to process the values returned by the select statement. The following code adds the column names to the SQL statement strings that are part of the generated frame's script:
begin
/* Use the SQL trim function to remove trailing
** blanks. */
columnname = trim(columnname);
columndatatype = trim(columndatatype);
/* Add column name to SQL statement strings
** being generated */
if numcolumns <= 0 then
    /* This is the first column found. Insert
    ** statement headers */
    selectstring = 'open table_cursor for select ' +
    HC_NEWLINE +HC_TAB + HC_TAB + HC_TAB +
    columnname;
    fetchstring = 'fetch table_cursor into ' +
    HC_NEWLINE + HC_TAB + HC_TAB + HC_TAB + ':' +
    columnname + ' =' + columnname;
    selectupdatestring = 'for update of ' +
    HC_NEWLINE + HC_TAB + HC_TAB + HC_TAB +
    columnname;
    updatestring = 'update ' + table_choices +
    HC_NEWLINE + HC_TAB + HC_TAB + 'set ' + HC_TAB +
    columnname +' = :' + columnname;
else
    /* Not the first column. Concatenated to
    ** strings */
    selectstring = selectstring + ',' + HC_NEWLINE +
    HC_TAB +  HC_TAB + HC_TAB + columnname;
    fetchstring = fetchstring + ',' + HC_NEWLINE +
    HC_TAB +  HC_TAB + HC_TAB + ':' + columnname + '
    = ' + columnname;
    updatestring = updatestring + ',' + HC_NEWLINE +
    HC_TAB + HC_TAB + HC_TAB + columnname + ' = :' +
    columnname;
    selectupdatestring = selectupdatestring + ',' +
    HC_NEWLINE+ HC_TAB + HC_TAB + HC_TAB +
    columnname;
endif;
numcolumns = numcolumns + 1;
This code uses the SQL trim string function to remove trailing blanks from the text of the column's name and data type.
The variable numcolumns, initialized to zero before the select, controls the execution of this code. If numcolumns is non-positive, the columnname variable represents the first row returned by the select statement and the Go operation executes the code that builds the initial portions of the SQL statements. The numcolumns variable is incremented with each pass through the loop so that subsequent values of the columnname variable are added to an existing string.
The variables that hold the character strings representing the SQL statements are of the data type varchar(2000). The system constants HC_NEWLINE and HC_TAB represent newline and tab characters, respectively. If your statement strings are longer than 2000 characters, it is preferable to use StringObject variables instead of varchar variables.
After adding the column name to the statement strings, the Go operation constructs the field title and the field itself (all of which are entry fields in the example). The following code creates the field title and attaches it to the form:
trim_ptr = FreeTrim.Create();
trim_ptr.XLeft = trimleft;
trim_ptr.YTop = fieldtop;
trim_ptr.TextValue = trim(
    trim(uppercase(left(columnname,1))) +
    trim(lowercase(shift(columnname,-1))) + ':');
trim_ptr.IsBold = TRUE;
trim_ptr.TypeSize = 10;
trim_ptr.Width = 33 * widthchar;
trim_ptr.Height = heightchar;
trim_ptr.ParentField = test_frame.TopForm;
Each field's title is an object of the class FreeTrim, which is used only for textual trim on forms. The previous code defines the trim's position on the form (XLeft and YTop), its actual text value (TextValue), and the appearance of the text (IsBold, TypeSize, Width, and Height).
The code also uses SQL string functions (uppercase and lowercase) to ensure that the title appears with initial capitalization.
Specifying a ParentField attribute for an object identifies which composite field contains that object. Therefore, assigning the form object in test_frame.TopForm to the ParentField attribute of the FreeTrim object attaches the trim to the form by identifying the form as the composite field that contains the trim.
After the title is constructed and attached to the form, the following code constructs its corresponding entry field:
field_ptr = EntryField.Create();
field_ptr.Name = columnname;
field_ptr.IsNullable = TRUE;
field_ptr.XLeft = fieldleft;
field_ptr.YTop = fieldtop;
The Create method returns an object of type EntryField to the reference variable, field_ptr. The previous code sample assigns a name to the field_ptr variable and specifies its nullability and its top left-hand starting position.
The Go operation then constructs the data type for the field:
fieldheight = 1;
/* Check first for non-character data types. */
if columndatatype = 'integer' then
    columnlength = 13;
    field_ptr.DataType = 'integer';
    field_ptr.FormatString = 'f10';
    field_ptr.IsMultiLine = FALSE;
elseif columndatatype = 'float' then
    columnlength = 15;
    field_ptr.DataType = 'f8';
    field_ptr.FormatString = 'f12.2';
    field_ptr.IsMultiLine = FALSE;
elseif columndatatype = 'date' then
    columnlength = 25;
    field_ptr.DataType = 'date';
    field_ptr.FormatString = 'd"Feb 3, 1901"';
    field_ptr.IsMultiLine = FALSE;
elseif columndatatype = 'money' then
    columnlength = 15;
    field_ptr.DataType = 'money';
    field_ptr.FormatString = '"$$$,$$$,$$$.nn"';
    field_ptr.IsMultiLine = FALSE;
else /* All character datatypes follow */
    field_ptr.DataType =
    'varchar(' + ascii(columnlength) + ')';
    /* See if we need a multiline entry field */
    if columnlength > 50 then
        field_ptr.IsMultiLine = TRUE;
        field_ptr.FocusBehavior =
        FT_TABTO;  /* Default */
        fieldheight = columnlength / 50 + 1;
        columnlength = 50;
        if fieldheight > 4 then
            field_ptr.HasScrollBar = TRUE;
            fieldheight = 4;
        endif;
    else
        field_ptr.IsMultiLine = FALSE;
        field_ptr.FocusBehavior = FT_TABTO;
        if columnlength < 5 then
            columnlength = 5;
        endif;
    endif;
endif;
If the data type is integer, float, date, or money, the preceding code ignores the column length returned by the select statement and assigns a length explicitly. Column length is set explicitly for non-character columns because the column length returned by the select statement identifies the number of bytes used by Ingres to store the data internally. Because the DynamicFrame frame requires the column length to define the width of the field on the form, it explicitly assigns column length values to these data types. (Alternatively, the CharsPerLine attribute of EntryField objects can be used to define the width of the field.)
In addition to assigning the column length for each non-character data type, the Go operation assigns the appropriate values for the DataType, FormatString, and IsMultiLine attributes of the EntryField objects.
If the value in the columndatatype variable indicates that the column has a character data type (char, varchar, c, or text), the preceding code sets the field's data type to varchar. The SQL ascii function converts the integer value in the columnlength variable to a character string.
In addition, character columns that are longer than 50 characters are formatted as multiline fields, and a maximum field height and length is enforced. Scroll bars are enabled for columns that require more than four 50-character lines to display.
After the Go operation constructs each field, it sets relevant attributes and attaches the field to the form. The example code sets each entry field's width equal to the value of the columnlength variable multiplied by the width of one character. The height of each entry field is set to the height of one character times the fieldheight variable. The outline width of each field is set using the LW_VERYTHIN constant.
The following code from the example frame sets each entry field's attributes and attaches it to the form:
    /* Now place it on the form. */
    field_ptr.Width = columnlength * widthchar;
    field_ptr.Height = heightchar * fieldheight;
    field_ptr.OutlineWidth = LW_VERYTHIN;
    /* attach to form */
    field_ptr.ParentField = test_frame.TopForm;
    fieldtop = fieldtop + field_ptr.Height +
    vertspace;
end; /* End of select loop */
commit;
After attaching the field to the form, the previous code assigns a new value to the fieldtop variable, which determines the position of the next field. The value in the fieldtop variable was assigned to the YTop attribute when the entry field was constructed. For discussions on setting the fieldtop variable, see How You Can Create the FrameSource Object.
The statement in the previous code that sets the fieldtop variable takes the current value of fieldtop and adds to it the height of the field just constructed and a predetermined amount of vertical space. The resulting figure is the value of fieldtop for the next iteration of the loop and places the next field on the form below the field just constructed. The amount of space between them is equal to the amount specified by the vertspace variable.
The select loop is repeated once for each column name returned by the select statement. When it is finished, all fields have been constructed and placed on the form. In addition, portions of the SQL statements used in the generated frame's script are also constructed.
How You Can Set the Form Height and Width
After all the fields have been constructed, you can set the dimensions of the window displaying the frame by setting the WindowHeight and WindowWidth attributes of the FrameSource object. The following code from the Go operation of the DynamicFrame frame sets these dimensions:
/* Now set the form height and width */
test_frame.WindowHeight = fieldtop + vertspace;
test_frame.WindowWidth = maxformwidth;
if test_frame.WindowHeight > maxformheight then
    test_frame.WindowHeight = maxformheight;
    test_frame.HasScrollbars = TRUE;
else
    test_frame.HasScrollbars = FALSE;
endif;
To prevent the window's height from exceeding a predetermined amount, the example code contains an if statement that checks the height. If it exceeds the limit (the value of the maxformheight variable), the WindowHeight attribute is set to the value of the maxformheight variable and scroll bars are added to the window, allowing the user to scroll to hidden fields. If the window's height does not exceed the limit, the scroll bars are not added.
How You Can Complete the SQL Statements
The Go operation finishes creating the text of the SQL statements begun in the select loop. Adding formatting characters and the final clause completes the selectstring variable. Adding the terminating semicolon completes the selectupdatestring and fetchstring variables. The following code from the DynamicFrame frame completes the text for the variables used in the SQL statements:
/* Now complete the select and fetch strings */
selectstring = selectstring + HC_NEWLINE + HC_TAB +
    HC_TAB + 'from ' + table_choices;
selectupdatestring = selectupdatestring + ';';
fetchstring = fetchstring + ';';
The completed strings are used later when the frame script for the generated frame is constructed.
How You Can Create the Frame Menu Items
The generated frame created by the DynamicFrame frame contains a menu bar that has two selections, File and Edit. If the user selects File, two new choices appear, Commit and Close, and Rollback and Close. If the user selects Edit, three new choices appear: Delete and Next, Update and Next, and Next.
The bar across the top of the generated frame's window, which displays the initial menu choices, is an instance of the MenuBar class. Each initial menu option and the submenu options accessed through the initial option is an instance of the MenuGroup class. In the generated frame, each of the submenu choices is an instance of the MenuButton class.
In the FrameSource object, which contains the frame's definition, the menu bar is contained in the StartMenu attribute. Therefore, to construct a menu for a frame, first create the MenuBar object and assign it to the StartMenu attribute. Then name the menu. The following code creates the named menu bar and attaches it to the generated frame:
test_frame.StartMenu = MenuBar.Create();
test_frame.StartMenu.Name = 'menu';
How You Can Construct a File Menu Group
The following discussion steps through the construction of the File menu group.
1. Create the MenuGroup object:
top_menu = MenuGroup.Create(); /* File Menu */
2. Define the TextLabel and internal name for the File menu group:
top_menu.TextLabel = 'File';
top_menu.Name = 'file_menu';
The text assigned to the TextLabel attribute is displayed by the frame in its menu bar. The preceding code, therefore, causes the word "File" to appear as one of the selections available in the frame's initial menu. The value assigned to the Name attribute is the name of the reference variable pointing to that MenuGroup object.
3. Define the two submenu options that appear when the user selects the File option from the menu bar: “Commit and Close” and “Rollback and Close.” Defining these operations follows the same procedures as creating the MenuGroup:
Create the object for each item (in this case a MenuButton object).
Define the TextLabel and Name for each menu item.
The following code creates the first submenu item:
test_menu = MenuButton.Create();
test_menu.Name = 'commit_menu';
test_menu.TextLabel = 'Commit and Close';
4. In addition to letting the user commit the transaction and close the frame by selecting the Commit menu button, the dynamic frame provides a second way to commit and close. The example frame provides alternative access to the Commit operation by defining a speed key, which lets the user select the Close operation from the keyboard rather than using the mouse.
The following code defines the speed key:
test_menu.SpeedKey = SK_CLOSE;
5. After each submenu option is defined, the following code attaches it to the field MenuGroup object:
test_menu.ParentMenuGroup = top_menu;
The preceding statement makes the MenuGroup object represented by top_menu to be the parent of the MenuButton object represented by test_menu.
6. After defining all of the individual items for the MenuGroup, the following code attaches the MenuGroup itself to the MenuBar object:
top_menu.ParenTMenuGroup = test_frame.StartMenu;
The following is the complete code that constructs both menu groups that belong to the menu bar:
top_menu = MenuGroup.Create();   /* File Menu */
top_menu.TextLabel = 'File';
top_menu.Name = 'file_menu';

test_menu = MenuButton.Create();   
    /* Commit and close menu item */
test_menu.Name = 'commit_menu';
test_menu.TextLabel = 'Commit and Close';
test_menu.SpeedKey = SK_CLOSE;
test_menu.ParentMenuGroup = top_menu; 
    /* Attach to File menu */
test_menu = MenuButton.Create();  
    /* Rollback and close menu item */
test_menu.Name = 'rollback_menu';
test_menu.TextLabel = 'Rollback and Close';
test_menu.ParentMenuGroup = top_menu;  
    /* Attach to File menu */
top_menu.ParentMenuGroup = test_frame.StartMenu;
/* Now, create the Edit menu */
top_menu = MenuGroup.Create();   /* Edit Menu */
top_menu.TextLabel = 'Edit';
top_menu.Name = 'edit_menu';
test_menu = MenuButton.Create();      
    /* Delete menu item */
test_menu.Name = 'delete_menu';
test_menu.TextLabel = 'Delete and Next';
test_menu.SpeedKey = SK_DELETE;
test_menu.ParentMenuGroup = top_menu; 
    /* Attach to Edit menu */
test_menu = MenuButton.Create();     
    /* Update menu item */
test_menu.Name = 'update_menu';
test_menu.TextLabel = 'Update and Next';
test_menu.ParentMenuGroup = top_menu;   
    /* Attach to Edit menu */
test_menu = MenuButton.Create(); 
    /* Next menu item */
test_menu.Name = 'next_menu';
test_menu.SpeedKey = SK_NEXT;
test_menu.TextLabel = 'Next';
test_menu.ParentMenuGroup = top_menu;     
    /* Attach to Edit menu */

/* Attach to Frame */
top_menu.ParentMenuGroup = test_frame.StartMenu;
For more information about the various types of menu objects, see the Language Reference Guide.
How Generating the Frame Source Code Works
Part of the source code for the generated frame was written in the select loop that constructed the fields. The next section of the DynamicFrame frame script creates the generated frame's source code from the strings constructed in the select loop.
The starting frame uses the following steps to create the generated frame's source code:
1. Creates the string object to hold the completed script and assigns it to the Script attribute of the FrameSource:
test_frame.Script = StringObject.Create();
2. Constructs the script, creating an initialize block and event code for five Click events that correspond to the five MenuButton objects constructed earlier in the program (Commit, Rollback, Delete, Update, and Next).
The example frame uses the ConcatVarchar method (defined for the StringObject class) to concatenate the various pieces of the initialize statement and each event block into one large string. The ConcatVarchar method appends the string specified by the text parameter to the StringObject and returns a reference to the original StringObject.
The following code constructs the script for the generated frame:
/* Set up the initialize block */
test_frame.Script.ConcatVarchar(text =
    'initialize (table_cursor = CursorObject)
    =' + HC_NEWLINE + HC_TAB + 'begin' +
    HC_NEWLINE + HC_TAB + HC_TAB + selectstring +
    HC_NEWLINE);
test_frame.Script.ConcatVarchar
    (text = HC_TAB + HC_TAB +
    selectupdatestring + HC_NEWLINE + HC_TAB +
    HC_TAB + 'CurFrame.SendUserEvent
    (eventname = ''Next'');' +
    HC_NEWLINE + HC_TAB + 'end;' +
    HC_NEWLINE + HC_NEWLINE);
/* Set up the file.commit script */
test_frame.Script.ConcatVarchar(text =
    'on click file_menu.commit_menu = ' +
    HC_NEWLINE + HC_TAB +'begin' + HC_NEWLINE +
    HC_TAB + HC_TAB + 'close table_cursor;' +
    HC_NEWLINE + HC_TAB + HC_TAB + 'commit work;'
    + HC_NEWLINE + HC_TAB + HC_TAB + 'return;' +
    HC_NEWLINE + HC_TAB + 'end;' +
    HC_NEWLINE + HC_NEWLINE);
/* Set up the file.rollback script */
test_frame.Script.ConcatVarchar(text =
    on click file_menu.rollback_menu =
    ' + HC_NEWLINE + HC_TAB + 'begin' +
    HC_NEWLINE + HC_TAB + HC_TAB + 'close
    table_cursor;' + HC_NEWLINE + HC_TAB + HC_TAB
    + 'rollback work;' + HC_NEWLINE + HC_TAB
    + HC_TAB + 'return;' + HC_NEWLINE + HC_TAB +
    'end;' + HC_NEWLINE + HC_NEWLINE);
/* Set up the edit.delete script */
test_frame.Script.ConcatVarchar(text =
    'on click edit_menu.delete_menu =
    ' + HC_NEWLINE + HC_TAB +'begin' + HC_NEWLINE
    + HC_TAB + HC_TAB + 'delete from ' +
    table_choices + ' where current of
    table_cursor;' + HC_NEWLINE
    + HC_TAB + HC_TAB +
    'CurFrame.SendUserEvent(eventname =
    ''Next'');' + HC_NEWLINE+ HC_TAB + 'end;' +
    HC_NEWLINE + HC_NEWLINE);
/* Set up the edit.update script */
test_frame.Script.ConcatVarchar(text =
    'on click edit_menu.update_menu = ' +
    HC_NEWLINE + HC_TAB + 'begin' + HC_NEWLINE +
    HC_TAB + HC_TAB + updatestring +
    HC_NEWLINE + HC_TAB + HC_TAB + ' where
    current of table_cursor;'
    + HC_NEWLINE + HC_TAB + HC_TAB +
    'CurFrame.SendUserEvent
    (eventname = ''Next'');' + HC_NEWLINE
    + HC_TAB + 'end;' + HC_NEWLINE + HC_NEWLINE);
/* Set up the edit.next script */
test_frame.Script.ConcatVarchar(text =
    'on click edit_menu.next_menu,' +
    HC_NEWLINE + 'on userevent ''Next'' =' +
    HC_NEWLINE + HC_TAB + 'begin' +
    HC_NEWLINE + HC_TAB + HC_TAB +
    fetchstring + HC_NEWLINE + HC_TAB + HC_TAB +
    'if table_cursor.State = CS_NO_MORE_ROWS
    then'+ HC_NEWLINE + HC_TAB + HC_TAB + HC_TAB
    + 'message ''No more rows.'';' + HC_NEWLINE
    + HC_TAB + HC_TAB + 'endif;' + HC_NEWLINE +
    HC_TAB + 'end;' + HC_NEWLINE + HC_NEWLINE);
3. To facilitate debugging, writes the completed script to a file using the WriteToFile method (defined for the StringObject class):
test_frame.Script.WriteToFile('test.script');
The WriteToFile method invoked in the preceding example creates a file named test.script.
How You Can Add the Frame to the Application
After the frame has been fully defined, it must be attached to the application by setting the ParentApplication attribute of the new frame's FrameSource object to the same value that is in the ParentApplication attribute of the currently executing frame. (The currently executing frame is the starting frame of the dynamic application.)
The following code from the example application sets the ParentApplication attribute of the generated frame:
test_frame.ParentApplication =
    CurFrame.ObjectSource.ParentApplication;
This statement assigns the contents of the ParentApplication attribute of the current frame's FrameSource object to the corresponding attribute of the generated frame's FrameSource object. For a more detailed explanation of the preceding syntax, see How You Can Attach the Frame to the Application.
The DynamicFrame frame performs the additional step of adding the frame to a list of already generated frames. Because the sample application lets a user return to the initial frame, select another table, and select the Go menu item again, a global array variable is used to keep track of which frames have already been generated.
Because the Generated_Frame_List global variable stores the names of generated frames, an existing frame can be executed without requiring regeneration if the user selects a table for which a frame already exists.
The following code from the example application adds the newly generated frame to the list in the global array variable:
i = Generated_Frame_List.LastRow + 1;
Generated_Frame_List[i].table_name = table_choices;
Generated_Frame_List[i].frame_name = table_choices +
    '_frame';
Generated_Frame_List[i].frame_source = test_frame;
The global array variable, Generated_Frame_List, has three attributes: table_name, frame_name, and frame_source. The previous code uses the LastRow attribute (defined for the ArrayObject class) to determine the row sequence number of the last row in the array. The current value of the LastRow attribute is incremented by one to create a new index into the array.
The reference to a previously non-existent row automatically appends a new, empty row to the end of the array. After the row has been added, each of its attributes is assigned the appropriate value for the newly generated frame. The process used to add a new row in the example code is called adding a row by first reference. For more information about adding rows to an array, see Creating a Frame at Runtime.
How You Can Execute the Frame
The following code from the DynamicFrame frame assigns a name to the newly generated frame and executes it:
test_frame_name = table_choices + '_frame';
callframe :test_frame_name;
The callframe statement compiles and runs the new frame. When OpenROAD automatically compiles a dynamically created frame or procedure on first call, it places the text of any compilation errors into Proc4GLSource's Compile_Errors attribute. Check whether the Compile_Errors attribute is null after the first call to detect whether compilation errors occurred.
The following code from the example frame checks the Compile_Errors attribute and writes any errors to a file to facilitate debugging:
if test_frame.Compile_Errors is not null then
    test_frame.Compile_Errors.WriteToFile
    ('test.errors');
    Generated_Frame_List.RemoveRow(rownumber = i);
endif;
If the frame does not compile correctly, its name is removed from the list in the global array variable, Generated_Frame_List.