├── Demos ├── jobs.DBF ├── jobs.FPT ├── existingformdemo.SCT ├── existingformdemo.scx ├── demo_0.prg ├── demo_1.prg ├── demo_2.prg ├── demo_3.prg ├── demo_yes_no_cancel.prg ├── demo_5.prg ├── demo_graph.prg ├── demo_4.prg └── logo.prg ├── Button Bar.snag ├── DynamicForm.PJT ├── DynamicForm.pjx ├── DynamicForms.zip ├── .gitignore ├── Documentation └── Images │ ├── Logo.snag │ ├── Columns.snag │ ├── Margins.snag │ ├── Example 2.snag │ ├── Form Layout.snag │ ├── code_sample_01.png │ ├── image_thumb_4.png │ ├── image_thumb_7.png │ ├── syntax_example.png │ ├── Dynamic Forms Logo.jpg │ ├── Dynamic Forms Logo.snag │ ├── SNAGHTML45155f2_thumb.png │ ├── SNAGHTML195fba9c_thumb.png │ ├── SNAGHTML488f0b72_thumb.png │ ├── SNAGHTML4dd81694_thumb.png │ ├── SNAGHTML5e028c84_thumb.png │ ├── Example 2 with dot syntax.snag │ ├── Form Sample Row Increment 0.png │ ├── VFPx Poject Proposal - Dynamic Form.docx │ └── VFPx Poject Proposal - Dynamic Form.pdf ├── DynamicFormVersionFile - bad.txt ├── markup_sample.prg ├── changelog.txt ├── _DynamicFormVersionFile.txt ├── df_tests.prg ├── README.md └── DynamicForm.prg /Demos/jobs.DBF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFPX/DynamicForms/master/Demos/jobs.DBF -------------------------------------------------------------------------------- /Demos/jobs.FPT: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFPX/DynamicForms/master/Demos/jobs.FPT -------------------------------------------------------------------------------- /Button Bar.snag: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFPX/DynamicForms/master/Button Bar.snag -------------------------------------------------------------------------------- /DynamicForm.PJT: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFPX/DynamicForms/master/DynamicForm.PJT -------------------------------------------------------------------------------- /DynamicForm.pjx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFPX/DynamicForms/master/DynamicForm.pjx -------------------------------------------------------------------------------- /DynamicForms.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFPX/DynamicForms/master/DynamicForms.zip -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.bak 2 | *.BAK 3 | *.fxp 4 | *.FXP 5 | FOXUSER.* 6 | *_ref.* 7 | DeployDynamicForm.ps1 8 | -------------------------------------------------------------------------------- /Demos/existingformdemo.SCT: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFPX/DynamicForms/master/Demos/existingformdemo.SCT -------------------------------------------------------------------------------- /Demos/existingformdemo.scx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFPX/DynamicForms/master/Demos/existingformdemo.scx -------------------------------------------------------------------------------- /Documentation/Images/Logo.snag: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFPX/DynamicForms/master/Documentation/Images/Logo.snag -------------------------------------------------------------------------------- /Documentation/Images/Columns.snag: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFPX/DynamicForms/master/Documentation/Images/Columns.snag -------------------------------------------------------------------------------- /Documentation/Images/Margins.snag: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFPX/DynamicForms/master/Documentation/Images/Margins.snag -------------------------------------------------------------------------------- /Documentation/Images/Example 2.snag: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFPX/DynamicForms/master/Documentation/Images/Example 2.snag -------------------------------------------------------------------------------- /Documentation/Images/Form Layout.snag: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFPX/DynamicForms/master/Documentation/Images/Form Layout.snag -------------------------------------------------------------------------------- /Documentation/Images/code_sample_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFPX/DynamicForms/master/Documentation/Images/code_sample_01.png -------------------------------------------------------------------------------- /Documentation/Images/image_thumb_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFPX/DynamicForms/master/Documentation/Images/image_thumb_4.png -------------------------------------------------------------------------------- /Documentation/Images/image_thumb_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFPX/DynamicForms/master/Documentation/Images/image_thumb_7.png -------------------------------------------------------------------------------- /Documentation/Images/syntax_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFPX/DynamicForms/master/Documentation/Images/syntax_example.png -------------------------------------------------------------------------------- /Documentation/Images/Dynamic Forms Logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFPX/DynamicForms/master/Documentation/Images/Dynamic Forms Logo.jpg -------------------------------------------------------------------------------- /Documentation/Images/Dynamic Forms Logo.snag: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFPX/DynamicForms/master/Documentation/Images/Dynamic Forms Logo.snag -------------------------------------------------------------------------------- /Documentation/Images/SNAGHTML45155f2_thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFPX/DynamicForms/master/Documentation/Images/SNAGHTML45155f2_thumb.png -------------------------------------------------------------------------------- /Documentation/Images/SNAGHTML195fba9c_thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFPX/DynamicForms/master/Documentation/Images/SNAGHTML195fba9c_thumb.png -------------------------------------------------------------------------------- /Documentation/Images/SNAGHTML488f0b72_thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFPX/DynamicForms/master/Documentation/Images/SNAGHTML488f0b72_thumb.png -------------------------------------------------------------------------------- /Documentation/Images/SNAGHTML4dd81694_thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFPX/DynamicForms/master/Documentation/Images/SNAGHTML4dd81694_thumb.png -------------------------------------------------------------------------------- /Documentation/Images/SNAGHTML5e028c84_thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFPX/DynamicForms/master/Documentation/Images/SNAGHTML5e028c84_thumb.png -------------------------------------------------------------------------------- /Documentation/Images/Example 2 with dot syntax.snag: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFPX/DynamicForms/master/Documentation/Images/Example 2 with dot syntax.snag -------------------------------------------------------------------------------- /Demos/demo_0.prg: -------------------------------------------------------------------------------- 1 | clear 2 | *_Vfp.width = 1280 3 | *_vfp.height = 720 4 | 5 | Cd h:\work\repos\dynamicform 6 | 7 | Set Procedure To 'DynamicForm' ADDITIVE 8 | -------------------------------------------------------------------------------- /Documentation/Images/Form Sample Row Increment 0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFPX/DynamicForms/master/Documentation/Images/Form Sample Row Increment 0.png -------------------------------------------------------------------------------- /Documentation/Images/VFPx Poject Proposal - Dynamic Form.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFPX/DynamicForms/master/Documentation/Images/VFPx Poject Proposal - Dynamic Form.docx -------------------------------------------------------------------------------- /Documentation/Images/VFPx Poject Proposal - Dynamic Form.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFPX/DynamicForms/master/Documentation/Images/VFPx Poject Proposal - Dynamic Form.pdf -------------------------------------------------------------------------------- /DynamicFormVersionFile - bad.txt: -------------------------------------------------------------------------------- 1 | lparameters toUpdateObject 2 | toUpdateObject.AvailableVersion = 'Dynamic Form - 1.9.1 Production Release August 30, 2017 - 201710830' 3 | return toUpdateObject 4 | -------------------------------------------------------------------------------- /Demos/demo_1.prg: -------------------------------------------------------------------------------- 1 | *-- Demo 1 - Binding to a cursor, showing ALL fields 2 | Close Databases 3 | 4 | If Used('Jobs') 5 | Use in Jobs 6 | Endif 7 | 8 | Select 0 9 | Use Jobs 10 | loForm = CreateObject('DynamicForm') 11 | loForm.Caption = 'Dynamic Forms Demo' 12 | loForm.cHeading = 'Job ' + Jobs.job_num 13 | 14 | loForm.cAlias = 'Jobs' 15 | 16 | loForm.oRenderEngine.nColumnHeight = 400 17 | 18 | loform.Show() 19 | 20 | If Vartype(loForm) = 'O' 21 | loForm.Release() 22 | EndIf 23 | -------------------------------------------------------------------------------- /Demos/demo_2.prg: -------------------------------------------------------------------------------- 1 | *-- Demo 2 - Binding to a cursor, showing all fields, except we'll skip a few. 2 | Close Databases 3 | 4 | Select 0 5 | Use Jobs 6 | loForm = CreateObject('DynamicForm') 7 | loForm.Caption = 'Dynamic Forms Demo' 8 | loForm.cHeading = 'Job ' + Jobs.job_num 9 | 10 | loForm.cAlias = 'Jobs' 11 | loForm.oRenderEngine.nColumnHeight = 400 12 | 13 | *-- Skip over certain fields. 14 | loForm.oRenderEngine.cSkipFields = 'ipkey, mach_cost, mat_cost, total_cost, mat_mkup' 15 | 16 | loform.Show(1, _screen) 17 | 18 | -------------------------------------------------------------------------------- /Demos/demo_3.prg: -------------------------------------------------------------------------------- 1 | *-- Demo 3 - Binding to a cursor, showing only selected fields using Markup Syntax 2 | Close Databases 3 | 4 | Select 0 5 | Use Jobs 6 | loForm = CreateObject('DynamicForm') 7 | loForm.Caption = 'Dynamic Forms Demo' 8 | loForm.cHeading = 'Job ' + Jobs.job_num 9 | 10 | loForm.cAlias = 'Jobs' 11 | loForm.oRenderEngine.nColumnHeight = 400 12 | 13 | *-- Build custom list of fields 14 | lcBodyMarkup = 'job_start | p_o_num | ship_date | ship_info' 15 | 16 | loForm.cBodyMarkup = lcBodyMarkup 17 | 18 | loForm.oRenderEngine.lLabelsabove = .t. 19 | 20 | loform.Show() 21 | 22 | -------------------------------------------------------------------------------- /Demos/demo_yes_no_cancel.prg: -------------------------------------------------------------------------------- 1 | *-- Custom Dialog box -------------- 2 | 3 | loForm = CreateObject('DynamicForm') 4 | 5 | Text to lcBodyMarkup 6 | 7 | .class = 'label' 8 | .caption = 'Do you want to try Dynamic Forms?' 9 | .FontSize = 14 10 | .FontName = 'Arial' 11 | .AutoSize = .t. 12 | .margin-bottom = 15 | 13 | 14 | .class = 'DF_ResultButton' 15 | .caption = '\ .f. 18 | :caption => 'Key Value' 19 | :label-fontbold => .t. | 20 | 21 | job_start | 22 | 23 | p_o_num 24 | :row-increment => 0 | 25 | 26 | ship_date 27 | :row-increment => 0 | 28 | 29 | ship_info 30 | :class => 'editbox' 31 | :width => 500 32 | :height => 200 33 | :anchor => 15 | 34 | 35 | EndText 36 | 37 | loForm.cBodyMarkup = lcBodyMarkup 38 | 39 | loform.Show() 40 | 41 | 42 | * DEMO NOTES 43 | *--------------------------------------------------------------------------------------- 44 | * 1. :row-increment => 0 45 | * 2. loForm.oRenderEngine.lLabelsABove =.t. 46 | -------------------------------------------------------------------------------- /Demos/demo_graph.prg: -------------------------------------------------------------------------------- 1 | *-- Demo 6 - Vector graphics 2 | loForm = CreateObject('DynamicForm') 3 | loForm.Caption = 'Dynamic Forms' 4 | 5 | loForm.cHeading ='Vector graphics demo.' 6 | loForm.cHeaderMarkup = '' 7 | loForm.cFooterMarkup = '' 8 | 9 | lcBodyMarkup = '' 10 | pi2 = Pi() * 2 11 | 12 | r = 250 13 | x_center = 300 14 | y_center = 300 15 | lnPoints = 720 16 | 17 | last_x = r * Cos(0) + x_center 18 | last_y = r * Sin(0) + y_center 19 | 20 | For n = 1 to lnPoints 21 | 22 | x = r * Cos(n/360 * pi2) + x_center 23 | y = r * Sin(n/360 * pi2) + y_center 24 | 25 | lnWidth = Abs(Abs(x) - Abs(last_x)) + 1 26 | lnHeight = Abs(Abs(y) - Abs(last_y)) + 1 27 | 28 | lcBodyMarkup = lcBodyMarkup + ":class => 'line' " + ; 29 | ":top => " + Transform(y) + ; 30 | ":left => " + Transform(x) + ; 31 | ":height => " + Transform(lnHeight) + ; 32 | ":width => " + Transform(lnWidth) + ; 33 | ":lineslant => '" + Iif((x > x_center and y > y_center) or (x < x_center and y < y_center), '/', '') + "'|" 34 | last_x = x 35 | last_y = y 36 | 37 | EndFor 38 | 39 | loForm.cBodyMarkup = lcBodyMarkup 40 | 41 | loform.Show(1, _screen) 42 | 43 | 44 | -------------------------------------------------------------------------------- /Demos/demo_4.prg: -------------------------------------------------------------------------------- 1 | *-- Demo 4 - Binding to a cursor, using attributes in the the markup to add styling 2 | Close Databases 3 | 4 | Select 0 5 | Use Jobs 6 | loForm = CreateObject('DynamicForm') 7 | loForm.Caption = 'Dynamic Forms Demo' 8 | loForm.cHeading = 'Job ' + Jobs.job_num 9 | 10 | loForm.cAlias = 'Jobs' 11 | loForm.oRenderEngine.nColumnHeight = 400 12 | 13 | *lcBodyMarkup = 'job_start | p_o_num | ship_date | ship_info' 14 | 15 | Text to lcBodyMarkup NoShow 16 | 17 | ipkey :enabled => .f. :caption => 'Key Value'| 18 | job_start | 19 | p_o_num :caption => 'Notes' :width => 200| 20 | :class => 'commandbutton' :caption => '...' :width => 20 :row-increment => 0 :margin-left => -10| 21 | ship_date | 22 | ship_info3 :class => 'editbox' :width => 500 :height => 200 :anchor => 15 | 23 | 24 | EndText 25 | 26 | loForm.cBodyMarkup = lcBodyMarkup 27 | 28 | loform.Show() 29 | 30 | * DEMO NOTES 31 | *--------------------------------------------------------------------------------------- 32 | * 1. Add ipkey field ipkey | 33 | * 2. Disable ipkey field :enabled => .f. 34 | * 3. Add caption to p_o_num field :caption => 'PO No.' 35 | * 4. Convert ship_info to edibox :class => 'editbox' :width => 500 :height => 200 36 | * Add anchor to editbox :anchor => 15 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /markup_sample.prg: -------------------------------------------------------------------------------- 1 | Text to lcBodyMarkup NoShow 2 | 3 | id :enabled => .f. 4 | :fontbold => .t. 5 | :label.FontBold => .t. | 6 | 7 | ad_type :class => 'combobox' 8 | :RowSource => 'laOptions' 9 | :RowSourceType => 5 10 | :row-increment => 0 | 11 | 12 | bool_3 :caption => 'You can specify BOLD captions.' 13 | :FontBold => .t. 14 | :width => 200 | 15 | 16 | first_name :set-focus => .t. | 17 | mid_init :row-increment => 0 | 18 | last_name :row-increment => 0 | 19 | 20 | notes :class => 'editbox' 21 | :width => 400 22 | :height => 80 23 | :anchor => 10 | 24 | 25 | lnPrice :label.caption => 'List Price' 26 | :label.alignment => 1 | 27 | 28 | weight :row-increment => 0 29 | :label.alignment => 1 | 30 | 31 | lnOption :class => 'optiongroup' 32 | :caption => 'Color options:' 33 | :buttoncount => 2 34 | :width => 200 35 | :height => 60 36 | :option1.caption => 'Red with orange stripes' 37 | :option1.autosize => .t. 38 | :option2.caption => 'Purple with black dots' 39 | :option2.autosize => .t. | 40 | 41 | :class => 'label' :caption => 'Thank you for trying DynamicForm.' 42 | :autosize => .t. 43 | :render-if => (Day(Date()) > 1) 44 | 45 | EndText 46 | 47 | -------------------------------------------------------------------------------- /Demos/logo.prg: -------------------------------------------------------------------------------- 1 | 2 | Private lcName , lnAge, llInput 3 | 4 | lcName = 'John Smith' 5 | lnAge = 0 6 | llInput = .f. 7 | 8 | Set Proc To 'DynamicForm' Additive 9 | loForm = CreateObject('DynamicForm') 10 | 11 | *-- You can set any form property, engine property, or 12 | *-- host container property in normal code, or in markup 13 | *-- as shown below. 14 | loForm.Caption = 'Dynamic Form Example' 15 | loForm.MinWidth = 400 16 | loForm.MinHeight = 300 17 | 18 | *-- Notice these same properties can be set in markup too: 19 | Text to lcBodyMarkup NoShow 20 | 21 | .cHeading = 'Dynamic Form has new features.' 22 | .cSaveButtonCaption = 'OK' 23 | .cCancelButtonCaption = 'No thanks' 24 | .lLabelsAbove = .t. 25 | 26 | .form-caption = 'Dynamic Form Example' 27 | .form-minwidth = 400 28 | .form-minheight = 300 29 | 30 | .container-margin-top = 10 31 | .container-margin-bottom = 10 32 | .container-margin-left = 10 33 | .container-margin-right = 10 34 | .container-borderwidth = 2 35 | .container-bordercolor = (Rgb(200,200,200)) 36 | .container-backcolor = (Rgb(180,208,233)) | 37 | 38 | lcName .caption = 'Name' .width = 200 | 39 | 40 | lnAge .caption = 'Age' .width = 50 | 41 | 42 | llInput .caption = 'Share data with others?' .width = 200 | 43 | 44 | EndText 45 | 46 | loForm.Render(lcBodyMarkup) 47 | 48 | loForm.Show() 49 | 50 | * If Vartype(loForm) = 'O' 51 | * lcReturn = loForm.cReturn 52 | * loform.Release() 53 | 54 | * ? lcName 55 | * ? lnAge 56 | * ? llHavingFunAtSWFox 57 | * 58 | * ?lcReturn 59 | 60 | * Endif 61 | 62 | 63 | * :class = 'commandbutton' :caption = 'OK' :left = 60 :margin-top = 80 :height = 30| 64 | * :class = 'commandbutton' :caption = 'Cancel' :row-increment = 0 :height = 30 | 65 | 66 | 67 | 68 | * :width = '50' :margin-top = 50 :centered = .t. 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | *"How do I define properties/methods in the form that's created?" 81 | 82 | *The answer to this is that you should never use the DynamciForm class directly in your app, rather, you should cub-class it into you own class and extend it from there as needed. 83 | 84 | 85 | *-- Define my own sub-class of DynamicForm -------------- 86 | Define Class MyDynamicForm as DynamicForm 87 | 88 | nMyNewNumeric = 1 89 | cMyNewString = '' 90 | oMyNewObject = .null. 91 | *Width = 500 92 | *Height = 200 93 | MinWidth = 350 94 | MinHeight = 200 95 | 96 | 97 | *--Override the default RenderEngine with my own subclass -------------------- 98 | Procedure Init 99 | 100 | DoDefault() 101 | 102 | This.oRenderEngine = CreateObject('MyDynamicFormRenderEngine') 103 | 104 | Endproc 105 | 106 | *--------------------------------------------------------------------------------------- 107 | Procedure MyNewProc 108 | 109 | Lparameters tcParam1 110 | 111 | Return 112 | 113 | EndProc 114 | 115 | EndDefine 116 | 117 | 118 | *-- Define my own sub-class of DynamicFormRenderEnginer -------------- 119 | Define Class MyDynamicFormRenderEngine as DynamicFormRenderEngine 120 | 121 | nMyNewNumeric = 1 122 | cMyNewString = '' 123 | oMyNewObject = .null. 124 | 125 | Procedure MyNewRednerProc 126 | Lparameters tnParam1 127 | Return 128 | EndProc 129 | 130 | Enddefine 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /changelog.txt: -------------------------------------------------------------------------------- 1 | Dynamic Forms is a new way to create forms based on a "markup syntax" similar to HTML and XAML, in which you specify both the layout and ControlSources for the various controls to be displayed on the form. 2 | 3 | The result will be a dynamically constructed form, specified in code, rather than manually creating it in the Form Designer. 4 | 5 | == Ver 1.9.1 (2017-08-30) 6 | *-- Fixed spelling, capitalization, formatting (no functional changes). 7 | 8 | == Ver 1.9.0 (2017-08-27) 9 | *-- Production release, Migrated from VFPx/CodePlex to GitHub 10 | 11 | == Ver 1.8.2 Beta Release (2014-09-29) 12 | 13 | === Ver 1.7.0 Alpha (2013-10-31) 14 | *-- 1. Added class DF_EditButton which can be used to display a DF_EditButton beside editbox classes. 15 | *-- Memo fields from table/cursor binding will automatically render a DF_EditButton beside the editbox. 16 | *-- Added property lGenerateEditButtonsForMemoFields to RenderEngine to control rendering of DF_EditButtons. 17 | *-- Added support for attribute ".ShowEditButton = .t." in markup which can be used on editbox classes to add 18 | *-- the DF_EditButton bseide the correpsonding control class. Control class must have an EditDate() method which 19 | *-- will be called by the DF_EditButton. 20 | *-- 2. Added oRenderOrder collectiton to each Field in the oFieldList so the attributes 21 | *-- will be applied to each rendered control in the same order as they appear in the markup. 22 | 23 | === Ver 1.6.1 Alpha (2012-12-31) 24 | *-- 1. Added a new UI class which can handle pop-up editing of memo fields via DoubleClicking. 25 | *-- See class: DF_MemoFieldEditBox 26 | *-- 2. Added code to cBodyMarkup_Assign() to store passed value onto form property. 27 | *-- 3. Adjusted ParseField() method so it would not consider "dot" when picking out ControlSource from passed markup. 28 | *-- It breaks on whitespace/newline only. 29 | *-- 4. An object reference to the Render Engine is now added to the passed Container. This is helpful for advanced new features in DF. 30 | 31 | 32 | === Ver 1.6.0 Alpha (2012-12-31) 33 | *-- 1. Added new handling for memo fields when binding to fields in a cursor. DF will now render an editbox for 34 | *-- memo fields on a cursor unless a specific class is specified in the markup. 35 | *-- 2. DF will now attempt to open the table if cAlias table is not already open. 36 | *-- 3. Cleaned up the markup in the default footer layout. 37 | 38 | === Ver 1.5.0 Alpha (2012-10-28) 39 | *-- Added new handling which will automatically detect which delimiters are used in the cBodyMarkup 40 | *-- ( :attribute => value or .attribute = value ) 41 | *-- Revised all markup samples to use . and =, rather than : and => 42 | *-- Render Engine: Added new property lResizeContainer (default = .f.). By default, the Render Engine does 43 | *-- not resize oContainer to fit rendered control. Dynamic Form overrides this property to .t. as it wants 44 | *-- the container resized to fit controls. 45 | *-- Render Engine: Renamed method ResizeMainContainer() to ResizeContainer() 46 | *-- Render Engine: Added oRenderEngine as an object to ParseField() doesn't have to create a new instance 47 | *-- each time it is called. 48 | 49 | === Ver 1.4.1 Alpha (2012-10-26) 50 | *-- Fixed a bug when assigning :left attribute with a string value 51 | 52 | === Ver 1.4.0 Alpha (2012-10-23) 53 | *-- All Form, Render Engine, and Container properties can now be set in the cBodyMarkup block. 54 | *-- Form.Render() will now accept the cBodyMarkup as a paramter. i.e.: loForm.Render(lcBodyMarkup) 55 | *-- Fixed bug which prevented was overriding :width setting if markup resulted in a DF_ErrorContainer control to show an error condition. 56 | 57 | === Ver 1.3.0 Alpha (2012-10-08) 58 | *-- Added support in the markup syntax for including code lines to be executed druing render. Simply wrap code statement in parentheses. 59 | *-- Added support for keeping Modeless forms alive. See Show() method documentation on http://vfpx.codeplex.com/wikipage?title=Dynamic%20Forms%20Properties 60 | *-- Added properties lInHeader and lInBody (already had lInFooter) to track which area the render engine is working in. See BuildMarkup(). 61 | *-- Refactored some code out of Render() method into new BuildMarkup() method. 62 | *-- BuildMarkup() injects some embedded code to indicate the rendering area (Header, Body, Footer). 63 | *-- Added property for oBusinessObject and cBusinessObjectSaveMethod. See http://vfpx.codeplex.com/wikipage?title=Using%20Dynamic%20Forms%20with%20Business%20Objects 64 | *-- Revised Save button to call the Business Object Save method, if everything is assigned. 65 | *-- Added better handling for cSkipFields list. 66 | 67 | === Ver 1.2.0 Alpha (2012-09-25) 68 | *-- Added support for new :set-focus => .t. attribute, which indicates which control will have focus when form is shown. 69 | *-- Improved support for :anchor settings on rendered controls. Code changed in ResizeMainContainer() method. 70 | *-- Restored ability to display all fields on cAlias or oDataObject if no cBodyMarkup is specified. See GetBodyMarkupForAll() and Render(). 71 | *-- Added new property lInFooter to indicate when the rendering has entered the footer area. This property is set in the Footer markup and is used 72 | *-- in processing to ensure the footer controls will be rendered at the proper vertical location. 73 | 74 | === Ver 1.1.0 Alpha (2012-09-23) 75 | *-- Default Footer is now anchored to bottom right of form. 76 | *-- Can now include single lines of FoxPro code in markup to execute at render time. Wrap code code in ( ). 77 | *-- Adjusted vertical spacing of controls DF_HorizontalLine, CommandButtons, CheckBoxes, Pictures 78 | *-- Added class property for new vertical spacing listed above. 79 | *-- Fixed bug when MinHeight and MinWidth are set to a size larger than the render container. 80 | *-- Fixed bug which did not handle Modeless form mode when calling loForm.Show(0) 81 | *-- Added new properties nLastControlLeft, nLastControlBottom, nLastControlRight 82 | 83 | === Ver 1.0.0 Alpha (2012-09-18) 84 | * The markup parser now allows attribute values to omit the surrounding quote marks around numeric and Boolean values. 85 | * Another changed in the markup parser is that expressions used as attribute values (which are evaluated run-time), must now be wrapped in parentheses. 86 | * (Credit to Jim Nelson for supplying these parser updates.) 87 | * New properties on the Render Engine allow you to specify custom delimiter patterns for cAttributeNameDelimiterPattern and cAttributeValueDelimiterPattern. 88 | 89 | 90 | === Ver 0.9.033 (2012-09-12) 91 | * Removed Button Bar controls from Form, replaced with cHeaderMarkup and cFooterMarkup. 92 | * cHeaderMarkup and cFooterMarkup are default markup strings which wrap around cBodyMarkup 93 | * to create the entire form. Either cHeaderMarkup and cFooterMarkup can be set to empty to 94 | * prevent them from being added to user supplied cBodyMarkup. They can also be overridden 95 | * by the developer to create custom Header and Footer markup layouts. 96 | * Also added cHeading and cHeadingFontSize which used by cHearderMarkup to create a title on the form. 97 | 98 | === Ver 0.9.032 (2012-09-12) 99 | * Removed Anchor preset values from DF_SaveButton and DF_CancelButton classes 100 | 101 | === Ver 0.9.031 (2012-09-11) 102 | * Refactored main rendering method into many smaller methods 103 | * For DF_ResultButton, added Caption_Assign() method to set Tag = Caption. 104 | * Added code in DynamicForm.QueryUnload() method to call RestoreData() 105 | * Moved code from DynamicForm.cmdSave.Click() and cmdCancel.Click(), down to their baseclasss, DF_SaveButton and DF_CancelButton. 106 | * Updates notes in sample code prg for steps 1 through 7 107 | * Documentation updates to VFPx pages. 108 | 109 | === Ver 0.9.030 (2012-09-10) 110 | * Added property cButtonBarLocation ('top', 'bottom', 'none') 111 | * Added support for Horizontal Lines via :class => 'DF_HorizontalLine' 112 | * Moved default location of "Button Bar" to 'bottom' of form. 113 | * Renamed lblHeading to lblButtonBar 114 | * Renamed TopLine to lineButtonBar 115 | 116 | === Ver 0.9.029 (2012-09-10) 117 | * Updated vertical positioning logic for better layout flow. 118 | 119 | === Ver 0.9.027 (2012-09-08) 120 | * Added new RenderEngine property and logic for: lAutoAdjustVerticalPositionAndHeight 121 | * Renamed property nMaxHeight to nColumnHeight 122 | * Major updates to documentation pages. See VFPs site for new and changed pages. 123 | 124 | === Ver 0.9.026 (2012-09-05) 125 | * Added more tests on the attribute values to report syntax or value errors 126 | * Added GetErrorsAsString() method to the RenderEnginer to show any errors. 127 | * Updates documentation pages on VFPx site. 128 | 129 | === Ver 0.9.025 (2012-09-04) 130 | * Added support for OptionGroup (radio buttons) 131 | 132 | === Ver 0.9.022 (2012-09-04) 133 | * Another attempt to fix bug that can happen when there is more than one column on the render surface. 134 | 135 | === Ver 0.9.021 (2012-09-04) 136 | * Fixed bug that can happen when there is more than one column on the render surface. 137 | 138 | === Ver 0.9.020 (2012-09-04) 139 | * By defaualt, the Save button is now set as 'Default' property on the form (will close form on Enter) 140 | * By defaualt, the Cancel button is now set as 'Cancel' property on the form (will close form on Escape) 141 | * Fixed bug with rendering all fields via cFieldList = '*' 142 | * Added properties to form for nMinWidth and nMinHeight 143 | 144 | === Ver 0.9.019 (2012-09-04) 145 | * Added complete list of baseclasses to the default class/class lib properlinks on RenderEngine 146 | 147 | === Ver 0.9.018 (2012-09-04) 148 | * Added links to the Dynamic Form page on the VFPx site and Google Groups disucssion forum. 149 | 150 | === Ver 0.9.017 (2012-09-03) 151 | * Revised logic for column management. 152 | * Revied to show labels above the error containter (when the field/property is invalid) 153 | 154 | === Ver 0.9.016 (2012-09-01) 155 | * Fixed bug in ParseField() that did not properly address cr/lf in passed parameter from cFieldList. 156 | * Fixed default label captions when binding to variables 157 | * Documentation updates 158 | 159 | === Ver 0.9.015 (2012-08-31) 160 | * Added :render-if attribute 161 | * Added DF_ErrorContainer class to display errors on the form 162 | * Added support for commans between attributes in cFieldList (they are optional) 163 | * Documentation updates 164 | 165 | === Ver 0.9.014 (2012-08-30) 166 | * Added RestoreData() support to restore all fields to original values upon Cancel. 167 | * Documentation updates 168 | 169 | === Ver 0.9.013 (2012-08-29) 170 | * Added lClearEventsOnClose property of DynamicForm class 171 | * Added nErrorCount and oErrors collection to the DynamicFormRenderEngine class 172 | * Documentation updates 173 | 174 | === Ver 0.9.011 (2012-08-29) 175 | * Bug fixes related to new cAlias setting 176 | * Documentation updates 177 | 178 | === Ver 0.9.010 (2012-08-28) 179 | * Added cAlias property to support binding to a cursor/alias 180 | 181 | === Ver 0.9.009 (2012-08-27) 182 | * Added Cancel button to DynamicForm 183 | 184 | === Ver 0.9.008 (2012-08-27) 185 | * Corrected horizontal spacing between controls when :row-increment => '0' 186 | 187 | === Ver 0.9.007 (2012-08-26) 188 | * Added propertied on DynamicFormRenderEnginer to specify custom class/class for Listboxes. 189 | * Updated Documentation area at top of PRG file. 190 | 191 | === Ver 0.9.005 (2012-08-25) 192 | * Separated Render() method from Show() method to give users a chance to modify the controls 193 | * before the form is shown. 194 | 195 | === Ver 0.9.004 (2012-08-24) 196 | * Add lLabelsABove property to RenderEngine to locate labels above the input controls 197 | * Added :row-increment and :row attributes -------------------------------------------------------------------------------- /_DynamicFormVersionFile.txt: -------------------------------------------------------------------------------- 1 | Lparameters toUpdateInfo 2 | 3 | Text to lcNote NoShow 4 | Dynamic Forms is a new way to create forms based on a "markup syntax" similar to HTML and XAML, in which you specify both the layout and ControlSources for the various controls to be displayed on the form. 5 | 6 | The result will be a dynamically constructed form, specified in code, rather than manually creating it in the Form Designer. 7 | 8 | == Ver 1.9.1 (2017-08-30) 9 | *-- Fixed spelling, capitalization, formatting (no functional changes). 10 | 11 | == Ver 1.9.0 (2017-08-27) 12 | *-- Production release, Migrated from VFPx/CodePlex to GitHub 13 | 14 | == Ver 1.8.2 Beta Release (2014-09-29) 15 | 16 | === Ver 1.7.0 Alpha (2013-10-31) 17 | *-- 1. Added oRenderOrder colleciton to each Field in the oFieldList so the attributes 18 | *-- will be applied to each rendered conrtol in the same order as they appear in the markup. 19 | 20 | === Ver 1.6.1 Alpha (2012-12-31) 21 | *-- 1. Added a new UI class which can handle pop-up editing of memo fields via DoubleClicking. 22 | *-- See class: DF_MemoFieldEditBox 23 | *-- 2. Added code to cBodyMarkup_Assign() to store passed value onto form property. 24 | *-- 3. Adjusted ParseField() method so it would not consider "dot" when picking out ControlSource from passed markup. 25 | *-- It breaks on whitespace/newline only. 26 | *-- 4. An object reference to the Render Engine is now added to the passed Container. This is helpful for advanced new features in DF. 27 | 28 | 29 | === Ver 1.6.0 Alpha (2012-12-31) 30 | *-- 1. Added new handling for memo fields when binding to fields in a cursor. DF will now render an editbox for 31 | *-- memo fields on a cursor unless a specific class is specified in the markup. 32 | *-- 2. DF will now attempt to open the table if cAlias table is not already open. 33 | *-- 3. Cleaned up the markup in the default footer layout. 34 | 35 | === Ver 1.5.0 Alpha (2012-10-28) 36 | *-- Added new handling which will automatically detect which delimiters are used in the cBodyMarkup 37 | *-- ( :attribute => value or .attribute = value ) 38 | *-- Revised all markup samples to use . and =, rather than : and => 39 | *-- Render Engine: Added new property lResizeContainer (default = .f.). By default, the Render Engine does 40 | *-- not resize oContainer to fit rendered control. Dynamic Form overrides this property to .t. as it wants 41 | *-- the container resized to fit controls. 42 | *-- Render Engine: Renamed method ResizeMainContainer() to ResizeContainer() 43 | *-- Render Engine: Added oRenderEngine as an object to ParseField() doesn't have to create a new instance 44 | *-- each time it is called. 45 | 46 | === Ver 1.4.1 Alpha (2012-10-26) 47 | *-- Fixed a bug when assigning :left attribute with a string value 48 | 49 | === Ver 1.4.0 Alpha (2012-10-23) 50 | *-- All Form, Render Engine, and Container properties can now be set in the cBodyMarkup block. 51 | *-- Form.Render() will now accept the cBodyMarkup as a paramter. i.e.: loForm.Render(lcBodyMarkup) 52 | *-- Fixed bug which prevented was overriding :width setting if markup resulted in a DF_ErrorContainer control to show an error condition. 53 | 54 | === Ver 1.3.0 Alpha (2012-10-08) 55 | *-- Added support in the markup syntax for including code lines to be executed druing render. Simply wrap code statement in parentheses. 56 | *-- Added support for keeping Modeless forms alive. See Show() method documentation on http://vfpx.codeplex.com/wikipage?title=Dynamic%20Forms%20Properties 57 | *-- Added properties lInHeader and lInBody (already had lInFooter) to track which area the render engine is working in. See BuildMarkup(). 58 | *-- Refactored some code out of Render() method into new BuildMarkup() method. 59 | *-- BuildMarkup() injects some embedded code to indicate the rendering area (Header, Body, Footer). 60 | *-- Added property for oBusinessObject and cBusinessObjectSaveMethod. See http://vfpx.codeplex.com/wikipage?title=Using%20Dynamic%20Forms%20with%20Business%20Objects 61 | *-- Revised Save button to call the Business Object Save method, if everything is assigned. 62 | *-- Added better handling for cSkipFields list. 63 | 64 | === Ver 1.2.0 Alpha (2012-09-25) 65 | *-- Added support for new :set-focus => .t. attribute, which indicates which control will have focus when form is shown. 66 | *-- Improved support for :anchor settings on rendered controls. Code changed in ResizeMainContainer() method. 67 | *-- Restored ability to display all fields on cAlias or oDataObject if no cBodyMarkup is specified. See GetBodyMarkupForAll() and Render(). 68 | *-- Added new property lInFooter to indicate when the rendering has entered the footer area. This property is set in the Footer markup and is used 69 | *-- in processing to ensure the footer controls will be rendered at the proper vertical location. 70 | 71 | === Ver 1.1.0 Alpha (2012-09-23) 72 | *-- Default Footer is now anchored to bottom right of form. 73 | *-- Can now include single lines of FoxPro code in markup to execute at render time. Wrap code code in ( ). 74 | *-- Adjusted vertical spacing of controls DF_HorizontalLine, CommandButtons, CheckBoxes, Pictures 75 | *-- Added class property for new vertical spacing listed above. 76 | *-- Fixed bug when MinHeight and MinWidth are set to a size larger than the render container. 77 | *-- Fixed bug which did not handle Modeless form mode when calling loForm.Show(0) 78 | *-- Added new properties nLastControlLeft, nLastControlBottom, nLastControlRight 79 | 80 | === Ver 1.0.0 Alpha (2012-09-18) 81 | * The markup parser now allows attribute values to omit the surrounding quote marks around numeric and Boolean values. 82 | * Another changed in the markup parser is that expressions used as attribute values (which are evaluated run-time), must now be wrapped in parentheses. 83 | * (Credit to Jim Nelson for supplying these parser updates.) 84 | * New properties on the Render Engine allow you to specify custom delimiter patterns for cAttributeNameDelimiterPattern and cAttributeValueDelimiterPattern. 85 | 86 | 87 | === Ver 0.9.033 (2012-09-12) 88 | * Removed Button Bar controls from Form, replaced with cHeaderMarkup and cFooterMarkup. 89 | * cHeaderMarkup and cFooterMarkup are default markup strings which wrap around cBodyMarkup 90 | * to create the entire form. Either cHeaderMarkup and cFooterMarkup can be set to empty to 91 | * prevent them from being added to user supplied cBodyMarkup. They can also be overridden 92 | * by the developer to create custom Header and Footer markup layouts. 93 | * Also added cHeading and cHeadingFontSize which used by cHearderMarkup to create a title on the form. 94 | 95 | === Ver 0.9.032 (2012-09-12) 96 | * Removed Anchor preset values from DF_SaveButton and DF_CancelButton classes 97 | 98 | === Ver 0.9.031 (2012-09-11) 99 | * Refactored main rendering method into many smaller methods 100 | * For DF_ResultButton, added Caption_Assign() method to set Tag = Caption. 101 | * Added code in DynamicForm.QueryUnload() method to call RestoreData() 102 | * Moved code from DynamicForm.cmdSave.Click() and cmdCancel.Click(), down to their baseclasss, DF_SaveButton and DF_CancelButton. 103 | * Updates notes in sample code prg for steps 1 through 7 104 | * Documentation updates to VFPx pages. 105 | 106 | === Ver 0.9.030 (2012-09-10) 107 | * Added property cButtonBarLocation ('top', 'bottom', 'none') 108 | * Added support for Horizontal Lines via :class => 'DF_HorizontalLine' 109 | * Moved default location of "Button Bar" to 'bottom' of form. 110 | * Renamed lblHeading to lblButtonBar 111 | * Renamed TopLine to lineButtonBar 112 | 113 | === Ver 0.9.029 (2012-09-10) 114 | * Updated vertical positioning logic for better layout flow. 115 | 116 | === Ver 0.9.027 (2012-09-08) 117 | * Added new RenderEngine property and logic for: lAutoAdjustVerticalPositionAndHeight 118 | * Renamed property nMaxHeight to nColumnHeight 119 | * Major updates to documentation pages. See VFPs site for new and changed pages. 120 | 121 | === Ver 0.9.026 (2012-09-05) 122 | * Added more tests on the attribute values to report syntax or value errors 123 | * Added GetErrorsAsString() method to the RenderEnginer to show any errors. 124 | * Updates documentation pages on VFPx site. 125 | 126 | === Ver 0.9.025 (2012-09-04) 127 | * Added support for OptionGroup (radio buttons) 128 | 129 | === Ver 0.9.022 (2012-09-04) 130 | * Another attempt to fix bug that can happen when there is more than one column on the render surface. 131 | 132 | === Ver 0.9.021 (2012-09-04) 133 | * Fixed bug that can happen when there is more than one column on the render surface. 134 | 135 | === Ver 0.9.020 (2012-09-04) 136 | * By defaualt, the Save button is now set as 'Default' property on the form (will close form on Enter) 137 | * By defaualt, the Cancel button is now set as 'Cancel' property on the form (will close form on Escape) 138 | * Fixed bug with rendering all fields via cFieldList = '*' 139 | * Added properties to form for nMinWidth and nMinHeight 140 | 141 | === Ver 0.9.019 (2012-09-04) 142 | * Added complete list of baseclasses to the default class/class lib properlinks on RenderEngine 143 | 144 | === Ver 0.9.018 (2012-09-04) 145 | * Added links to the Dynamic Form page on the VFPx site and Google Groups disucssion forum. 146 | 147 | === Ver 0.9.017 (2012-09-03) 148 | * Revised logic for column management. 149 | * Revied to show labels above the error containter (when the field/property is invalid) 150 | 151 | === Ver 0.9.016 (2012-09-01) 152 | * Fixed bug in ParseField() that did not properly address cr/lf in passed parameter from cFieldList. 153 | * Fixed default label captions when binding to variables 154 | * Documentation updates 155 | 156 | === Ver 0.9.015 (2012-08-31) 157 | * Added :render-if attribute 158 | * Added DF_ErrorContainer class to display errors on the form 159 | * Added support for commans between attributes in cFieldList (they are optional) 160 | * Documentation updates 161 | 162 | === Ver 0.9.014 (2012-08-30) 163 | * Added RestoreData() support to restore all fields to original values upon Cancel. 164 | * Documentation updates 165 | 166 | === Ver 0.9.013 (2012-08-29) 167 | * Added lClearEventsOnClose property of DynamicForm class 168 | * Added nErrorCount and oErrors collection to the DynamicFormRenderEngine class 169 | * Documentation updates 170 | 171 | === Ver 0.9.011 (2012-08-29) 172 | * Bug fixes related to new cAlias setting 173 | * Documentation updates 174 | 175 | === Ver 0.9.010 (2012-08-28) 176 | * Added cAlias property to support binding to a cursor/alias 177 | 178 | === Ver 0.9.009 (2012-08-27) 179 | * Added Cancel button to DynamicForm 180 | 181 | === Ver 0.9.008 (2012-08-27) 182 | * Corrected horizontal spacing between controls when :row-increment => '0' 183 | 184 | === Ver 0.9.007 (2012-08-26) 185 | * Added propertied on DynamicFormRenderEnginer to specify custom class/class for Listboxes. 186 | * Updated Documentation area at top of PRG file. 187 | 188 | === Ver 0.9.005 (2012-08-25) 189 | * Separated Render() method from Show() method to give users a chance to modify the controls 190 | * before the form is shown. 191 | 192 | === Ver 0.9.004 (2012-08-24) 193 | * Add lLabelsABove property to RenderEngine to locate labels above the input controls 194 | * Added :row-increment and :row attributes 195 | EndText 196 | 197 | 198 | AddProperty(toUpdateInfo, 'AvailableVersion', 'Dynamic Form - 1.9.1 - August 30, 2017 - 20170830') 199 | AddProperty(toUpdateInfo, 'SourceFileUrl', 'https://raw.githubusercontent.com/VFPX/DynamicForms/master/DynamicForms.zip') 200 | AddProperty(toUpdateInfo, 'LinkPrompt', 'Dynamic Forms Home Page') 201 | AddProperty(toUpdateInfo, 'Link', 'https://github.com/VFPX/DynamicForms') 202 | AddProperty(toUpdateInfo, 'Notes', lcNote) 203 | 204 | Execscript (_Screen.cThorDispatcher, 'Result=', toUpdateInfo) 205 | Return toUpdateInfo 206 | -------------------------------------------------------------------------------- /df_tests.prg: -------------------------------------------------------------------------------- 1 | Private loJob, loObject 2 | Private lnPrice, laOptions[1], lnOption 3 | 4 | loObject = CreateObject('Empty') 5 | AddProperty(loObject , 'id', '12345') 6 | AddProperty(loObject , 'first_name', 'Joe') 7 | AddProperty(loObject , 'mid_init', 'N.') 8 | AddProperty(loObject , 'last_name', 'Coderman') 9 | AddProperty(loObject , 'ad_type', 'Banner') 10 | AddProperty(loObject , 'notes', 'This man came here and wrote some codez.') 11 | AddProperty(loObject , 'still_here', .f.) 12 | AddProperty(loObject , 'has_laptop', .t.) 13 | AddProperty(loObject , 'bool_1', .t.) 14 | AddProperty(loObject , 'bool_2', .t.) 15 | AddProperty(loObject , 'bool_3', .t.) 16 | AddProperty(loObject , 'weight', 185) 17 | 18 | *-- Example of a Private variables that can be bound to also 19 | lnPrice = 107.15 20 | lnOption = 2 21 | 22 | *-- Array of options/values to display in the ComboBox defined in cFieldList above (note that it's declared Private above --- 23 | Dimension laOptions[3] 24 | laOptions[1] = 'Banner' 25 | laOptions[2] = 'Placard' 26 | laOptions[3] = 'Name Tag' 27 | 28 | 29 | 30 | Cd h:\work\repos\dynamicForm && Adjust path to you local dev environment as needed 31 | Set Procedure To DynamicForm Additive && Adjust path to you local dev environment as needed 32 | 33 | If !Used('Jobs') 34 | Use Demos\Jobs In 0 35 | EndIf 36 | 37 | Select Jobs 38 | Goto top 39 | Scatter Name loJob Memo 40 | 41 | Set Procedure To DynamicForm additive 42 | 43 | ? ' ' 44 | ? ' ' 45 | ? ' ' 46 | 47 | *clear 48 | Do execute_code_test 49 | 50 | Do largedemo1 with .f., '.class = "label" .caption = "Thanks!!" | .class = "DF_HorizontalLine"', '', '.class = "DF_CancelButton"' 51 | 52 | Do passing_a_container_to_the_render_engine_test 53 | Do attributes_with_numbers_passed_as_strings_test 54 | Do jrn_syntax_test 55 | Do bind_to_all_fields_on_an_alias_test 56 | Do modeless_form_test 57 | Do minheight_and_minwidth_test 58 | Do binding_errors_test 59 | Do largedemo1 with .t. 60 | Do largedemo1 with .f. 61 | Do largedemo1 with .t., '', '', '.class = "DF_CancelButton"' 62 | Do largedemo1 with .t., '.class = "DF_CancelButton"', '', '' 63 | 64 | 65 | *--------------------------------------------------------------------------------------- 66 | Procedure passing_a_container_to_the_render_engine_test 67 | 68 | Local loContainer as 'container' 69 | Local loEngine as 'DynamicFormRenderEngine' 70 | Local lcBodyMarkup, llResult 71 | 72 | PrintTestName() 73 | 74 | loEngine = CreateObject('DynamicFormRenderEngine') 75 | 76 | lcBodyMarkup = "job_num .enabled = .F. | cust_num | status | job_start |" 77 | loEngine.cFooterMarkup = '' 78 | 79 | loEngine.cBodyMarkup = lcBodyMarkup 80 | loEngine.oDataObject = loJob 81 | loEngine.lLabelsAbove = .t. 82 | 83 | loContainer = CreateObject('container') 84 | llResult = loEngine.Render(loContainer) 85 | 86 | PrintResult(llResult) 87 | 88 | Endproc 89 | 90 | 91 | *--------------------------------------------------------------------------------------- 92 | Procedure modeless_form_test 93 | 94 | Local loForm as 'DynamicForm' 95 | Local lcBodyMarkup, llResult 96 | 97 | PrintTestName() 98 | 99 | loForm = CreateObject('DynamicForm') 100 | lcBodyMarkup = BigFieldList() 101 | loForm.cBodyMarkup = lcBodyMarkup 102 | loForm.cHeading = gcTestName 103 | loForm.oRenderEngine.lLabelsAbove = .t. 104 | loForm.oDataObject = loObject && Set the data object that the form fields bind to 105 | 106 | llResult = loForm.Render() 107 | 108 | loForm.lClearEventsOnClose = .t. 109 | loForm.Show(0) 110 | 111 | Read events 112 | 113 | PrintResult(llResult) 114 | 115 | Endproc 116 | 117 | *--------------------------------------------------------------------------------------- 118 | Procedure jrn_syntax_test 119 | 120 | Local loForm as 'DynamicForm' 121 | Local lcBodyMarkup, llResult 122 | 123 | PrintTestName() 124 | 125 | loForm = CreateObject('DynamicForm') 126 | lcBodyMarkup = BigFieldList() 127 | lcBodyMarkup = Strtran(lcBodyMarkup, ':', '.') 128 | lcBodyMarkup = Strtran(lcBodyMarkup, '=', '=') 129 | loForm.cHeading = gcTestName 130 | loForm.oRenderEngine.lLabelsAbove = .t. 131 | loForm.oDataObject = loObject && Set the data object that the form fields bind to 132 | 133 | llResult = loForm.Render(lcBodyMarkup ) 134 | PrintResult(llResult) 135 | loForm.Show() 136 | 137 | 138 | Endproc 139 | *--------------------------------------------------------------------------------------- 140 | Procedure attributes_with_numbers_passed_as_strings_test 141 | 142 | Local loForm as 'DynamicForm' 143 | Local lcBodyMarkup, llResult 144 | 145 | Private lcTest 146 | 147 | PrintTestName() 148 | 149 | lcTest = '' 150 | loForm = CreateObject('DynamicForm') 151 | 152 | loForm.cHeading = gcTestName 153 | loForm.oRenderEngine.lLabelsAbove = .t. 154 | 155 | Text to lcBodyMarkup NoShow 156 | 157 | .class = 'label' .caption = 'This next textbox should be pushed down and in very far due to margin-top and margin-left ' 158 | .width = 500 .height = 100 .wordwrap = .t. | 159 | 160 | lcTest .left = 10 .margin-left = 200 .top = 70 .margin-top = 200 .margin-bottom = 200 | 161 | 162 | .class = 'label' .caption = 'This label should be pushed down due to margin-bottom of previous control' 163 | .width = 500 .height = 100 .wordwrap = .t. 164 | 165 | EndText 166 | 167 | llResult = loForm.Render(lcBodyMarkup) 168 | PrintResult(llResult) 169 | loForm.Show() 170 | 171 | Endproc 172 | 173 | *--------------------------------------------------------------------------------------- 174 | Procedure bind_to_all_fields_on_an_alias_test 175 | 176 | Local loForm as 'DynamicForm' 177 | Local lcBodyMarkup, llResult 178 | 179 | PrintTestName() 180 | 181 | loForm = CreateObject('DynamicForm') 182 | loForm.cHeading = gcTestName 183 | loForm.cAlias = 'Jobs' 184 | loForm.oRenderEngine.nColumnHeight = 400 185 | 186 | llResult = loForm.Render() 187 | PrintResult(llResult) 188 | loForm.Show() 189 | 190 | Endproc 191 | 192 | *--------------------------------------------------------------------------------------- 193 | Procedure minheight_and_minwidth_test 194 | 195 | Local loForm as 'DynamicForm' 196 | Local lcBodyMarkup, llResult 197 | 198 | PrintTestName() 199 | 200 | loForm = CreateObject('DynamicForm') 201 | loForm.cHeading = gcTestName 202 | lcBodyMarkup = 'Month | llProj .Caption = "Projections?"' 203 | 204 | Text to lcBodyMarkup Noshow 205 | 206 | .class = 'label' .width = 300 .height = 100 .wordwrap = .t. 207 | .caption = 'This test tests the MinWidth and MinHeight for the form, so the form should render much larger than 208 | its normal resize-to-fit-controls behaviour.' 209 | 210 | EndText 211 | 212 | loForm.MinHeight = 700 213 | loForm.MinWidth = 700 214 | 215 | llResult = loForm.Render(lcBodyMarkup) 216 | PrintResult(llResult) 217 | loForm.Show() 218 | 219 | Endproc 220 | 221 | *--------------------------------------------------------------------------------------- 222 | Procedure execute_code_test 223 | 224 | Local loForm as 'DynamicForm' 225 | Local lcBodyMarkup, llResult 226 | 227 | PrintTestName() 228 | 229 | loForm = CreateObject('DynamicForm') 230 | 231 | Text to lcBodyMarkup Noshow 232 | 233 | (MessageBox('Executing some VFP code from the markup syntax.')) | 234 | .cHeading = (gcTestName) | 235 | (This.nLastControlBottom = 200) | 236 | .class = 'label' 237 | .width = 300 238 | .height = 100 239 | .wordwrap = .t. 240 | .caption = 'This test executes some FoxPro code.' | 241 | (? Chr(13) + Chr(13) + Chr(13) + 'Did you see a messagebox?') | 242 | 243 | 244 | 245 | EndText 246 | 247 | loForm.MinHeight = 700 248 | loForm.MinWidth = 700 249 | 250 | llResult = loForm.Render(lcBodyMarkup) 251 | PrintResult(llResult) 252 | loForm.Show() 253 | 254 | Endproc 255 | 256 | *--------------------------------------------------------------------------------------- 257 | Procedure binding_errors_test 258 | 259 | Local loForm as 'DynamicForm' 260 | Local lcBodyMarkup, llResult 261 | Private lcTest 262 | 263 | PrintTestName() 264 | 265 | loForm = CreateObject('DynamicForm') 266 | loForm.cHeading = gcTestName 267 | lcBodyMarkup = 'lnMonth | llProj' 268 | 269 | Text to lcBodyMarkup Noshow 270 | llNotDefined_1 | 271 | llNotDefined_2 .width = 200 | 272 | .class = 'label' .width = 300 .height = 100 .wordwrap = .t. 273 | .caption = ('This markup attempts to bind to private vars which are not defined. There should be' + 274 | 'two red error boxes on the form.') 275 | 276 | EndText 277 | 278 | llResult = loForm.Render(lcBodyMarkup) 279 | PrintResult(loForm.oRenderEngine.nErrorCount = 2) 280 | loForm.Show() 281 | 282 | Endproc 283 | 284 | 285 | 286 | *--------------------------------------------------------------------------------------- 287 | Procedure LargeDemo1 288 | 289 | Lparameters tlLabelsAbove, tcHeaderMarkup, tcBodymarkup, tcFooterMarkup 290 | 291 | Local loForm as 'DynamicForm' 292 | 293 | PrintTestName() 294 | 295 | loForm = CreateObject('DynamicForm') 296 | 297 | loForm.Caption = 'Edit data' 298 | loForm.oDataObject = loObject && Set the data object that the form fields bind to 299 | loForm.cHeading = 'cHeading appears here, if set.' 300 | 301 | loForm.oRenderEngine.lLabelsAbove = tlLabelsAbove && Generate label above each control (default is to the left of the control) (default is .F., which meaans "inline with controls") 302 | 303 | *-- Set Header markup ------------------------- 304 | If Vartype(tcHeaderMarkup) = 'C' 305 | loForm.cHeaderMarkup = tcHeaderMarkup 306 | Endif 307 | 308 | *-- Set Body markup ------------------------- 309 | If Empty(tcBodymarkup) 310 | loForm.oRenderEngine.cBodyMarkup = BigFieldList() 311 | Else 312 | loForm.oRenderEngine.cBodyMarkup = tcBodymarkup 313 | Endif 314 | 315 | *-- Set Footer markup ------------------------- 316 | If Vartype(tcFooterMarkup) = 'C' 317 | loForm.cFooterMarkup = tcFooterMarkup 318 | Endif 319 | 320 | llResult = loForm.Render() 321 | 322 | If llResult = .t. 323 | ?? 'Passed.' 324 | loForm.Show(1) && 1 = Modal. If you want to use Modeless, you'll need make a Read Events call here after showing form. 325 | Else 326 | ?? '*** Failed.***' 327 | MessageBox(loForm.oRenderEngine.GetErrorsAsString() , 0, 'Notice:') 328 | loForm.Show(1) 329 | Endif 330 | 331 | If Vartype(loForm) = 'O' and Lower(loForm.cReturn) = 'save' 332 | Else 333 | EndIf 334 | 335 | Release loForm 336 | 337 | Endproc 338 | 339 | 340 | 341 | *======================================================================================= 342 | Procedure BigFieldList 343 | 344 | Local lcBodyMarkup 345 | 346 | Text to lcBodyMarkup NoShow 347 | 348 | id .enabled = .f. 349 | .fontbold = .t. 350 | .label.FontBold = .t. | 351 | 352 | ad_type .class = 'combobox' 353 | .RowSource = 'laOptions' 354 | .RowSourceType = 5 355 | .top = 150| 356 | 357 | bool_3 .caption = 'You can specify BOLD captions.' 358 | .FontBold = '.t.' 359 | .width = '200' 360 | .margin-left = 50 | 361 | 362 | first_name | 363 | mid_init .row-increment = 0 | 364 | last_name .row-increment = 0 | 365 | 366 | notes .class = 'editbox' 367 | .width = 400 368 | .height = 80 369 | .anchor = 10 370 | .margin-left = 1 371 | .margin-top = 1 372 | .margin-bottom = 1 | 373 | 374 | lnPrice .label.caption = 'List Price' 375 | .label.alignment = 1 | 376 | 377 | weight .row-increment = 0 378 | .label.alignment = 1 | 379 | 380 | lnOption .class = 'optiongroup' 381 | .caption = 'Color options.' 382 | .buttoncount = 2 383 | .width = 200 384 | .height = 60 385 | .option1.caption = 'Red with orange stripes' 386 | .option1.autosize = .t. 387 | .option2.caption = 'Purple with black dots' 388 | .option2.autosize = .t. | 389 | 390 | .class = 'label' .caption = 'Thank you for trying DynamicForm.' 391 | .autosize = .t. 392 | .render-if = (Day(Date()) > 0) 393 | .column = 1 394 | 395 | EndText 396 | 397 | Return lcBodyMarkup 398 | 399 | 400 | Endproc 401 | 402 | 403 | *--------------------------------------------------------------------------------------- 404 | Procedure PrintResult(tlResult) 405 | 406 | If tlResult 407 | ?? ' passed.' 408 | Else 409 | ?? '*** Failed.***' 410 | Endif 411 | 412 | 413 | Endproc 414 | 415 | *--------------------------------------------------------------------------------------- 416 | Procedure PrintTestName 417 | 418 | Public gcTestName 419 | 420 | gcTestName = Lower(GetWordNum(Sys(16, 2), 2)) 421 | 422 | ? gcTestName + '...' 423 | 424 | 425 | Endproc 426 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dynamic Forms 2 | Dynamically generate UI forms from objects or cursors via a "markup syntax” in your code. 3 | 4 | > Note: Matt's original repository is at https://github.com/mattslay/DynamicForms but since he sadly passed away in 2021, this fork is now the one the VFPX project list links to so others can contribute to the project. 5 | 6 | ![Dynamic Forms Logo](Documentation/Images/Dynamic%20Forms%20Logo.jpg "Dynamic Forms Logo") 7 | 8 | Original CodePlex link: https://vfpx.codeplex.com/wikipage?title=Dynamic%20Forms 9 | 10 | author: Matt Slay 11 | 12 | credits: Jim Nelson, Doug Hennig, Rick Schummer 13 | 14 | * * * 15 | 16 | **Discussion Group** 17 | 18 | [Join the discussion group to post or read usage discussions.](https://groups.google.com/forum/?fromgroups#!forum/foxprodynamicforms) 19 | 20 | After joining the group, post through web access in the discussion portal, or email discussion questions (with screenshots) to: 21 | 22 |     [foxprodynamicforms@googlegroups.com](mailto:foxprodynamicforms@googlegroups.com) 23 | * * * 24 | 25 | **Videos** 26 | 27 | * Video #1 – [Introduction and Demos](http://bit.ly/DynamicForms-Video-1)  (8:15) 28 | * Video #2 – [Exploring the class PRG and code sample](http://bit.ly/Dynamic-Forms-Video-2)  (9:09) 29 | 30 | * * * 31 | 32 | ### What are Dynamic Forms? 33 | 34 | Dynamic Forms is a new way to create forms based on a "[markup syntax](#markup_syntax)" similar to HTML and XAML, in which you specify both the layout and ControlSources for the various controls to be displayed on the form. 35 | 36 | The result will be a dynamically constructed form, specified in code, rather than manually creating it in the Form Designer. 37 | 38 | ### Source code 39 | 40 | The source code for this class is a single PRG file which contains the class definitions needed to create Dynamic Forms in your apps, and also includes as a ready-to-run sample to display a rendered form sample. 41 | 42 | The procedural code at the top of the source code PRG serves as an example of how to use the DynamicForm class at run time to dynamically render a live FoxPro form to view and edit properties on an object. As listed below, you can also bind to Fields on Cursors/Aliases/Tables/Views as well as defined Private or Public variables. 43 | 44 | ### Documentation Links 45 | 46 | - [Sample](#sample) 47 | - [Basic Usage](#basic_usage) 48 | - [Binding Form Controls to Data](#binding) 49 | - [Markup Syntax – Designing your form layout](#markup_syntax) 50 | - [Field Labels](#field_labels) 51 | - [Header, Body,  and Footer areas on the Form](http://vfpx.codeplex.com/wikipage?title=Dynamic%20Form%20Main%20Form%20Layout) 52 | - [Return Values from the form](#custom_return_values) 53 | - [Restoring data changes  made by the user](http://vfpx.codeplex.com/wikipage?title=Restoring%20Data%20Values) 54 | - [Dynamic Form Class - Properties and Methods](http://vfpx.codeplex.com/wikipage?title=Dynamic%20Forms%20Properties) 55 | - [Additional Markup support for setting Form and Render Engine properties](http://vfpx.codeplex.com/wikipage?title=Dynamic%20Form%20Example%202) 56 | 57 | Other references: 58 | - [Dynamic Form Render Engine Class - Properties and Methods](http://vfpx.codeplex.com/wikipage?title=Dynamic%20Forms%20Properties#RenderEngine) 59 | - [Subclassing DynamicForm and DynamicFormRenderEngine](http://vfpx.codeplex.com/wikipage?title=Dynamic%20Forms%20Advanced%20Topics#subclassing) 60 | - [Working with Business Objects](http://vfpx.codeplex.com/wikipage?title=Using%20Dynamic%20Forms%20with%20Business%20Objects) 61 | - [Change Log](http://vfpx.codeplex.com/wikipage?title=Change%20Log) 62 | 63 | 64 | 65 | * * * 66 | 67 | ### Sample 68 | 69 | The following form was generated strictly from markup syntax using the DynamicForm class: 70 | 71 | ![](Documentation/Images/SNAGHTML195fba9c_thumb.png "Sample form") 72 | 73 | 74 | ### Code / Markup 75 | 76 | Here is the markup syntax that was used to generate the above sample form. It is only a partial display of all the capabilities that the markup syntax brings to declarative form layout. 77 | 78 | _(See the [complete markup guide](#markup_syntax) for full details)_ 79 | 80 | * * * 81 | ![](Documentation/Images/code_sample_01.png "Code sample") 82 | * * * 83 | 84 | 85 | 86 | ### Basic Usage 87 | 88 | The source code PRG contains a ready-to-run code sample that follows these 7 steps. Please review it. 89 | 90 | The basic flow goes like this: 91 | 92 | 93 | 94 | **Step 1**: Data source(s) for the UI controls 95 | 96 | Note: You can mix-and-match any of these data sources when building a form. 97 | 98 | >**Working with Cursors** - Open and arrange the cursor(s) that you want to display on the form. You can reference more than one Cursor/Alias/Table/View in your layout. Be sure to locate the record pointer(s) to the correct record(s) before launching the form. 99 | 100 | >**Working with an Object** – Create or populate an object with various properties (aka oDataObject). 101 | 102 | >**Working with Variables** – Declare and define variables as Public or Private variables to bind the form control to. 103 | 104 | 105 | 106 | 107 | **Step 2**: Define your layout markup in a text string ([see markup syntax](#markup_syntax) below). 108 | 109 | 110 | 111 | **Step 3**: Create an instance of the DynamicForm class: 112 | 113 |
114 | 115 |   loForm = CreateObject('DynamicForm') 116 | 117 | Set the cBodyMarkup property with your markup syntax string from step 2: 118 | 119 |       loForm.cBodyMarkup = 120 | 121 |
122 | 123 |
124 | For cursors, set loForm.cAlias to the alias to which your layout will bind. 125 | 126 | For objects, set loForm.oDataObject to the object reference to which you layout will bind. 127 | 128 | For Public/Private, no additional settings are required. Just use the variable name in ControlSource field of the layout. 129 |
130 | 131 | 132 | 133 | **Step 4**: Set any other options/properties on loForm and/or loForm.oRenderEngine.  134 | 135 | >[Learn more about the main Form layout and properties](http://vfpx.codeplex.com/wikipage?title=Dynamic%20Form%20Main%20Form%20Layout) here. 136 | 137 | >[Learn more about the RenderEngine Class Properties](http://vfpx.codeplex.com/wikipage?title=Dynamic%20Forms%20Properties) here. 138 | 139 | 140 | 141 | **Step 5**: Call loForm.Render() to create the controls on the Form: 142 | 143 |
144 | 145 |      llResult = loForm.Render() 146 | 147 | Note: Render() is an optional step which can give you a return value to test if everything rendered okay. You can skip this step and go straight to the Show() method per step 6, if you do not wish to take this preliminary step in the process 148 | 149 | The return value indicates if everything rendered without errors. (Any errors usually caused by errors in your cBodyMarkup markup string.) 150 | 151 | If the Render() methods returns .F., this means there were rendering errors, which you can read from loForm.oRenderEngine.nErrorCount and get details on every error from the loForm.oRenderEngine.oErrors collection, or the error list in a string format by calling loForm.oRenderEngine.GetErrorsAsString(). 152 | 153 | Learn more about [rendering errors](#rendering_errors) here. 154 | 155 | Even with rendering errors, the form can still be displayed to the users (as shown in step 6) if desired. During development, showing the form (even with errors) will help you see where the errors occurred, as it will show a red error container in place of any controls which had rendering errors. 156 | 157 |
158 | 159 | 160 | 161 | **Step 6**: Call loForm.Show() to display the form to the user: 162 | 163 |
loForm.Show() 164 | 165 | By default, a Modal form is created, but this can be a Modeless form by passing a value of zero as the first parameter to Show(). 166 | 167 | At this point, the user is interacting with the form, and it will eventually be closed when they click Save, Cancel, or the [X] button. At that time, when using a Modal form, flow will return to the calling code where you can read any property on loForm and loForm.oRenderEngine, and even access the rendered controls, and continue processing per step 7\. For details about Modeless forms, see the Modeless Forms section under the Show() method documentation link below. 168 | 169 | Learn more details and options for the the [Show()](http://vfpx.codeplex.com/wikipage?title=Dynamic%20Forms%20Properties#show) method here. 170 | 171 |
172 | 173 | **Step 7**: When using the default Modal form mode, when the form is closed by the user, you can test to see which button was clicked to close the form: 174 | 175 | By default, the return values in loForm.cReturn are: 176 | 177 |
178 | 179 |    'Save' if the Save Button was clicked. 180 | 181 |    'Cancel' if the Cancel Button was clicked. 182 | 183 |    '' (empty) for [X] button. 184 | 185 |
186 | 187 | Alternatively, you can test if the Save or Cancel button was clicked by testing these properties: 188 | 189 |
190 | loForm.lSaveClicked 191 | loForm.lCancelClicked 192 |
193 | 194 | If the Cancel button is clicked, of if the form was closed with [X], then all changes to the object properties, cursors, and variables referenced are restored to their original values. 195 | 196 | Other notes: 197 |    Need to validate user input? Learn more about [restoring data values](http://vfpx.codeplex.com/wikipage?title=Restoring%20Data%20Values) here. 198 |    Learn more about the [Save and Cancel buttons](http://vfpx.codeplex.com/wikipage?title=Dynamic%20Form%20Main%20Form%20Layout) here. 199 |    Learn more about [Custom Return Values](#custom_return_values) here.  200 |    Need to use Modeless forms? See the Modeless Forms section the [Show()](http://vfpx.codeplex.com/wikipage?title=Dynamic%20Forms%20Properties#show) method here. 201 | 202 | 203 | 204 | ### Binding form controls to data 205 | 206 | Besides visual layout, the markup syntax (shown in the next section) first allows you to specify the ControlSource for each control, linking it to table fields, object properties, or Public/Private variables that you want to display and bind to in your dynamically generated form. 207 | 208 | > Typical data sources are the same as what you would use in the Form Designer or at run time in your current apps, which can include: 209 | > 210 | > * All or selected fields from one or more Cursor/Alias/Table/View 211 | > * All or selected properties on an object. (I.e. an oDataObject from a table row, or other populated object) 212 | > * Private or Public variables 213 | > * Any combination of the above 214 | 215 |
216 | 217 | **Binding to Aliases** 218 | For aliases, set loForm.cAlias to the alias to which your layout fields will bind, or include the alias name in the ControlSource for each field. 219 | 220 | If you are working with multiple aliases, you will need to include the target alias names as part of the ControlSource setting in the markup. 221 | 222 | MyAlias1.some_field 223 | 224 | MyAlias2.other_field 225 | 226 | **Binding to Objects** 227 | For objects, set loForm.oDataObject to the object reference to which your layout fields will bind. 228 | 229 | **Binding to variables** 230 | For Public/Private, no additional settings are required. Just use the variable name in ControlSource field of the layout. 231 | 232 |
233 | 234 | 235 | 236 | ### Markup Syntax 237 | 238 | The form layout is driven by a custom "markup syntax” which is defined in a text string. The markup syntax contains the list of properties/fields/variables that you want to display and bind to in the generated form. It is similar to HTML markup. Once you have defined your form layout, assign it to the cBodyMarkup property on the oRenderEngine. 239 | 240 | The syntax for the layout markup string is: 241 | 242 |
[controlsource] .attribute1 = value1 .attribute2 = value2 | ...repeat for next field ... |
243 | 244 | ![](Documentation/Images/syntax_example.png "Syntax example") 245 | 246 | [controlsource] 247 | 248 |
249 | 250 | * [controlsource] is optional, but is used when you want to bind a control to a data value. 251 | * [controlsource] can contain an explicit alias reference. i.e. MyAlias.some_field. See [Binding](#binding). 252 | * [controlsource] is not surrounded by any ' or " or [ ], it's just the field, property, or variable name. 253 | 254 |
255 | 256 | Attributes 257 | 258 | * Each attribute corresponds to a native FoxPro property on the UI control, or a custom attribute related to controlling the flow of UI controls on the rendered form. 259 | * Numeric and Boolean attribute values may be surrounded with single quotes or double quotes, if desired. 260 | * Expressions can be used in attribute values  if they are surrounded in parentheses, and these will be evaluated at run time to derive the value. 261 | * There are several  special attributes to adjust the flow or layout of controls on the rendered form. See [special attributes](#special_attributes) section for details. 262 | 263 | Executing code from markup during the render cycle: 264 | 265 | * You can also execute single lines of FoxPro code at any point during the rendering of the fields in the markup string. Simply include the code line(s) wrapped in parentheses and separated by the Pipe character delimiter, just like all other field definitions. 266 | 267 | Other notes: 268 | 269 | * Commas are optional between each name/value pair in the markup string. 270 | * Notice that '|' is the default delimiter between each field definition in the cBodyMarkup string. 271 | 272 | After you define the layout markup string, assign it to the cBodyMarkup property on the form: 273 | 274 |
loForm.cBodyMarkup = lcMyBodyMarkupString
275 | 276 | ### Additional supported feature in the Markup syntax 277 | 278 | You can also set any property of the Render Engine, Form, or render Container in the markup block. See [this page](http://vfpx.codeplex.com/wikipage?title=Dynamic%20Form%20Example%202) for more details. 279 | 280 | This allows you to entirely configure and drive all properties from the markup, without having to do it through your FoxPro code. A nice feature for anyone who wants to store fully configured form definitions in a lookup table, or those who prefer the markup technique better than the code technique. 281 | 282 | ### Examples 283 | 284 | Note: For a complete walkthrough of these markup examples, please watch **Video #1** – [Introduction and Demos](http://bit.ly/DynamicForms-Video-1). 285 | 286 | **All Fields/Properties** 287 | 288 | By default, ALL fields on the oDataObject or cAlias target will be displayed if no string is assigned to cBodyMarkup. It is the equivalent of: 289 | 290 |
lcBodyMarkup = ""    or    lcBodyMarkup = "*"
291 | 292 | **Specifying selected Properties, Fields, or variables** 293 | 294 | If you do not want to display ALL fields or properties, then you can specify only the selected ones you want to display: 295 | 296 |
lcBodyMarkup = "some_field1  | some_field_2  |some_field_3 | some_field_4 | ... |"
297 | 298 | **Adding styling attributes to the markup** 299 | 300 | In addition to specifying the controlsources, you can also include styling attributes to control all native properties on the generated control, or create other effects to control the rendered form view: 301 | 302 |
lcBodyMarkup = "some_field1 .width = 100 .caption = 'Some caption' |...|"
303 | 304 | **Specifying alias name(s) within ControlSource** 305 | 306 | A global alias name can be set on oRenderEngine.cAlias, but you can override here to specify other aliases as shown: 307 | 308 |
lcBodyMarkup = "MyOtherAlias.some_field1 .width = 100 .caption = 'Some caption' | some_field_2 .width = 300 | ... |"
309 | 310 | Note, you can specify [controlsource] in the form of an attribute if you find this format to be more clear: 311 | 312 |
.controlsource = 'field1' .attribute1 = value1 .attribute2 = value2 | ...repeat for next field or control...
313 | 314 | or, if you need to specify the Alias name on the controlsource: 315 | 316 |
.controlsource = 'MyAlias.field1' .attribute1 = value1 .attribute2 = value2 | ...repeat for next field or control...
317 | 318 | Once you have defined your markup layout, pass it to the RenderEngine like this: 319 | 320 |
loForm.cBodyMarkup = "...layout markup text..."
321 | 322 | **Attributes** 323 | 324 | Each markup "attribute" maps to a native FoxPro property by the same name and they are applied just as you would do in FoxPro code or in the Properties window. Every native FoxPro property is supported, as well as custom properties if you are using a custom class. Please report any issues if you find one that is not support or interpreted properly. 325 | 326 | If you reference a custom class in the :class attribute, you can also specify its .classlibrary attribute (only required if the classlibrary or procedure is not already in place via Set ClassLib or Set Procedure). 327 | 328 | 329 | 330 | **Special Attributes** 331 | 332 | Additionally, the following special attributes are supported to control layout and flow of the controls: 333 | 334 | 335 | 336 |
337 |
.column 
338 | .margin-bottom
339 | .margin-top 
340 | .margin-left 
341 | .row 
342 | .row-increment 
343 | .render-if
344 | .set-focus
345 | 346 |
347 | 348 | 349 |
(Optional) Most forms can be rendered in a single “column”, but you can move the flow to the top of the next “column” by specify a column number to generate the control into. The default column is 1, but the RenderEngine will increment to the next column once the render container reaches the max height specified in the oRenderEngine.nColumnHeight property. 350 | You can also advance the column location yourself by including this attribute on any one control, and then flow of remaining controls continues in the specified column, and will wrap to the next column if oRenderEngine.nMaxHeight is reached.
351 | 352 | ![](Documentation/Images/SNAGHTML5e028c84_thumb.png "Column explanation") 353 | 354 |
.margin-top = n
355 | 356 |
Adds additional spacing between the previous row and the current row, so that the current is pushed down from the previous. 357 | 'n' is the number of pixels
358 | 359 |
.margin-bottom = n 
360 | 361 |
Adds additional spacing below the current row (after it's rendered), so that the NEXT row will be pushed further down. 362 | 'n' is the number of pixels
363 | 364 |
.margin-left = n
365 | 366 |
367 | 368 | Adds additional to the left of the control so as to create more space between the previous control or column left edge. 369 | 'n' is the number of pixels 370 | 371 |
372 | 373 | ![](Documentation/Images/SNAGHTML45155f2_thumb.png "Margin explanation") 374 | 375 |
.row-increment = n
376 | 377 |
378 | 379 | Instructs the engine to skip down 'n' number of rows (default is 1). 380 | The row height is controlled by the nVerticalSpacing property on the RenderEngine. 381 | 'n' is the number of rows to skip down in the render flow. 382 | Tip: To render the current control on the same row as the previous control, 383 | use .row-increment = 0 as shown here: 384 | 385 |
386 | 387 |
[![image](http://download-codeplex.sec.s-msft.com/Download?ProjectName=vfpx&DownloadId=485463 "image")](http://download-codeplex.sec.s-msft.com/Download?ProjectName=vfpx&DownloadId=485462)
388 | 389 | ### Conditional Rendering 390 | 391 |
.render-if = 
392 | 393 |
394 | 395 | You can include .render-if = (SomeExpressionOrFunctionCall()) to conditionally render the control. 396 | 397 |
398 | 399 | ### Setting Focus 400 | 401 |
.set-focus = .t. 
402 | 403 |
404 | 405 | This attribute, when used on a field markup, indicates this control is to have focus when form is shown. 406 | 407 |
408 | 409 | ### Absolute positioning 410 | 411 | You can achieve absolute positioning of any control using the native FoxPro Top and Left properties as attributes: 412 | 413 |
.top = n 
414 | .left = n 
415 | 416 |
Once a control has been rendered using an absolute position, the next control in cBodyMarkup will resume rendering flow from this last render location.
417 | 418 | 419 | 420 | ### Function calls or in attribute values 421 | 422 | You can use function calls for any attribute value, as long as the function returns to correct data type for the targeted property. Wrap the expression in parentheses as shown here: 423 | 424 | Examples: 425 | 426 |
.caption = (GetCaptionForBlah())
427 | 428 |
.enabled = (UserIsAdmin())
429 | 430 |
.tooltiptext = (pcSomePrivateStringVar)
431 | 432 | 433 | 434 | ### Field Labels 435 | 436 | Field labels will be automatically added for each UI controls that is bound to an property, field, or variable. 437 | 438 | 1. The default Caption for each label is the name of the property/field/variables specified in the control source. 439 | 2. Underscore characters in the property/field/variable names will be converted to a space for cleaner caption text. 440 | 3. You can override the label caption by using  .label.caption = 'Blah' attribute. 441 | 4. You can prevent a label from being displayed by setting .label.visible = .f. 442 | 5. You can set ANY FoxPro property on the label by using 'label.' a prefix on the attribute: 443 |     i.e: 444 | 445 |
446 | 447 |
.label.Caption = 'My Caption'
448 | .label.FontBold = .t.
449 | .label.Alignment = 1 (Right aligned with input control)
450 | 451 |
452 | 453 | 6. By default, field labels are rendered to the left of the input control (inline with). 454 |
455 | Set loForm.oRenderEngine.lLabelsAbove = .t. to render the labels ABOVE the input controls: 456 | 457 | ![](Documentation/Images/SNAGHTML488f0b72_thumb.png "Label positions") 458 | 459 |
460 | 461 | 462 | ### Return values from the form 463 | 464 | You can get custom return values from the Dynamic Form using instances of the default “commandbutton” class, which resolves to the custom DF_ResultButton class by default. **Using these command buttons, when the button is clicked, the form will hide and the caption of the command button will be passed to the loForm.cReturn property** (any hotkey characters in the caption will be removed). This allows you to easily test loForm.cReturn to see which of command button was clicked, and you can take the appropriate action as control passes back to the calling program. 465 | 466 | Actually, the caption is passed to the Tag property on the button, and the button click event passes its Tag value back to loForm.cResult and then hides the form. So, if needed, you can set the Tag value directly if you do not want to use the caption as the return value. 467 | 468 | 469 | 470 | ### Custom UI control classes 471 | 472 | 473 | 474 | By default, native FoxPro baseclasses (textbox, label, checkbox, etc.) are used on the form. If you wish to specify your own custom classes to use instead, the following properties allow you to change the defaults for all instances on the form: 475 | 476 |
cLabelClass = 'DF_Label'
477 | cLabelClassLib = ''
478 | cTextboxClass = 'textbox'
479 | cTextboxClassLib = ''
480 | cEditboxClass = 'editbox'
481 | cEditboxCLassLib = ''
482 | cCommandButtonClass = 'DF_ResultButton'
483 | cCommandButtonClassLib = ''
484 | cOptionGroupClass = 'optiongroup'
485 | cOptionGroupClassLib = ''
486 | cCheckboxClass = 'DF_Checkbox'
487 | cCheckboxClassLib = ''
488 | cComboboxClass = 'combobox'
489 | cComboboxClassLib = ''
490 | cListboxClass = 'listbox'
491 | cListboxClassLib = ''
492 | cSpinnerClass = 'spinner'
493 | cSpinnerClassLib = ''
494 | cGridClass = 'grid'
495 | cGridClassLib = ''
496 | cImageClass = 'image'
497 | cImageClassLib = ''
498 | cTimerClass = 'timer'
499 | cTimerClassLib = ''
500 | cPageframeGroupClass = 'pageframe'
501 | cPageframeClassLib = ''
502 | cLineClass = 'line'
503 | cLineClassLib = ''
504 | cShapeClass = 'shape'
505 | cShapeClassLib = ''
506 | cContainerClass = 'container'
507 | cContainerClassLib = ''
508 | cClassLib = '' && General classlib where controls can be found, if not specified above.
509 | 510 | You can also specify :class and :classlibrary inline on each control in the field . 511 | 512 |
SomeValue .class = 'MyTextBoxClass' .classlibrary = 'MyClassLib.vcx'
513 | 514 | 515 | 516 | ### Rendering Errors 517 | 518 | A return value from calling loForm.Render() indicates if everything rendered without errors. Any rendering errors are usually caused by errors in your cBodyMarkup markup string. The error messages and rendered form should help you identify which control(s) had the errors. 519 | 520 | If the Render() methods returns .F., this means there were rendering errors, which you can read from the nErrorCount property: 521 | 522 |
loForm.oRenderEngine.nErrorCount
523 | 524 | and you can get details on each error from the oErrors collection: 525 | 526 |
loForm.oRenderEngine.oErrors
527 | 528 | You can also call the oRenderEngine.GetErrorsAsString() method to get the errors in a string format which can be easier to read than the working with the oRenderEngine.oErrors collection. You can see a sample of the error test and this method call in the source PRG file (in the sample procedural code at the top of the file). 529 | 530 | ![](Documentation/Images/SNAGHTML4dd81694_thumb.png "Rendering errors") 531 | 532 | Even with rendering errors, the form can still be displayed, but the error controls on the form are really intended to be a Developer tool to help you find bugs in your markup. It will show a red error container in place of any controls which had rendering errors. 533 | 534 | ![](Documentation/Images/image_thumb_7.png "Form sample with error field") 535 | 536 | ### Release history 537 | 538 | 539 | 2017-08-30 = Ver 1.9.1 : Fixed spelling, capitalization, formatting (no functional changes). 540 | 541 | 2017-08-27 = Ver 1.9.0 Production release, Migrated from VFPx/CodePlex to GitHub 542 | 543 | 2014-09-29 = Ver 1.8.2 Beta 544 | 545 | 2013-10-31 = Ver 1.7.0 Alpha 546 | 547 | 2012-10-18 = Alpha 1.5.0 released. 548 | 549 | 2012-10-26 – Alpha 1.4.1 released. 550 | 551 | 2012-10-23 – Alpha 1.4.0 released. 552 | 553 | 2012-10-08 – Alpha 1.3.0 released. 554 | 555 | 2012-09-25 – Alpha 1.2.0 released. 556 | 557 | 2012-09-18 – Alpha 1.0.0 released. 558 | 559 | 2012-09-04 – Public alpha release 0.9.0 released on VFPx. 560 | 561 | 2012-08-31 – Dynamic Forms accepted as an official VFPx Project. 562 | 563 | 2012-08-24 – Project proposal submitted to VFPx admins. 564 | 565 | 2012-05-08 – Initial concept class created and the first ever Dynamic Form was generated from a few lines of code. 566 | 567 | 568 | -------------------------------------------------------------------------------- /DynamicForm.prg: -------------------------------------------------------------------------------- 1 | *======================================================================================================= 2 | * Dynamic Form - 1.9.1 3 | *--------------------------------------------------------------------------------------- 4 | * By: Matt Slay 5 | *------------------------------------------------------------------------------------------------------- 6 | *-- 7 | *-- Web Site: https://github.com/mattslay/DynamicForms 8 | *-- 9 | *-- 10 | *-- You can automatically download this Component and its updates through "Thor - Check for Updates". 11 | *-- To learn more about Thor and its Tools and Updaters for FoxPro, see: 12 | *-- https://github.com/VFPX/Thor/blob/master/Docs/Thor_one-click_update.md 13 | *-- 14 | *-- User Discussions Group: https://groups.google.com/forum/#!forum/foxprodynamicforms 15 | *-- 16 | *-- after JOINING the Group above, you can post to the web forum or 17 | *-- email to: foxprodynamicforms@googlegroups.com 18 | *-- 19 | *--======================================================================================= 20 | *-- 21 | *-- This source code PRG contains the class definitions needed to create Dynamic Forms in your apps, 22 | *-- and is also a ready-to-run sample to show you a rendered form sample. 23 | *-- 24 | *-- Just run this PRG to see a Dynamic Form sample from the code below. The sample creates a Modal form 25 | *-- which is bound to a few Private variables and an simple data object (the data object is created in the code 26 | *-- at run time simply to mock a real data object and to prevent the demo from having to ship with a sample dbf.) 27 | *-- You can also easily bind to local cursors via the cAlias property, rather than data objects. Please 28 | *-- see the documentation link below for more details on using Dynamic Forms with cursors, and advanced 29 | *-- uses like creating Modeless forms or working with Business Objects to save the data when binding to 30 | *-- an oDataObject. 31 | *-- 32 | *--======================================================================================= 33 | *-- DOCUMENTAION - Visit this link to see FULL DOCUMENTAION pages: 34 | *-- 35 | *-- https://github.com/mattslay/DynamicForms 36 | *-- 37 | *----------------------------------------------------------------------------------------- 38 | *-- VIDEOS - 39 | *-- 40 | *-- Video #1 ? Introduction and Demos (8:15) View here: http://bit.ly/DynamicForms-Video-1 41 | *-- 42 | *-- Video #2 ? Exploring the class PRG and code sample (9:09) View here: http://bit.ly/Dynamic-Forms-Video-2 43 | *-- 44 | *----------------------------------------------------------------------------------------- 45 | *-- 46 | *-- Version History 47 | *-- 48 | *-- 1.9.1 2017-08-30 Fixed spelling, capitalization, formatting (no functional changes). 49 | *-- 1.9.0 Production release - August 27, 2017 - 20170827 (Migrated from VFPx/CodePlex to GitHub) 50 | *-- 1.8.2 Beta - September 29, 2014 - 2014-09-29 51 | *-- 1.7.0 Alpha - October 31, 2013 - 2013-10-31 52 | *-- 2012-10-18 Alpha 1.5.0 released. 53 | *-- 2012-10-26 Alpha 1.4.1 released. 54 | *-- 2012-10-23 Alpha 1.4.0 released. 55 | *-- 2012-10-08 Alpha 1.3.0 released. 56 | *-- 2012-09-25 Alpha 1.2.0 released. 57 | *-- 2012-09-18 Alpha 1.0.0 released. 58 | *-- 2012-09-04 Public alpha release 0.9.0 released on VFPx. 59 | *-- 2012-08-31 Dynamic Forms accepted as an official VFPx Project. 60 | *-- 2012-08-24 Project proposal submitted to VFPx admins. 61 | *-- 2012-05-08 Initial concept class created and the first ever Dynamic Form was generated from a few lines of code. 62 | *-- 63 | *--------------------------------------------------------------------------------------- 64 | 65 | *-- Example usage: 66 | 67 | Private lnPrice, laOptions[1], lnOption 68 | Private loForm as 'DynamicForm' 69 | Local loObject, lcBodyMarkup 70 | 71 | *---- Step 1: Prepare the data... 72 | *-- As noted in the Documentation you can bind the form to table aliases and cursors, private or public variables, or Data Objects. 73 | *-- See https://github.com/mattslay/DynamicForms#binding 74 | *-- 75 | *-- In this example, we'll build a Data Object in code so that we do not have to distribute a sample dbf with this project package. 76 | *-- Often, "Data Objects" come from table rows, via the Scatter command, or other object-building techniques. You then pass that 77 | *-- object into this Dynamic Form per this code sample. Watch the video series to see examples of working with cursors. 78 | *-- 79 | loObject = CreateObject('Empty') 80 | AddProperty(loObject , 'id', '12345') 81 | AddProperty(loObject , 'first_name', 'Joe') 82 | AddProperty(loObject , 'mid_init', 'N.') 83 | AddProperty(loObject , 'last_name', 'Coderman') 84 | AddProperty(loObject , 'ad_type', 'Banner') 85 | AddProperty(loObject , 'notes', 'This man came here and wrote some codez.') 86 | AddProperty(loObject , 'still_here', .f.) 87 | AddProperty(loObject , 'has_laptop', .t.) 88 | AddProperty(loObject , 'bool_1', .t.) 89 | AddProperty(loObject , 'bool_2', .t.) 90 | AddProperty(loObject , 'bool_3', .t.) 91 | AddProperty(loObject , 'weight', 185) 92 | 93 | *-- Step 2: Define the UI layout string (similar to HTML / XAML markup) -------------------------------------------- 94 | *-- See https://github.com/mattslay/DynamicForms#markup_syntax 95 | 96 | Text to lcBodyMarkup NoShow 97 | 98 | .lLabelsAbove = .t. | 99 | 100 | id .enabled = .f. 101 | .fontbold = .t. 102 | .label.FontBold = .t. | 103 | 104 | ad_type .class = 'combobox' 105 | .RowSource = 'laOptions' 106 | .RowSourceType = 5 107 | .row-increment = 0 | 108 | 109 | bool_3 .caption = 'You can specify BOLD captions.' 110 | .FontBold = .t. 111 | .width = 400 | 112 | 113 | 114 | first_name .set-focus = .t. | 115 | mid_init .row-increment = 0 | 116 | last_name .row-increment = 0 | 117 | 118 | notes .class = 'editbox' 119 | .width = 400 120 | .height = 80 121 | .anchor = 10| 122 | 123 | lnPrice .label.caption = 'List Price' 124 | .label.alignment = 1 | 125 | 126 | weight .row-increment = 0 127 | .label.alignment = 1 | 128 | 129 | lnOption .class = 'optiongroup' 130 | .caption = 'Color options.' 131 | .buttoncount = 2 132 | .width = 200 133 | .height = 60 134 | .option1.caption = 'Red with orange stripes' 135 | .option1.autosize = .t. 136 | .option2.caption = 'Purple with black dots' 137 | .option2.autosize = .t. | 138 | 139 | .class = 'label' .caption = 'Thank you for trying DynamicForm.' 140 | .autosize = .t. 141 | .render-if = (Day(Date()) > 1) 142 | 143 | EndText 144 | 145 | *-- Example of a Private variables that can be bound to also 146 | lnPrice = 107.15 147 | lnOption = 2 148 | 149 | *-- Array of options/values to display in the ComboBox defined in cMarkup above (note that it's declared Private above --- 150 | Dimension laOptions[3] 151 | laOptions[1] = 'Banner' 152 | laOptions[2] = 'Placard' 153 | laOptions[3] = 'Name Tag' 154 | 155 | *-- Step 3. Create an instance of DynamicForm class 156 | *-- See https://github.com/mattslay/DynamicForms#step3 157 | loForm = CreateObject('DynamicForm') 158 | 159 | *-- Step 4. Set a few properties on loForm to wire everything up and set rendering options... 160 | *-- See https://github.com/mattslay/DynamicForms#step4 161 | loForm.oDataObject = loObject && Set the data object that the form fields bind to 162 | 163 | loForm.Caption = 'Dynamic Forms' 164 | loForm.oRenderEngine.lLabelsAbove = .t. && Generate field labels above each control. Default is .F., meaning "inline with controls", to the left. 165 | 166 | *-- Setup Header area (or disable it)------------- 167 | loForm.cHeading = 'Sample Form' 168 | loForm.nHeadingFontSize = 14 && You can set heading label font size as desired 169 | 170 | *loForm.cHeaderMarkup = '' && Set to empty to disable automatic Header markup. Or, 171 | && assign custom markup string to customize the header area. See http://vfpx.codeplex.com/wikipage?title=Dynamic%20Form%20Main%20Form%20Layout 172 | 173 | *-- Set the main body area markup --------- 174 | loForm.cBodyMarkup = lcBodyMarkup 175 | 176 | * loForm.cFooterMarkup = '' && Set to empty to disable use of automatic Footer markup. Or, 177 | && assign custom markup string to customize the footer area. See http://vfpx.codeplex.com/wikipage?title=Dynamic%20Form%20Main%20Form%20Layout 178 | 179 | *-- Step 5. Call Render method to create the controls in the Form 180 | *-- See https://github.com/mattslay/DynamicForms#step5 181 | llResult = loForm.Render() 182 | 183 | loForm.MinHeight = loForm.cntMain.Height 184 | loForm.MinWidth = loForm.cntMain.Width 185 | 186 | *-- Step 6. Show the form to the user 187 | *-- See https://github.com/mattslay/DynamicForms#step6 188 | If llResult = .t. 189 | *-- Note. You have a chance here to programmatically change anything on the form or controls 190 | *-- in any way needed before showing the form to the user... 191 | loForm.Show() 192 | *loForm.Show(1, '300,10') && 0 = Modeless, 1 = Modal. See http://vfpx.codeplex.com/wikipage?title=Dynamic%20Forms%20Properties#show 193 | && You can also pass a 'left,top' pair to position the form at a fixed point. 194 | Else 195 | MessageBox(loForm.oRenderEngine.GetErrorsAsString() , 0, 'Notice.') 196 | *-- If there were any rendering errors (llResult = .f.), then you can read loForm.oRenderEngine.nErrorCount property 197 | *-- and loForm.oRenderEngine.oErrors collection for a detail of each error. Or call loForm.oRenderEngine.GetErrorsAsString(). 198 | loForm.Show(1) 199 | Endif 200 | 201 | *-- At this point, the user is interacting with the form, and it will eventually be closed when they click 202 | *-- Save, Cancel, or the [X] button. At that time, flow will return here, and we can then read any property 203 | *-- on loForm and loForm.oRenderEngine, and even access the rendered controls. 204 | 205 | *-- Step 7. Proceed with program flow based on whether user clicked Save or Cancel/closed the form. 206 | *-- See https://github.com/mattslay/DynamicForms#step7 207 | If Vartype(loForm) = 'O' and Lower(loForm.cReturn) = 'save' 208 | *-- If Save is clicked, the controlsources are already updated with the new values from the UI. 209 | *-- Do whatever local processing you need following the Save click by the user... 210 | Release loForm 211 | Else 212 | *-- Do whatever processing for Close/Cancel user action... 213 | *-- If using the Button Bar or and instance of DF_CancelButton on the form and Cancel was clicked, 214 | *-- and the property loForm.lRestoreDataOnCancel = .t. (default), then the controlsources will already 215 | *-- be restored to their original value by the Form class. 216 | EndIf 217 | 218 | *-- After the preceding Save/Cancel processing, we can now Release the loForm object. 219 | 220 | 221 | 222 | 223 | *-- Class definitions follow: 224 | 225 | 226 | *======================================================================================= 227 | #DEFINE CR Chr(13) 228 | #DEFINE CRCR Chr(13) 229 | 230 | Define Class DynamicForm as Form 231 | 232 | cVersion = '1.9.1' 233 | cVersionFull = '1.9.1 Production Release - August 30, 2017' 234 | Caption = '' 235 | *-- Binding form fields to a cursor/alias... 236 | cAlias = '' && The cursor/alias that your form fields bind to. Make sure this alias is opened and positioned to the correct record. 237 | 238 | *-- Binding form fields to a DataObject for data, and optionally setting an oBusinessObject for Saving the data. 239 | oDataObject = .null. && The object which has the data properties that you form fields bind to. 240 | oBusinessObject = .null. && oBusinessObject often has a Save() method to save the values from oDataObject back to its table. 241 | cBusinessObjectSaveMethod = 'Save()' && This method on the oBusinessObject will be called when the Save button is clicked. 242 | cDataObjectRef = 'Thisform.oDataObject' 243 | 244 | lRestoreDataOnCancel = .t. && When .T., changes to oDataObject or cAlias will be restored to their original values if form is Cancelled by user. 245 | lClearEventsOnClose = .f. && Only use if you want to call Clear Events when this form is closed. 246 | oRenderEngine = .null. && Will be populated in Thisform.Init() event. You can override with your own instance of a Render Engine after this form in Initlialized. 247 | 248 | cHeading = .null. && This text is displayed in the default Header area of the form. 249 | cSaveButtonCaption = .null. 250 | cCancelButtonCaption = .null. 251 | nHeadingFontSize = 14 && The font size for the label in the Header area. 252 | 253 | cHeaderMarkup = .null. 254 | cBodyMarkup = .null. 255 | cFooterMarkup = .null. 256 | cPopupFormBodyMarkup = .null. 257 | 258 | MinWidth = 400 259 | MinHeight = 250 260 | 261 | *-- Consider these as ReadOnly after the form has been Hidden by one of the form Buttons -------- 262 | lSaveClicked = .f. 263 | lCancelClicked = .f. 264 | cReturn = '' 265 | cHandle = '' && A unique string used to store a a reference to this form on _screen. This is used in handling Modeless forms. 266 | 267 | Width = 10000 && This high values will be set to actual rendered size in the Show() method 268 | Height = 10000 && This high values will be set to actual rendered size in the Show() method 269 | DataSession = 1 270 | 271 | Add Object cntMain as Container With ; 272 | Top = 0, ; 273 | Left = 0, ; 274 | Width = 1, ; && Will be resized by RenderEngine to size required to hold all controls 275 | Height = 1, ; 276 | BorderWidth = 0, ; 277 | Anchor = 15, ; 278 | margin_left = 0, ; 279 | margin_right = 0, ; 280 | margin_top = 0, ; 281 | margin_bottom = 0 282 | 283 | *--------------------------------------------------------------------------------------- 284 | Procedure cSaveButtonCaption_Assign()tcCaption 285 | 286 | This.oRenderEngine.cSaveButtonCaption = tcCaption 287 | 288 | Endproc 289 | 290 | *--------------------------------------------------------------------------------------- 291 | Procedure cCancelButtonCaption_Assign(tcCaption) 292 | 293 | This.oRenderEngine.cCancelButtonCaption = tcCaption 294 | 295 | Endproc 296 | 297 | *--------------------------------------------------------------------------------------- 298 | Procedure cHeading_assign(tcCaption) 299 | 300 | This.oRenderEngine.cHeading = tcCaption 301 | 302 | Endproc 303 | 304 | *--------------------------------------------------------------------------------------- 305 | Procedure nHeadingFontSize_assign(tnFontSize) 306 | 307 | This.oRenderEngine.nHeadingFontSize = tnFontSize 308 | 309 | Endproc 310 | 311 | *--------------------------------------------------------------------------------------- 312 | Procedure cHeaderMarkup_assign(tcMarkup) 313 | 314 | This.oRenderEngine.cHeaderMarkup = tcMarkup 315 | 316 | EndProc 317 | 318 | *--------------------------------------------------------------------------------------- 319 | Procedure cBodyMarkup_assign(tcMarkup) 320 | 321 | This.cBodyMarkup = tcMarkup 322 | This.oRenderEngine.cBodyMarkup = tcMarkup 323 | 324 | Endproc 325 | 326 | *--------------------------------------------------------------------------------------- 327 | Procedure cFooterMarkup_assign(tcMarkup) 328 | 329 | This.oRenderEngine.cFooterMarkup = tcMarkup 330 | 331 | EndProc 332 | 333 | *--------------------------------------------------------------------------------------- 334 | Procedure Init() 335 | 336 | This.cHandle = 'DF_' + Sys(2015) && Used to keep a ref to modeless forms alive 337 | This.oRenderEngine = CreateObject('DynamicFormRenderEngine') 338 | 339 | Endproc 340 | 341 | *--------------------------------------------------------------------------------------- 342 | Procedure Save 343 | && Add any code to be called when the Save() button is clicked. 344 | && You can also set a BindEvent() call to this method to react to the Save button click. 345 | EndProc 346 | 347 | *--------------------------------------------------------------------------------------- 348 | Procedure Activate 349 | 350 | This.Refresh() 351 | 352 | Endproc 353 | 354 | *--------------------------------------------------------------------------------------- 355 | Procedure Destroy 356 | 357 | If Vartype(This.oRenderEngine) = 'O' 358 | This.oRenderEngine.Destroy() && This will force objects on RE to get released 359 | EndIf 360 | 361 | This.oRenderEngine = .null. 362 | This.oBusinessObject = .null. 363 | This.oDataObject = .null. 364 | 365 | Store .null. to (This.cHandle) 366 | 367 | RemoveProperty(_screen, This.cHandle) 368 | 369 | If This.lClearEventsOnClose 370 | Clear Events 371 | Endif 372 | 373 | EndProc 374 | 375 | *--------------------------------------------------------------------------------------- 376 | Procedure QueryUnload 377 | 378 | *-- This Event is triggered when the Form's close button [X] is clicked. 379 | If This.lRestoreDataOnCancel = .t. 380 | This.RestoreData() 381 | Endif 382 | 383 | EndProc 384 | 385 | *--------------------------------------------------------------------------------------- 386 | Procedure Show(tnStyle, toHostForm ) 387 | 388 | *-- Params: 389 | *-- tnStyle. 1 = Modal (Default), 0 = Modeless 390 | *-- toHostForm. (Optional) The form this was called from. If passed, we will center this form in the center of host form. 391 | 392 | Local lnAnchor, lnStyle, lnX, loControl 393 | 394 | *-- lnAnchor = This.cntMain.Anchor 395 | *-- This.cntMain.Anchor = 0 396 | *-- This.cntMain.Width = Max(This.MinWidth, This.Width) 397 | *-- This.cntMain.Height = Max(This.MinHeight, This.Height) 398 | *-- This.cntMain.Anchor = lnAnchor 399 | 400 | 401 | If This.oRenderEngine.lRendered = .f. 402 | This.Render() 403 | EndIf 404 | 405 | If Pcount() < 2 and (Version(2) <> 0) && No host passed, and working in dev mode 406 | This.Left = Max(Int(Min(_vfp.Width,1600) - This.Width) / 2, 0) 407 | This.Top = Max(Int((_vfp.Height - This.Height) / 2) - Sysmetric(9) - 0, 0) 408 | Endif 409 | 410 | *-- If a reference to the calling form was passed, then center this form in host form 411 | If Vartype(toHostForm) = 'O' 412 | Do Case 413 | Case PemStatus(toHostForm, 'ShowWindow', 5) and toHostForm.ShowWindow = 1 414 | This.Left = Max(Int(toHostForm.Width - This.Width) / 2 + toHostForm.Left, 0) 415 | This.Top = Max(Int(toHostForm.Height - This.Height - Sysmetric(9)) / 2 + toHostForm.Top, 0) 416 | Case PemStatus(toHostForm, 'ShowWindow', 5) and toHostForm.ShowWindow = 2 417 | This.Left = Max(Int(toHostForm.Width - This.Width) / 2, 0) 418 | This.Top = Max(Int(toHostForm.Height - This.Height - Sysmetric(9)) / 2, 0) 419 | Otherwise 420 | This.Left = Max(Int(toHostForm.Width - This.Width) / 2 + toHostForm.Left, 0) 421 | This.Top = Max(Int(toHostForm.Height - This.Height) / 2 + toHostForm.Top, 0) 422 | Endcase 423 | Endif 424 | 425 | If Vartype(toHostForm) = 'C' 426 | This.Top = Val(GetWordNum(toHostForm, 2, ',')) 427 | This.Left = Val(GetWordNum(toHostForm, 1, ',')) 428 | Endif 429 | 430 | If Vartype(tnStyle) # 'N' or tnStyle < 0 or tnStyle > 1 431 | lnStyle = 1 432 | Else 433 | lnStyle = tnStyle 434 | Endif 435 | 436 | If lnStyle = 0 && Modeless 437 | AddProperty(_screen, This.cHandle, This) 438 | Endif 439 | 440 | This.WindowType = lnStyle 441 | DoDefault(lnStyle) 442 | 443 | *-- Set Focus handling... 444 | If Type('This.cntMain.DF_oSetFocus') = 'O' 445 | This.cntMain.DF_oSetFocus.SetFocus() 446 | Else && Set focus to first enabled control 447 | For lnX = 1 to This.cntMain.ControlCount 448 | loControl = This.cntMain.Controls(lnX) 449 | If PemStatus(loControl, 'Enabled', 5) and PemStatus(loControl, 'SetFocus', 5) and loControl.Enabled = .t. 450 | loControl.Setfocus() 451 | Exit 452 | Endif 453 | EndFor 454 | Endif 455 | 456 | *-- For some reason the Save button will not appear unless the form is resized. Crazy. So, I jiggle the size around and it appears!!! 457 | Thisform.Width = Thisform.Width + 1 458 | Thisform.Width = Thisform.Width - 1 459 | 460 | EndProc 461 | 462 | *--------------------------------------------------------------------------------------- 463 | Procedure Hide 464 | 465 | DoDefault() 466 | 467 | If This.lClearEventsOnClose 468 | Clear Events 469 | Endif 470 | 471 | Endproc 472 | 473 | 474 | *--------------------------------------------------------------------------------------- 475 | Procedure Render(tcBodyMarkup) 476 | 477 | Local lcRenderSizeMessage, llReturn, lnAnchor, lnRenderHeight, lnRenderWidth 478 | 479 | If Vartype(tcBodyMarkup) = 'C' 480 | This.cBodyMarkup = tcBodyMarkup 481 | EndIf 482 | 483 | If This.oRenderEngine.lRendered = .f. 484 | This.SetupRenderEngine() 485 | llReturn = This.oRenderEngine.Render() 486 | EndIf 487 | 488 | lnAnchor = This.cntMain.Anchor 489 | This.cntMain.Anchor = 0 490 | 491 | *-- Move continer for any margin-top or margin-bottom that was set 492 | This.cntMain.Left = This.cntMain.Left + This.cntMain.margin_left 493 | This.cntMain.Top = This.cntMain.Top + This.cntMain.margin_top 494 | 495 | *-- If form it still at its default size, then resize to fit the size of cntMain, which now has all its controls in it. 496 | If This.Width = 10000 497 | With This.cntMain 498 | This.Width = Max(.Width + .Left + .margin_right, This.MinWidth) 499 | Endwith 500 | EndIf 501 | 502 | If This.Height = 10000 503 | With This.cntMain 504 | This.Height = Max(.Top + .Height + .margin_bottom , This.MinHeight) 505 | Endwith 506 | EndIf 507 | 508 | *-- Make sure container Width and Height fills up the entire width of the form. 509 | If !PemStatus(This.cntMain, 'container_width', 5) 510 | This.cntMain.Width = Thisform.Width - This.cntMain.Left - This.cntMain.margin_right 511 | EndIf 512 | If !PemStatus(This.cntMain, 'container_height', 5) 513 | This.cntMain.Height = Thisform.Height - This.cntMain.Top - This.cntMain.margin_bottom 514 | Endif 515 | 516 | lcRenderSizeMessage = '' 517 | lnRenderWidth = This.cntMain.Left + This.cntMain.Width + This.cntMain.margin_right 518 | If lnRenderWidth > This.Width 519 | lcRenderSizeMessage = 'Warning: Rendered control area is wider than form width.' + CRCR + ; 520 | '[' + Transform(lnRenderWidth) + ' vs.' + Transform(This.Width) + ']' 521 | 522 | lnAnchor = lnAnchor - 8 523 | Endif 524 | 525 | lnRenderHeight = This.cntMain.Top + This.cntMain.Height + This.cntMain.margin_bottom 526 | If lnRenderHeight > This.Height 527 | lcRenderSizeMessage = Iif(!Empty(lcRenderSizeMessage), CRCR + lcRenderSizeMessage, '') + ; 528 | 'Rendered control area it taller than form height. '+ CRCR +; 529 | '[' + Transform(lnRenderHeight) + ' vs.' + Transform(This.height) + ']' 530 | lnAnchor = lnAnchor - 4 531 | Endif 532 | 533 | If !Empty(lcRenderSizeMessage) 534 | MessageBox(lcRenderSizeMessage, 64, 'Render size warning:') 535 | This.oRenderEngine.AddError(lcRenderSizeMessage, .null.) 536 | EndIf 537 | 538 | This.cntMain.Anchor = Iif(lnAnchor > 0, lnAnchor, 0) 539 | 540 | Return llReturn 541 | 542 | EndProc 543 | 544 | *--------------------------------------------------------------------------------------- 545 | Procedure RestoreData 546 | 547 | If Vartype(This.oRenderEngine) = 'O' 548 | This.oRenderEngine.RestoreData() 549 | Endif 550 | 551 | EndProc 552 | 553 | *--------------------------------------------------------------------------------------- 554 | Procedure SetupRenderEngine 555 | 556 | With This.oRenderEngine 557 | .cAlias = This.cAlias 558 | .oBusinessObject = This.oBusinessObject 559 | .oDataObject = This.oDataObject 560 | .cDataObjectRef = This.cDataObjectRef 561 | .cBusinessObjectSaveMethod = This.cBusinessObjectSaveMethod 562 | .oContainer = This.cntMain 563 | .lResizeContainer = .t. 564 | EndWith 565 | 566 | EndProc 567 | 568 | *--------------------------------------------------------------------------------------- 569 | *-- This method allows you to pass in toBusinessObject and toDataObject all at once. 570 | Procedure BindBusinessAndDataObjects(toBusinessObject, toDataObject) 571 | 572 | 573 | This.oBusinessObject = Evl(toBusinessObject, .null.) 574 | This.oDataObject = Evl(toDataObject, .null.) 575 | 576 | Endproc 577 | 578 | 579 | 580 | EndDefine 581 | 582 | *======================================================================================= 583 | Define Class DynamicFormRenderEngine as Custom 584 | 585 | *-- See website for complete documentation. 586 | *-- https://github.com/mattslay/DynamicForms 587 | 588 | cVersion = '1.9.1' 589 | cVersionFull = '1.9.1 Production Release - August 30, 2017' 590 | 591 | cAlias = '' && The name of a cursor or alias to which the cMarkup controls are bound 592 | 593 | *-- These properties deal with the data object and properties/fields on it 594 | oBusinessObject = .null. 595 | oDataObject = .null. && The Object to which the cMarkup controls are bound. 596 | cDataObjectRef = '' && The reference to the oDataObject to be used in the ControlSource property of each control that gets generated.. 597 | && I.e. 'Thisform.oBusObj.oData' 598 | cBusinessObjectSaveMethod = '' 599 | 600 | cSkipFields = '' 601 | *cDisabledFields = '' 2012-09-26 Support for this feature has been removed. 602 | 603 | cAttributeNameDelimiterPattern = ':' 604 | cAttributeValueDelimiterPattern = '=>' 605 | cFieldDelimiterPattern = '|' && Caution. Don't use a comma as delimiter. It will likely break things! 606 | 607 | oContainer = .null. && The container in which controls will be rendered. Set by the calling form. 608 | 609 | *-- These properties are only used when a BusinessObject is configured to handle the Save button click. 610 | lShowSaveErrors = .t. && Determines if an error dialog will appear if the call to the Business Object Save() method returns .f. 611 | cSaveErrorMsg = 'Could not save data in Business Object.' 612 | cSaveErrorCaption = 'Warning...' 613 | 614 | cHeading = '' 615 | cSaveButtonCaption = .null. 616 | cCancelButtonCaption = .null. 617 | nHeadingFontSize = 14 618 | cHeaderMarkup = .null. && See GetHeaderMarkup() for default markup string 619 | cBodyMarkup = .null. && The markup/field list for the "body" of the form. 620 | cFooterMarkup = .null. && See GetFooterMarkup() for default markup string 621 | cPopupFormBodyMarkup = .null. && See GetPopupFormBodyMarkupMarkup() for default markup string 622 | 623 | *-- These properties control the visual layout and flow of the UI controls 624 | nControlLeft = .null. && See Render method for calculation of default value 625 | nFirstControlTop = .null. && See Render method for calculation of default value 626 | nVerticalSpacing = .null. && See Render method for calculation of default value 627 | nVerticalSpacingNonControlSourceControls = .null. 628 | nHorizontalSpacing = .null. && Only used when rendering on the same row with .row=increment = '0' 629 | nHorizontalLineLeft = 10 630 | nControlHeight = 24 && I.e. the Height of Textboxes 631 | nCheckboxHeight = 24 && Default Height for Checkboxes 632 | nCommandButtonHeight = This.nControlHeight 633 | nHorizontalLabelGap = 8 && The horizontal spacing between the label and the input control (when label are NOT above the inputs) 634 | lLabelsAbove = .f. && Default position if for labels to be inline with the input control, to its left. Set this property to .t. to have the labels placed ABOVE the input control. 635 | lAutoAdjustVerticalPositionAndHeight = .f. && Forces the .Top and .Height of each control to 'snap'? to a grid system based on increments on 636 | && nControlHeight and nVerticalSpacing. The helps keeps control vertically aligned when form spans two 637 | && columns or more. When enabling this feature, any .Top and .Height values specified in attributes may 638 | && be adjusted to �snap� to the grid system at its incremental points. 639 | lResizeContainer = .f. &&Indicates if engine should resize (enlarge) oContainer to fit controls as they are added. 640 | lGenerateEditButtonForEditBoxes = .t. && If the field is form a cursor or table and it is a mem data type 641 | && DF can render a small command button beside the editbox which can be used 642 | && to pop-up a larger editbox for the memo field. This pop-up can also be activated 643 | && by double-clicking in the editbox. 644 | 645 | *-- Properties related to the popup editbox form feature 646 | cPopupFormEditboxClass = 'editbox' 647 | nPopupFormEditboxWidth = 500 648 | nPopupFormEditboxHeight = 300 649 | *-- Fields related to columns. See. http.//vfpx.codeplex.com/wikipage?title=Dynamic%20Forms#columns 650 | nColumnWidth = 200 651 | nColumnHeight = 800 && The host container can grow to this height before engine will switch to next column 652 | 653 | nTextBoxWidth = 100 654 | nEditBoxWidth = 200 655 | nNumericFieldTextboxWidth = 100 656 | nDateFieldTextboxWidth = 100 657 | nDateTimeFieldTextboxWidth = 150 658 | nCheckBoxWidth = 100 659 | nControlWidth = 100 && For any other controls besides the specific ones above 660 | 661 | nCheckBoxAlignment = 0 && 0 = Middle Left (Default in VFP) - Places caption to the right of checkbox. 662 | && 1 = Middle Right - Places caption to the left of checkbox. 663 | *nWidth = 0 && The _Assign method for this property will set oContianer Width to this value 664 | *nHeight = 0 && The _Assign method for this property will set oContianer Height to this value 665 | 666 | *-- Default classes used to create UI controls (You can override these at run time to use your own custom classes.) 667 | cLabelClass = 'DF_Label' 668 | cLabelClassLib = '' 669 | cTextboxClass = 'textbox' 670 | cTextboxClassLib = '' 671 | cEditboxClass = 'DF_MemoFieldEditBox' 672 | cEditboxCLassLib = '' 673 | cCommandButtonClass = 'DF_ResultButton' 674 | cCommandButtonClassLib = '' 675 | cOptionGroupClass = 'optiongroup' 676 | cOptionGroupClassLib = '' 677 | cCheckboxClass = 'DF_Checkbox' 678 | cCheckboxClassLib = '' 679 | cComboboxClass = 'combobox' 680 | cComboboxClassLib = '' 681 | cListboxClass = 'listbox' 682 | cListboxClassLib = '' 683 | cSpinnerClass = 'spinner' 684 | cSpinnerClassLib = '' 685 | cGridClass = 'grid' 686 | cGridClassLib = '' 687 | cImageClass = 'image' 688 | cImageClassLib = '' 689 | cTimerClass = 'timer' 690 | cTimerClassLib = '' 691 | cPageframeGroupClass = 'pageframe' 692 | cPageframeClassLib = '' 693 | cLineClass = 'line' 694 | cLineClassLib = '' 695 | cShapeClass = 'shape' 696 | cShapeClassLib = '' 697 | cContainerClass = 'container' 698 | cContainerClassLib = '' 699 | cClassLib = '' && General classlib where controls can be found, if not specified above. 700 | 701 | *--------------------------------------------------------------------------------------- 702 | * Control classes based on data types. If specified, will override the default classes. 703 | cCharacterClass = '' 704 | cCharacterClassLib = '' 705 | cNumericClass = '' 706 | cNumericClassLib = '' 707 | cDateClass = '' 708 | cDateClassLib = '' 709 | cDateTimeClass = '' 710 | cDateTimeClassLib = '' 711 | 712 | *-- Consider these read only --- 713 | nErrorCount = 0 714 | oErrors = .null. 715 | oRegex = .null. 716 | 717 | *======================================================================================= 718 | *-- Private properties used/maintained by this class only!! 719 | *Hidden nFieldCount 720 | *Hidden nColumnCount 721 | nNextControlTop = 0 722 | 723 | nColumnCount = 1 724 | nFieldsInCurrentColumn = 1 725 | oFieldList = .null. 726 | nLastControlTop = 0 727 | nLastControlBottom = 0 728 | nLastControlLeft = 0 729 | nLastControlRight = 0 730 | nControlCount = 0 731 | lInHeader = .f. 732 | lInBody = .f. 733 | lInFooter = .f. 734 | lLastControlRendered = .f. 735 | lRendered = .f. 736 | cMarkup = '' 737 | 738 | Dimension aBackup[1] 739 | Dimension aColumnWidths[1] 740 | 741 | *--------------------------------------------------------------------------------------- 742 | Procedure Init() 743 | 744 | This.oFieldList = CreateObject('Collection') 745 | This.oErrors = CreateObject('Collection') 746 | 747 | EndProc 748 | 749 | *--------------------------------------------------------------------------------------- 750 | Procedure Destroy 751 | 752 | This.oContainer = .null. 753 | This.oBusinessObject = .null. 754 | This.oDataObject = .null. 755 | This.oErrors = .null. 756 | This.oFieldList = .null. 757 | This.oRegex = .null. 758 | 759 | Endproc 760 | 761 | *--------------------------------------------------------------------------------------- 762 | Procedure Render(toContainer) 763 | 764 | Local lcCode, lcControlSource, loField 765 | 766 | *-- Open table if cAlias points to something not already open 767 | If !Empty(This.cAlias) and !Used(JustStem(This.cAlias)) 768 | llReturn = This.OpenTable() 769 | If !lLReturn 770 | Return .f. 771 | Endif 772 | Endif 773 | 774 | This.cAlias = JustStem(This.cAlias) 775 | 776 | This.oContainer = Iif(Vartype(toContainer) = 'O', toContainer, This.oContainer) 777 | If Vartype(This.oContainer) = 'U' 778 | MessageBox('Must pass container object into Render() method, or set .oContainer property.', 0, 'Warning.') 779 | Return -1 780 | Else 781 | AddProperty(This.oContainer, 'oRenderEngine', This) && Temporary. This reference will be cleared out at the end of this method. 782 | EndIf 783 | 784 | This.cBodyMarkup = Nvl(This.cBodyMarkup, This.GetBodyMarkupForAll()) 785 | 786 | If Vartype(This.oRegex) <> 'O' 787 | This.oRegex = CreateObject('VBScript.RegExp') 788 | This.PrepareRegex() 789 | Endif 790 | 791 | This.PreProcessBodyMarkup() 792 | This.BuildMarkup() && Merges Header, Body, and Footer marker, and adds some special formatting to help with rendering. 793 | This.BuildFieldList() && Build a collection of controls to be rendered by parsing the cMarkup built in BuildMarkup(). 794 | 795 | *-- Set default values for various class properties, if the user has not set any values to them... 796 | This.nControlLeft = Nvl(This.nControlLeft , Iif(This.lLabelsAbove = .t., 20, 120)) 797 | This.nFirstControlTop = Nvl(This.nFirstControlTop , Iif(This.lLabelsAbove = .t., 30, 10)) 798 | This.nHorizontalSpacing = Nvl(This.nHorizontalSpacing, 15) && Only used when rendering on the same row with .row=increment = '0' 799 | 800 | If This.lAutoAdjustVerticalPositionAndHeight = .t. 801 | If This.lLabelsAbove = .t. 802 | This.nVerticalSpacing = Nvl(This.nVerticalSpacing, 50) && Value is distance from the .Top of the last control to the the .Top of the next control 803 | Else 804 | This.nVerticalSpacing = Nvl(This.nVerticalSpacing, 30) 805 | Endif 806 | Else 807 | If This.lLabelsAbove = .t. 808 | This.nVerticalSpacing = Nvl(This.nVerticalSpacing, 30) && Value is distance from the BOTTOM of the last control to the the .Top of the next control 809 | Else 810 | This.nVerticalSpacing = Nvl(This.nVerticalSpacing, 15) 811 | Endif 812 | EndIf 813 | 814 | This.nVerticalSpacingNonControlSourceControls = Nvl(This.nVerticalSpacingNonControlSourceControls, 10) 815 | 816 | This.nNextControlTop = This.nFirstControlTop 817 | *This.nLastControlTop = This.nFirstControlTop 818 | This.nLastControlRight = This.nControlLeft - This.nHorizontalSpacing 819 | This.aColumnWidths[1] = This.nColumnWidth 820 | 821 | This.cSkipFields = ' ' + Strtran(This.cSkipFields, ',', ' ') + ' ' 822 | 823 | *-- Loop over the FieldList collection to render each control, or execute embedded code... 824 | For Each loField in This.oFieldList FOXOBJECT 825 | lcControlSource = loField.ControlSource 826 | If Left(lcControlSource, 1) + Right(lcControlSource, 1)= '()' && If ControlSource element is wrapped in (), then it's to be executed as a VFP code block, Execute it!! 827 | lcCode = Substr(lcControlSource, 2, Len(lcControlSource) - 2) 828 | Try 829 | &lcCode 830 | Catch 831 | This.AddError('Error executing code block.', loField) 832 | EndTry 833 | Else 834 | If Empty(lcControlSource) or !(' ' + Upper(lcControlSource) + ' ' $ Upper(This.cSkipFields)) 835 | This.GenerateControl(loField) 836 | EndIf 837 | Endif 838 | EndFor 839 | 840 | This.lRendered = .t. && This indicates that the Render method has been called and has completed. 841 | 842 | *-- Remove the reference to this Render Engine from the oContainer 843 | This.oContainer.oRenderEngine = .null. 844 | RemoveProperty(This.oContainer, 'oRenderEngine') 845 | 846 | Return (This.nErrorCount = 0) 847 | 848 | EndProc 849 | 850 | *--------------------------------------------------------------------------------------- 851 | *-- If cAlias is specified, but not open, then attempt to open it... 852 | Procedure OpenTable 853 | 854 | Local lcAlias, loException 855 | 856 | Try 857 | Use (This.cAlias) Again In 0 858 | This.cAlias = JustStem(This.cAlias) && Now that it's open, trim off any path and extension 859 | Catch to loException 860 | This.oErrors.Add(loException) 861 | Return .F. 862 | Endtry 863 | 864 | Endproc 865 | 866 | *--------------------------------------------------------------------------------------- 867 | *-- This method combines the Header, Body, and Footer markup together, and mixes in a 868 | *-- little extra markup between each section that will help the rendering process keep 869 | *-- track of where it is working. 870 | Procedure BuildMarkup 871 | 872 | 873 | This.cHeaderMarkup = Nvl(This.cHeaderMarkup, This.GetHeaderMarkup()) 874 | *-- See PreProcessBodyMarkup() method to see how it is preparied for use here 875 | This.cFooterMarkup = Nvl(This.cFooterMarkup, This.GetFooterMarkup()) 876 | 877 | Text to This.cMarkup NoShow TextMerge 878 | 879 | (This.lInHeader = .t.) | 880 | <> | 881 | (This.lInHeader = .f.) | 882 | 883 | (This.nFirstControlTop = This.nLastControlTop + This.nFirstControlTop) | 884 | (This.nFieldsInCurrentColumn = 1) | 885 | (This.lInBody = .t.) | 886 | <> | 887 | (This.lInBody = .f.) | 888 | 889 | (This.nLastControlBottom = This.oContainer.Height) | 890 | (This.lInFooter = .t.) | 891 | <> | 892 | (This.lInFooter = .f.) | 893 | EndText 894 | 895 | This.cMarkup = Chrtran(This.cMarkup, Chr(13) + Chr(10), ' ') 896 | 897 | EndProc 898 | 899 | 900 | *--------------------------------------------------------------------------------------- 901 | Procedure PropertyMatch(tcField, tcList) 902 | 903 | Local llSkip, x 904 | 905 | For x = 1 to GetWordCount(tcList, ', ') 906 | If Like(Upper(Alltrim(GetWordNum(tcList, x, ', '))), Upper(tcField)) 907 | Return .t. 908 | Endif 909 | Endfor 910 | 911 | Return .f. 912 | 913 | EndProc 914 | 915 | *--------------------------------------------------------------------------------------- 916 | Procedure AddControl(tcClass, tcClassLib, tcControlSourceField, tcDataType) 917 | 918 | Local lcBaseClass, lcClass, lcClassLib, lcControlName, lcPrefix, llNewObject, loControl 919 | 920 | Do Case 921 | Case Lower(tcClass) == 'label' 922 | lcClass = This.cLabelClass 923 | lcClassLib = Evl(This.cLabelClasslib, This.cClassLib) 924 | Case Lower(tcClass) == 'textbox' 925 | lcClass = This.cTextBoxClass 926 | lcClassLib = Evl(This.cTextboxClasslib, This.cClassLib) 927 | 928 | *-- These properties, if set, override the default class determined 929 | If Vartype(tcDataType) = 'C' 930 | Do Case 931 | Case tcDataType = 'C' 932 | lcClass = Evl(This.cCharacterClass, lcClass) 933 | lcClassLib = Evl(This.cCharacterClassLib, lcClassLib) 934 | Case tcDataType = 'N' 935 | lcClass = Evl(This.cNumericClass, lcClass) 936 | lcClassLib = Evl(This.cNumericClassLib, lcClassLib) 937 | Case tcDataType = 'D' 938 | lcClass = Evl(This.cDateClass, lcClass) 939 | lcClassLib = Evl(This.cDateTimeClassLib, lcClassLib) 940 | Case tcDataType = 'T' 941 | lcClass = Evl(This.cDateTimeClass, lcClass) 942 | lcClassLib = Evl(This.cDateTimeClassLib, lcClassLib) 943 | Endcase 944 | Endif 945 | Case Lower(tcClass) == 'editbox' 946 | lcClass = This.cEditboxClass 947 | lcClassLib = Evl(This.cEditboxClasslib, This.cClassLib) 948 | Case Lower(tcClass) == 'commandbutton' 949 | lcClass = This.cCommandButtonClass 950 | lcClassLib = Evl(This.cCommandButtonClasslib, This.cClassLib) 951 | Case Lower(tcClass) == 'optiongroup' 952 | lcClass = This.cOptionGroupClass 953 | lcClassLib = Evl(This.cOptionGroupClasslib, This.cClassLib) 954 | Case Lower(tcClass) == 'checkbox' 955 | lcClass = This.cCheckboxClass 956 | lcClassLib = Evl(This.cCheckboxClasslib, This.cClassLib) 957 | Case Lower(tcClass) == 'combobox' 958 | lcClass = This.cComboboxClass 959 | lcClassLib = Evl(This.cComboboxClasslib, This.cClassLib) 960 | Case Lower(tcClass) == 'listbox' 961 | lcClass = This.cListboxClass 962 | lcClassLib = Evl(This.cListboxClasslib, This.cClassLib) 963 | Case Lower(tcClass) == 'spinner' 964 | lcClass = This.cSpinnerClass 965 | lcClassLib = Evl(This.cSpinnerClasslib, This.cClassLib) 966 | Case Lower(tcClass) == 'grid' 967 | lcClass = This.cGridClass 968 | lcClassLib = Evl(This.cGridClasslib, This.cClassLib) 969 | Case Lower(tcClass) == 'image' 970 | lcClass = This.cImageClass 971 | lcClassLib = Evl(This.cImageClasslib, This.cClassLib) 972 | Case Lower(tcClass) == 'timer' 973 | lcClass = This.cTimerClass 974 | lcClassLib = Evl(This.cTimerClasslib, This.cClassLib) 975 | Case Lower(tcClass) == 'pageframe' 976 | lcClass = This.cPageframeClass 977 | lcClassLib = Evl(This.cPageframeClasslib, This.cClassLib) 978 | Case Lower(tcClass) == 'line' 979 | lcClass = This.cLineClass 980 | lcClassLib = Evl(This.cLineClasslib, This.cClassLib) 981 | Case Lower(tcClass) == 'shape' 982 | lcClass = This.cShapeClass 983 | lcClassLib = Evl(This.cShapeClasslib, This.cClassLib) 984 | Case Lower(tcClass) == 'container' 985 | lcClass = This.cContainerClass 986 | lcClassLib = Evl(This.cContainerClasslib, This.cClassLib) 987 | Otherwise 988 | lcClass = tcClass 989 | lcClassLib = Evl(tcClassLib, This.cClassLib) 990 | EndCase 991 | 992 | Try 993 | llNewObject = This.oContainer.NewObject(Sys(2015), lcClass, lcClassLib) && Sys(2015) = Random name for object. Will rename below... 994 | This.nControlCount = This.nControlCount + 1 995 | Catch 996 | llNewObject = .f. 997 | Endtry 998 | 999 | 1000 | If llNewObject = .t. 1001 | loControl = This.oContainer.Controls(This.oContainer.ControlCount) && The last control added. See above. 1002 | lcBaseClass = Lower(loControl.baseclass) 1003 | Do Case 1004 | Case lcBaseclass = 'label' 1005 | lcPrefix = 'lbl' 1006 | Case lcBaseclass = 'textbox' 1007 | lcPrefix = 'txt' 1008 | Case lcBaseclass = 'editbox' 1009 | lcPrefix = 'edit' 1010 | Case lcBaseclass = 'commandbutton' 1011 | lcPrefix = 'cmd' 1012 | Case lcBaseclass = 'optiongroup' 1013 | lcPrefix = 'opt' 1014 | Case lcBaseclass = 'checkbox' 1015 | lcPrefix = 'chk' 1016 | Case lcBaseclass = 'combobox' 1017 | lcPrefix = 'cbo' 1018 | Case lcBaseclass = 'listbox' 1019 | lcPrefix = 'list' 1020 | Case lcBaseclass = 'spinner' 1021 | lcPrefix = 'spinner' 1022 | Case lcBaseclass = 'grid' 1023 | lcPrefix = 'grid' 1024 | Case lcBaseclass = 'image' 1025 | lcPrefix = 'img' 1026 | Case lcBaseclass = 'timer' 1027 | lcPrefix = 'timer' 1028 | Case lcBaseclass = 'pageframe' 1029 | lcPrefix = 'pageframe' 1030 | Case lcBaseclass = 'line' 1031 | lcPrefix = 'line' 1032 | Case lcBaseclass = 'shape' 1033 | lcPrefix = 'shape' 1034 | Case lcBaseclass = 'container' 1035 | lcPrefix = 'cnt' 1036 | Otherwise 1037 | lcPrefix = lcClass 1038 | EndCase 1039 | 1040 | lcControlName = lcPrefix + Iif(!Empty(tcControlSourceField), Strtran(tcControlSourceField, '.', '_'), '') + '_' + Transform(This.nControlCount) 1041 | 1042 | Try 1043 | loControl.Name = lcControlName 1044 | Catch 1045 | Endtry 1046 | Else 1047 | loControl = .null. 1048 | Endif 1049 | 1050 | Return loControl 1051 | 1052 | Endproc 1053 | 1054 | *--------------------------------------------------------------------------------------- 1055 | Procedure GenerateControl(toField) 1056 | 1057 | Local laProperties[1], lcAttribute, lcBackupType, lcClassLib, lcControlClass, lcControlSource 1058 | Local lcDataSource, lcDataType, lcErrorMessage, llSuccess, lnControlMultiplier, lnX, loControl 1059 | Local loLabel, luData, llRender, llIsMemoField 1060 | 1061 | *-- Handle/Test the .render-if clause -------------------- 1062 | If PemStatus(toField, 'render_if', 5) 1063 | Try 1064 | llRender = This.GetValue(toField.render_if) 1065 | Catch 1066 | lcErrorMessage = 'Error in .render-if clause for [' + toField.ControlSource + '].' 1067 | This.AddError(lcErrorMessage, toField) 1068 | AddProperty(toField, 'class', 'DF_ErrorContainer') 1069 | AddProperty(toField, 'cErrorMsg', lcErrorMessage) 1070 | If !PemStatus(toField, 'Width', 5) 1071 | AddProperty(toField, 'width', 200) 1072 | Endif 1073 | AddProperty(toField, 'controlsource', toField.ControlSource) 1074 | loControl = This.AddControl('DF_ErrorContainer', '', toField.ControlSource) 1075 | This.StyleControl(loControl, toField) 1076 | EndTry 1077 | 1078 | If !llRender 1079 | If This.lLastControlRendered = .t. and This.GetValue(toField.row_increment) <> 0 1080 | This.nLastControlTop = This.nNextControlTop + ((This.GetValue(toField.row_increment) - 1) * This.nVerticalSpacing) 1081 | This.nNextControlTop = This.nLastControlTop 1082 | This.nLastControlRight = This.nControlLeft - This.nHorizontalSpacing 1083 | EndIf 1084 | This.lLastControlRendered = .f. 1085 | Return 1086 | Endif 1087 | Endif 1088 | 1089 | 1090 | If !Empty(toField.ControlSource) 1091 | 1092 | Do Case 1093 | *-- Does the referenced controlsource exist on the oDataObject? 1094 | Case Vartype(This.oDataObject) = 'O' and PemStatus(This.oDataObject, toField.ControlSource, 5) && Binding on an Object 1095 | lcControlSource = This.cDataObjectRef + '.' + toField.ControlSource 1096 | lcDataSource = 'This.oDataObject.' + toField.ControlSource 1097 | *-- Does the referenced controlsource exist in cAlias? 1098 | Case !Empty(This.cAlias) and Used(This.cAlias) and !Empty(Field(toField.ControlSource, This.cAlias)) 1099 | lcControlSource = This.cAlias + '.' + toField.ControlSource 1100 | lcDataSource = lcControlSource 1101 | llIsMemoField = This.IsMemoField(This.cAlias, toField.ControlSource) 1102 | *-- Does the referenced controlsource exist in the current work area ? 1103 | Case !Empty(Alias()) and !Empty(Field(toField.ControlSource, Alias())) 1104 | lcControlSource = Alias() + '.' + toField.ControlSource 1105 | lcDataSource = lcControlSource 1106 | llIsMemoField = This.IsMemoField(Alias(), toField.ControlSource) 1107 | *-- If none of the above were true, let's see if the referenced controlsource is a defined variable, or a Cursor.Field 1108 | Otherwise 1109 | *-- Probably a Cursor.Field controlsource 1110 | llIsMemoField = This.IsMemoField(GetWordNum(toField.ControlSource, 1, '.'), GetWordNum(toField.ControlSource, 2, '.')) 1111 | lcControlSource = toField.ControlSource 1112 | lcDataSource = lcControlSource 1113 | EndCase 1114 | 1115 | Try 1116 | luData = Evaluate(lcDataSource) 1117 | lcDataType = Vartype(luData) 1118 | llSuccess = .t. 1119 | Catch 1120 | *-- If all of the above failed to give us any data, then render an error label in this spot. 1121 | lcErrorMessage = 'Controlsource [' + toField.ControlSource + '] not found.' 1122 | This.AddError(lcErrorMessage, toField) 1123 | AddProperty(toField, 'class', 'DF_ErrorContainer') 1124 | AddProperty(toField, 'cErrorMsg', lcErrorMessage) 1125 | If !PemStatus(toField, 'Width', 5) 1126 | AddProperty(toField, 'width', 200) 1127 | Endif 1128 | AddProperty(toField, 'controlsource', lcControlSource) 1129 | Endtry 1130 | EndIf 1131 | 1132 | *-- If we were able to resolve the Controlsource, let's analyze what the original source was 1133 | If llSuccess 1134 | Do Case 1135 | Case Type(JustStem(lcDataSource)) = 'O' 1136 | lcBackupType = 'Object' 1137 | Case '.' $ lcDataSource and Used(JustStem(lcDataSource)) 1138 | lcBackupType = 'Alias' 1139 | Case !('.' $ lcDataSource and Used(JustStem(lcDataSource))) 1140 | lcBackupType = 'Property' 1141 | Otherwise 1142 | lcBackupType = '' 1143 | EndCase 1144 | Else 1145 | *lcControlSource = '' 1146 | lcDataType = '' 1147 | lcBackupType = '' 1148 | EndIf 1149 | 1150 | If !Empty(toField.ControlSource) 1151 | *-- Note: These baseclasses will be re-mapped to specific classes from the RenderEngine properties in the AddControl() method call below. 1152 | Do Case 1153 | Case llIsMemoField 1154 | lcControlClass = 'editbox' 1155 | Case lcDataType = 'L' 1156 | lcControlClass = 'checkbox' 1157 | Otherwise 1158 | lcControlClass = 'textbox' 1159 | Endcase 1160 | Else 1161 | lcControlClass = '' 1162 | Endif 1163 | 1164 | *-- Override default class, if one is specified in attributes 1165 | If PemStatus(toField, 'Class', 5) 1166 | lcControlClass = toField.class 1167 | Endif 1168 | 1169 | *-- Handle ClassLib assignment in the attribute list... 1170 | If PemStatus(toField, 'ClassLibrary', 5) 1171 | lcClassLib = toField.classlibrary 1172 | Else 1173 | lcClassLib = This.cClassLib 1174 | Endif 1175 | 1176 | *-- Create the control, (also adds it to the continer) 1177 | If !Empty(lcControlClass) 1178 | loControl = This.AddControl(lcControlClass, lcClassLib, toField.ControlSource, lcDataType) 1179 | Else 1180 | Return 1181 | EndIf 1182 | 1183 | If Type('loControl') = 'O' and PemStatus(loControl, 'oRenderEngine', 5) 1184 | loControl.oRenderEngine = this 1185 | Endif 1186 | 1187 | *-- If control could not be created, show an error container... 1188 | If Vartype(loControl) # 'O' 1189 | llSuccess = .f. 1190 | lcErrorMessage = 'Error in Class/ClassLibrary settings for [' + toField.ControlSource + '].' 1191 | This.AddError(lcErrorMessage, toField) 1192 | AddProperty(toField, 'class', 'DF_ErrorContainer') 1193 | AddProperty(toField, 'cErrorMsg', lcErrorMessage) 1194 | If !PemStatus(toField, 'Width', 5) 1195 | AddProperty(toField, 'width', 200) 1196 | Endif 1197 | AddProperty(toField, 'controlsource', lcControlSource) 1198 | loControl = This.AddControl('DF_ErrorContainer', '', toField.ControlSource) 1199 | EndIf 1200 | 1201 | AddProperty(loControl, 'DataType', lcDataType) 1202 | 1203 | *-- Set ControlSource on loControl 1204 | Try 1205 | loControl.ControlSource = lcControlSource 1206 | If llSuccess 1207 | This.BackupData(lcDataSource, luData, lcBackupType) 1208 | Endif 1209 | Catch 1210 | EndTry 1211 | 1212 | This.StyleControl(loControl, toField) && (Will also add the label) 1213 | 1214 | If This.lGenerateEditButtonForEditBoxes 1215 | If (Lower(loControl.baseclass) = 'editbox' or (PemStatus(toField, 'ShowEditButton',5) and This.GetValue(toField.ShowEditButton))) 1216 | If !(PemStatus(toField, 'ShowEditButton',5) and This.GetValue(toField.ShowEditButton) = .f.) 1217 | ln = loControl.Anchor 1218 | loControl.Anchor = 0 1219 | loControl.Width = loControl.Width - 20 1220 | loControl.Anchor = ln 1221 | loEditButton = This.AddControl('DF_EditButton', '', '') 1222 | AddProperty(loEditButton, 'oEditBox', loControl) 1223 | loEditButton.Visible = .t. 1224 | loEditButton.Top = This.nLastControlTop + 2 1225 | loEditButton.Left = This.nLastControlRight - 18 1226 | Do case 1227 | Case InList(ln, 4, 6) 1228 | loEditButton.Anchor = 4 1229 | Case InList(ln, 8, 9, 10, 11, 13, 15) 1230 | loEditButton.Anchor = 8 1231 | Case InList(ln, 12, 14) 1232 | loEditButton.Anchor = 12 1233 | EndCase 1234 | Endif 1235 | Endif 1236 | Endif 1237 | 1238 | 1239 | EndProc 1240 | 1241 | 1242 | *--------------------------------------------------------------------------------------- 1243 | Procedure IsMemoField(tcCursor, tcField) 1244 | 1245 | Local laFieldsFromAlias[1], llIsMemoField, lnFieldFromArray 1246 | 1247 | If Empty(tcCursor) or Empty(tcField) or not Used(tcCursor) 1248 | Return .F. 1249 | Endif 1250 | 1251 | 1252 | Try 1253 | AFields(laFieldsFromAlias, tcCursor) 1254 | lnFieldFromArray = Ascan(laFieldsFromAlias, Upper(tcField)) 1255 | If laFieldsFromAlias[lnFieldFromArray + 1] = 'M' 1256 | llIsMemoField = .t. 1257 | EndIf 1258 | Catch 1259 | llIsMemoField = .f. 1260 | Endtry 1261 | 1262 | Return llIsMemoField 1263 | 1264 | Endproc 1265 | 1266 | *--------------------------------------------------------------------------------------- 1267 | *-- This procedure will set the top, left, width, height, and apply the attributes to the control, 1268 | Procedure StyleControl(toControl, toField) 1269 | 1270 | Local laProperties[1], lcAttribute, lnColumnTest, lnControlMultiplier, lnX, loLabel 1271 | 1272 | *-- Format the control and update a few class properties to manage flow 1273 | With toControl 1274 | 1275 | This.AssignHeight(toControl, toField) 1276 | This.ManageColumn(toControl, toField) 1277 | This.AssignTop(toControl, toField) 1278 | 1279 | If Lower(toControl.Class) <> 'df_horizontalline' && Special handling for this control in Do Case below. Can't apply Left or Width here. 1280 | This.AssignWidth(toControl, toField) 1281 | This.AssignLeft(toControl, toField) 1282 | Endif 1283 | 1284 | Do Case 1285 | Case Lower(.baseclass) = 'commandbutton' 1286 | This.StyleCommandButton(toControl, toField) 1287 | Case Lower(.baseclass) = 'checkbox' 1288 | This.StyleCheckbox(toControl, toField) 1289 | Case Lower(toControl.Class) = 'df_horizontalline' 1290 | This.StyleHorizontalLine(toControl, toField) 1291 | EndCase 1292 | 1293 | .Visible = .T. 1294 | 1295 | This.ApplyAttributes(toControl, toField) 1296 | 1297 | If Lower(toControl.class) = 'df_errorcontainer' 1298 | AddProperty(toControl, 'Enabled', .t.) && This is to ensure that error container is rendered as ENABLED 1299 | EndIf 1300 | 1301 | *-- For Checkboxes, may need to change the Left property if Alignment is set for caption on left side 1302 | If Lower(.baseclass) = 'checkbox' 1303 | If .Alignment = 1 1304 | .Left = .Left - .Width + 14 1305 | EndIf 1306 | EndIf 1307 | 1308 | *-- Create a label for any control that has a ControlSource property (skip Checkboxes, they are already handled above) 1309 | If PemStatus(toControl, 'ControlSource', 5) and !Empty(toField.ControlSource) and !(Lower(toControl.baseclass) = 'checkbox') 1310 | loLabel = This.GenerateLabel(toControl, toField) 1311 | *-- Setting .row-increment = '0' will force this control to be generated on same row as last control, so 1312 | *-- we need to shift the label and control over to the right by the width of the label 1313 | Try 1314 | If This.GetValue(toField.row_increment) = 0 and !This.lLabelsAbove 1315 | toControl.Left = toControl.Left + loLabel.Width 1316 | loLabel.Left = loLabel.Left + loLabel.Width 1317 | EndIf 1318 | Catch 1319 | EndTry 1320 | 1321 | AddProperty(toControl, 'cLabelcaption', loLabel.caption) && Added this in 1.6.3 1322 | 1323 | EndIf 1324 | 1325 | If This.lResizeContainer = .t. 1326 | This.ResizeContainer(toControl) 1327 | Endif 1328 | 1329 | *-- Test is we need to widen the current column 1330 | lnColumnTest = (toControl.Left + toControl.Width) - (This.GetColumnLeft() + This.aColumnWidths[This.nColumnCount]) 1331 | 1332 | If lnColumnTest > 0 1333 | This.aColumnWidths[This.nColumnCount] = This.aColumnWidths[This.nColumnCount] + lnColumnTest 1334 | Endif 1335 | 1336 | *-- Store some values to class properties so the rendering flow can continue from here for the next control 1337 | *--This.nFieldsInCurrentColumn = This.nFieldsInCurrentColumn + lnControHeightMultiplier 1338 | This.nFieldsInCurrentColumn = This.nFieldsInCurrentColumn + 1 1339 | This.nLastControlTop = toControl.Top 1340 | This.nLastControlBottom = toControl.Top + toControl.Height + This.GetValue(toField.margin_bottom) 1341 | This.nLastControlLeft = toControl.Left 1342 | This.nLastControlRight = toControl.Left + toControl.Width + Iif(PemStatus(toField, 'margin_right', 5), This.GetValue(toField.margin_right), 0) 1343 | This.lLastControlRendered = .t. 1344 | 1345 | EndWith 1346 | 1347 | 1348 | EndProc 1349 | 1350 | *--------------------------------------------------------------------------------------- 1351 | *-- Apply any attributes that were set by the user in cMarkup for this control 1352 | Procedure ApplyAttributes(toControl, toField) 1353 | 1354 | 1355 | Local laProperties[1], lcAttribute, lcObjectName, lcObjectProperty, llApplied, llValue, lnAnchor 1356 | Local lnX, lxValue 1357 | 1358 | AMembers(laProperties, toField) 1359 | 1360 | * For lnX = 1 to Alen(laProperties) 1361 | * lcAttribute = Lower(laProperties[lnX]) 1362 | * lxValue = Getpem(toField, lcAttribute) 1363 | 1364 | lnX = 0 1365 | For Each loProperty in toField.oRenderOrder FOXOBJECT 1366 | lnX = lnX + 1 1367 | lcAttribute = Lower(loProperty) 1368 | lxValue = Getpem(toField, lcAttribute) 1369 | 1370 | Do Case 1371 | Case lcAttribute $ 'controlsource top left' 1372 | Loop && Do not apply these attribute, as we've already dealt with them in the AssignXXXXX() methods. 1373 | Case lcAttribute = 'set_focus' 1374 | Try 1375 | llValue = This.GetValue(lxValue) 1376 | If llValue = .t. 1377 | AddProperty(This.oContainer, 'DF_oSetFocus', toControl) 1378 | EndIf 1379 | Catch 1380 | Endtry 1381 | Loop 1382 | Endcase 1383 | 1384 | lnAnchor = toControl.Anchor 1385 | toControl.Anchor = 0 1386 | 1387 | If Lower(toControl.baseclass) = 'optiongroup' and ('__' $ lcAttribute) 1388 | lcObjectName = JustStem(Strtran(lcAttribute, '__', '.')) 1389 | lcObjectProperty = JustExt(Strtran(lcAttribute, '__', '.')) 1390 | Try 1391 | AddProperty(toControl.&lcObjectName, lcObjectProperty, Evaluate(toField.&lcAttribute.)) 1392 | Catch 1393 | Try 1394 | AddProperty(toControl.&lcObjectName, lcObjectProperty, toField.&lcAttribute.) 1395 | Catch 1396 | Endtry 1397 | EndTry 1398 | Loop 1399 | EndIf 1400 | 1401 | *-- JRN 9/13/2012 . Only use EVAL if the attribute value begins with ' (' 1402 | *-- the special text put in place by ParseField 1403 | *-- Todo: Per JRN, need to add code to handle these special ones: 1404 | *-- Here's the list of properties that PEME uses that can look numeric but must be character. 1405 | *-- 'CAPTION', 'COLUMNWIDTHS', 'COMMENT', 'DISPLAYVALUE', 'FORMAT', 'INPUTMASK', 'TAG', 'TOOLTIPTEXT', 'VALUE' 1406 | 1407 | llApplied = .F. 1408 | If Vartype(lxValue) = 'C' and Left(lxValue, 2) = ' (' 1409 | Try 1410 | toControl.&lcAttribute. = Evaluate(lxValue) 1411 | llApplied = .T. 1412 | Catch 1413 | lxValue = Substr(lxValue, 3, Len(lxValue) - 3) 1414 | EndTry 1415 | Endif 1416 | If !llApplied 1417 | Try 1418 | toControl.&lcAttribute. = lxValue 1419 | llApplied = .t. 1420 | Catch 1421 | Endtry 1422 | EndIf 1423 | 1424 | 1425 | If Lower(lcAttribute) # 'anchor' 1426 | toControl.Anchor = lnAnchor 1427 | Endif 1428 | 1429 | EndFor 1430 | Endproc 1431 | 1432 | 1433 | *--------------------------------------------------------------------------------------- 1434 | Procedure StyleHorizontalLine(toControl, toField) 1435 | 1436 | Local lnAnchor 1437 | 1438 | *-- Special handling for DF_HorizontalLine Class --------------- 1439 | With toControl 1440 | lnAnchor = .Anchor 1441 | .Anchor = 0 1442 | AddProperty(toField, 'Height', 0) 1443 | toField.oRenderOrder.Add('Height') 1444 | 1445 | If .Left = -1 && If still at the default value (from its class definition) 1446 | .Left = This.nHorizontalLineLeft 1447 | EndIf 1448 | If .Width = 10000 && If still at the default value (from its class definition) 1449 | .Width = Max(This.oContainer.Width - 2 * toControl.Left, 1) 1450 | EndIf 1451 | .Anchor = lnAnchor 1452 | EndWith 1453 | 1454 | Endproc 1455 | 1456 | *--------------------------------------------------------------------------------------- 1457 | Procedure StyleCheckbox(toControl, toField) 1458 | 1459 | Local lnAnchor 1460 | 1461 | *-- Slightly special handling for Checkboxes, mostly dealing with whether caption is on the left vs. right of the checkbox 1462 | With toControl 1463 | lnAnchor = .Anchor 1464 | .Anchor = 0 1465 | .Caption = This.GetLabelCaption(toControl, toField) 1466 | .WordWrap = .t. 1467 | .Height = This.nCheckboxHeight 1468 | .Alignment = This.nCheckBoxAlignment && Read default value from class property. Passed attribute may override 1469 | .Anchor = lnAnchor 1470 | Endwith 1471 | Endproc 1472 | 1473 | *--------------------------------------------------------------------------------------- 1474 | Procedure StyleCommandButton(toControl, toField) 1475 | 1476 | Local lnAnchor 1477 | 1478 | With toControl 1479 | lnAnchor = .Anchor 1480 | .Anchor = 0 1481 | .Height = This.nCommandButtonHeight 1482 | .Anchor = lnAnchor 1483 | Endwith 1484 | Endproc 1485 | 1486 | 1487 | *--------------------------------------------------------------------------------------- 1488 | Procedure AssignTop(toControl, toField, tnSpacing) 1489 | 1490 | Local luValue 1491 | 1492 | *-- For controls like commandbutton, line, checkbox, picture, etc, we only need a small amount of vertical space between this control and the previous control 1493 | If !PemStatus(toControl, 'ControlSource', 5) or (Lower(toControl.baseclass) = 'checkbox' and This.lLabelsAbove) 1494 | lnSpacing = This.nVerticalSpacingNonControlSourceControls 1495 | Else 1496 | lnSpacing = This.nVerticalSpacing 1497 | EndIf 1498 | 1499 | With toControl 1500 | *-- Setting .row-increment = '0' will force this control to be generated on same row as last control. 1501 | If PemStatus(toField, 'row', 5) 1502 | Try 1503 | .Top = This.nFirstControlTop + ((This.GetValue(toField.row) - 1) * (lnSpacing + This.nControlHeight)) 1504 | Catch 1505 | This.AddError('Error in row attribute value.', toField) 1506 | Endtry 1507 | Else 1508 | Try 1509 | If This.nFieldsInCurrentColumn = 1 and !This.lInFooter && If we are working on the first control in the column. 1510 | .Top = This.nFirstControlTop 1511 | Else 1512 | *-- See what row-increment is. Default is 1. User might have requested 0, or more than 1... 1513 | luValue = This.GetValue(toField.row_increment) 1514 | If luValue = 0 1515 | .Top = This.nLastControlTop 1516 | Else 1517 | .Top = This.nLastControlBottom + lnSpacing + ((luValue -1) * (lnSpacing + This.nControlHeight)) 1518 | EndIf 1519 | Endif 1520 | Catch 1521 | This.AddError('Error in row-increment attribute value.', toField) 1522 | Endtry 1523 | Endif 1524 | 1525 | *-- Override with setting from markup attribute, if specified. 1526 | If PemStatus(toField, 'top', 5) 1527 | Try 1528 | .Top = This.GetValue(toField.top) 1529 | Catch 1530 | This.AddError('Error in Top attribute value.', toField) 1531 | Endtry 1532 | EndIf 1533 | 1534 | *-- Add any .margin-top spacing that was set in attributes 1535 | If Vartype(tnSpacing) = 'L' 1536 | Try 1537 | .Top = .Top + This.GetValue(toField.margin_top) 1538 | Catch 1539 | This.AddError('Error in margin-top attribute value.', toField) 1540 | Endtry 1541 | EndIf 1542 | 1543 | Endwith 1544 | EndProc 1545 | 1546 | *--------------------------------------------------------------------------------------- 1547 | Procedure AssignLeft(toControl, toField) 1548 | 1549 | *-- First, assign a default Left value 1550 | toControl.Left = This.nControlLeft + This.GetColumnLeft() 1551 | 1552 | *-- Setting .row = "0" will force this control to be generated on same row as last control, and off the the right of the last control 1553 | Try 1554 | If This.GetValue(toField.row_increment) = 0 1555 | toControl.Left = This.nLastControlRight + This.nHorizontalSpacing 1556 | EndIf 1557 | Catch 1558 | EndTry 1559 | 1560 | *-- Override with setting from markup attribute, if specified. 1561 | If PemStatus(toField, 'left', 5) 1562 | Try 1563 | .Left = This.GetValue(toField.left) 1564 | Catch 1565 | This.AddError('Error in Left attribute value.', toField) 1566 | Endtry 1567 | Endif 1568 | 1569 | *-- Adjust for .margin-left as specified in markup attributes 1570 | Try 1571 | .Left = .Left + This.GetValue(toField.margin_left) 1572 | Catch 1573 | This.AddError('Error in margin-left attribute value.', toField) 1574 | Endtry 1575 | 1576 | If PemStatus(toField, 'centered', 5) 1577 | toControl.Left = (This.oContainer.Width - toControl.Width)/2 1578 | Endif 1579 | 1580 | Endproc 1581 | 1582 | *--------------------------------------------------------------------------------------- 1583 | *-- This method assigns a default Width value, from Render Engine logic and properties, but, 1584 | *-- this value may be overridden by the user in their markup syntax. If so, it will applied in 1585 | *-- the ApplyAfftributes method() call which occurs later. 1586 | Procedure AssignWidth(toControl, toField) 1587 | 1588 | 1589 | Local lcNothing, lnAnchor 1590 | 1591 | lnAnchor = toControl.Anchor 1592 | toControl.Anchor = 0 1593 | 1594 | If PemStatus(toControl, 'controlsource', 5) 1595 | Do Case 1596 | Case PemStatus(toControl, 'ControlSource', 5) and Empty(toControl.ControlSource) 1597 | *-- Do nothing 1598 | Case toControl.DataType $ 'N' 1599 | toControl.Width = Int(This.nNumericFieldTextboxWidth) 1600 | Case toControl.DataType $ 'D' 1601 | toControl.Width = Int(This.nDateFieldTextboxWidth) 1602 | Case toControl.DataType $ 'T' 1603 | toControl.Width = Int(This.nDateTimeFieldTextboxWidth) 1604 | Case Lower(toControl.baseclass) = 'checkbox' 1605 | toControl.Width = Int(This.nCheckboxWidth) 1606 | Case Lower(toControl.baseclass) = 'textbox' 1607 | toControl.Width = This.nTextBoxWidth 1608 | Case Lower(toControl.baseclass) = 'editbox' 1609 | toControl.Width = This.nEditBoxWidth 1610 | Otherwise 1611 | toControl.Width = This.nControlWidth 1612 | Endcase 1613 | EndIf 1614 | 1615 | toControl.Anchor = lnAnchor 1616 | 1617 | Endproc 1618 | 1619 | *--------------------------------------------------------------------------------------- 1620 | *-- This method assigns a default Height value, from Render Engine logic and properties, but, 1621 | *-- this value may be overridden by the user in their markup syntax. If so, it will applied in 1622 | *-- the ApplyAfftributes method() call which occurs later. 1623 | Procedure AssignHeight(toControl, toField) 1624 | 1625 | Local lnControHeightMultiplier 1626 | 1627 | 1628 | With toControl 1629 | *-- Slight override to the height if the user passed in a height attribute... 1630 | *-- (We need it to be an integer increment of This.nVerticalSpacing, so let's do a little rounding...) 1631 | lnControHeightMultiplier = 1 1632 | If PemStatus(toField, 'height', 5) and This.lAutoAdjustVerticalPositionAndHeight = .t. 1633 | Try 1634 | lnControHeightMultiplier = Int((This.GetValue(toField.Height) - This.nControlHeight) / This.nVerticalSpacing) + 1 1635 | .Height = This.nControlHeight + (lnControHeightMultiplier - 1) * This.nVerticalSpacing 1636 | Catch 1637 | This.AddError('Error in height attribute value.', toField) 1638 | Endtry 1639 | Else 1640 | If Lower(toControl.baseclass) = 'checkbox' 1641 | .Height = This.nCheckboxHeight 1642 | Else 1643 | .Height = This.nControlHeight && + (lnControHeightMultiplier - 1) * This.nVerticalSpacing 1644 | Endif 1645 | EndIf 1646 | 1647 | EndWith 1648 | 1649 | Endproc 1650 | 1651 | 1652 | *--------------------------------------------------------------------------------------- 1653 | Procedure GetColumnLeft 1654 | 1655 | Local lnColumnLeft 1656 | 1657 | *-- Calculate Left position, considering which "column" we are in... 1658 | lnColumnLeft = 0 1659 | For lnX = 1 to Alen(This.aColumnWidths) - 1 1660 | lnColumnLeft = lnColumnLeft + This.aColumnWidths[lnX] 1661 | EndFor 1662 | 1663 | Return lnColumnLeft 1664 | 1665 | Endproc 1666 | 1667 | 1668 | *--------------------------------------------------------------------------------------- 1669 | *-- Manage which column we are working in ---------------- 1670 | Procedure ManageColumn(toControl, toField) 1671 | 1672 | Local lnMarginBottom, lnMarginTop, lnX 1673 | 1674 | If PemStatus(toField, 'column', 5) 1675 | Try 1676 | If This.GetValue(toField.column) > This.nColumnCount 1677 | This.nColumnCount = This.GetValue(toField.column) 1678 | This.nFieldsInCurrentColumn = 1 1679 | *-- This.nNextControlTop = This.nFirstControlTop 1680 | Endif 1681 | Catch 1682 | This.AddError('Error in column attribute value.', toField) 1683 | Endtry 1684 | Else 1685 | lnMarginTop = This.GetValue(toField.margin_top) 1686 | lnMarginBottom = This.GetValue(toField.margin_bottom) 1687 | If This.GetValue(toField.row_increment) > 0 and ; 1688 | (This.nLastControlBottom + This.nVerticalSpacing + toControl.Height + lnMarginTop + lnMarginBottom) > This.nColumnHeight 1689 | This.nColumnCount = This.nColumnCount + 1 1690 | This.nFieldsInCurrentColumn = 1 1691 | *-- This.nNextControlTop = This.nFirstControlTop 1692 | EndIf 1693 | EndIf 1694 | 1695 | *-- Update column widths array, and set default column width for any new columns. 1696 | Dimension This.aColumnWidths[This.nColumnCount] 1697 | For lnX = 1 to Alen(This.aColumnWidths) 1698 | If Vartype(This.aColumnWidths[lnX]) = 'L' 1699 | This.aColumnWidths[lnX] = This.nColumnWidth 1700 | Endif 1701 | Endfor 1702 | 1703 | Endproc 1704 | 1705 | 1706 | *--------------------------------------------------------------------------------------- 1707 | Procedure ResizeContainer(toControl) 1708 | 1709 | Local laControls[1], lnX, loControl, loFixList 1710 | 1711 | lnAnchor = toControl.Anchor 1712 | toControl.Anchor = 0 1713 | 1714 | loFixList = CreateObject('Collection') 1715 | 1716 | *-- Store each control who use value 4 (bottom) in its anchor setting. Need to clear this, then reset it 1717 | *-- after the container is resized. 1718 | For each loControl in This.oContainer.controls 1719 | If InList(loControl.Anchor, 4, 5, 6, 7, 12, 14, 15) 1720 | loFixList.Add(loControl) 1721 | loControl.Anchor = loControl.Anchor - 4 1722 | Endif 1723 | Endfor 1724 | 1725 | *-- Adjust container Width and Height if the current control size and positions falls off the container size 1726 | With toControl 1727 | If PemStatus(This.oContainer, 'Width', 5) 1728 | This.oContainer.Width = Max(This.oContainer.Width, .left + .width + 10) 1729 | EndIf 1730 | If PemStatus(This.oContainer, 'Height', 5) 1731 | This.oContainer.Height = Max(This.oContainer.Height, .top + .height + 10) 1732 | Endif 1733 | EndWith 1734 | 1735 | For each loControl in loFixList FOXOBJECT 1736 | loControl.Anchor = loControl.Anchor + 4 1737 | Endfor 1738 | 1739 | toControl.Anchor = lnAnchor 1740 | 1741 | Endproc 1742 | 1743 | 1744 | 1745 | *--------------------------------------------------------------------------------------- 1746 | Procedure GenerateLabel(toControl, toField) 1747 | 1748 | Local laProperties[1], lcAttribute, lcibute, lnX, loLabel 1749 | 1750 | loLabel = This.AddControl(This.cLabelClass, This.cLabelClassLib, toField.ControlSource) 1751 | 1752 | With loLabel 1753 | 1754 | If This.lLabelsAbove = .f. 1755 | .Top = toControl.Top + 4 1756 | .Alignment = 1 1757 | Else 1758 | .Top = toControl.Top - 18 1759 | EndIf 1760 | 1761 | .AutoSize = .t. 1762 | .Caption = This.GetLabelCaption(toControl, toField) 1763 | .Visible = .t. 1764 | 1765 | If This.lLabelsAbove = .f. 1766 | .Left = toControl.Left - This.nHorizontalLabelGap - .Width && The locates it properly in case the Caption was set in an attribute 1767 | Else 1768 | If loLabel.Alignment = 1 1769 | .Left = toControl.Left + toControl.Width - loLabel.Width 1770 | Else 1771 | .Left = toControl.Left 1772 | Endif 1773 | Endif 1774 | 1775 | *-- Apply any attributes that were set by the user for this field 1776 | AMembers(laProperties, toField) 1777 | For lnX = 1 to Alen(laProperties) 1778 | lcFullAttribute = laProperties[lnX] 1779 | If Lower(GetWordNum(lcFullAttribute, 1, '_')) = 'label' 1780 | lcAttribute = GetWordNum(lcFullAttribute, 2, '_') 1781 | Try 1782 | .&lcAttribute. = Evaluate(toField.&lcFullAttribute.) 1783 | Catch 1784 | Try 1785 | .&lcAttribute. = toField.&lcFullAttribute. 1786 | Catch 1787 | loLabel.Caption = 'Error in cMarkup for this label' 1788 | loLabel.ForeColor = Rgb(255,0,0) 1789 | Endtry 1790 | EndTry 1791 | Endif 1792 | EndFor 1793 | 1794 | *If PemStatus(toField, 'left', 5) && Re-apply Left if it was hard set in an attribute. 1795 | * .Left = This.GetValue(toField.Left) 1796 | *Endif 1797 | 1798 | EndWith 1799 | 1800 | Return loLabel 1801 | 1802 | EndProc 1803 | 1804 | 1805 | *--------------------------------------------------------------------------------------- 1806 | Procedure GetLabelCaption(toControl, toField) 1807 | 1808 | Local lcCaption 1809 | 1810 | lcCaption = '' 1811 | 1812 | If PemStatus(toField, 'caption', 5) 1813 | lcCaption = toField.Caption 1814 | Else 1815 | If PemStatus(toControl, 'ControlSource', 5) 1816 | lcCaption = toControl.ControlSource 1817 | Else 1818 | If PemStatus(toField, 'ControlSource', 5) 1819 | lcCaption = toField.ControlSource 1820 | Endif 1821 | EndIf 1822 | 1823 | If '.' $ lcCaption 1824 | lcCaption = JustExt(lcCaption) 1825 | Endif 1826 | 1827 | lcCaption = Strtran(Proper(lcCaption), '_', ' ') 1828 | EndIf 1829 | 1830 | Return lcCaption 1831 | 1832 | Endproc 1833 | 1834 | 1835 | *--------------------------------------------------------------------------------------- 1836 | *-- This method parses the field definition string passed in (i.e one of the items from cMarkup) 1837 | *-- to return an oField object containing properties of each attribute in the item 1838 | Procedure ParseField(tcParam) 1839 | 1840 | Local lnX, lcAttribute, lcValue, lcControlSource 1841 | Local loMatch, loMatches, loField, laPositions[1], llHasCodeLine 1842 | Local loRegEx as 'VBScript.RegExp' 1843 | Local lcErrorMsg, lcProperty, loException 1844 | 1845 | tcParam = This.TrimIt(tcParam) 1846 | loRegEx = This.oRegex 1847 | 1848 | Do Case 1849 | Case Left(tcParam,1) = This.cAttributeNameDelimiterPattern 1850 | lcControlSource = '' 1851 | Case Left(tcParam,1) + Right(tcParam,1)= '()' && This allows FoxPro code to be executed, if it's store as a string in the ControlSource area. No attributes should follow on this line!! 1852 | llHasCodeLine = .t. 1853 | *--lcControlSource = Substr(tcParam, 2, Len(tcParam) - 2) 1854 | lcControlSource = tcParam 1855 | Otherwise 1856 | * (here is the 1.5.0 version): lcControlSource = GetWordNum(tcParam, 1, ' .,' + Chr(9)) && There might be no whitespace after property name and before first colon 1857 | * New in 1.6.0: 1858 | lcControlSource = GetWordNum(tcParam, 1, ' ' + Chr(9) + Chr(13)) && There must be at least one whitespace or newline after controlsource and before first attribute 1859 | Endcase 1860 | 1861 | loField = CreateObject('Empty') 1862 | AddProperty(loField, 'ControlSource', lcControlSource) 1863 | AddProperty(loField, 'row_increment', 1) && Go ahead and add this default to every object. Mayb be overridden in the attributes 1864 | AddProperty(loField, 'margin_top', 0) && etc 1865 | AddProperty(loField, 'margin_bottom', 0) && etc 1866 | AddProperty(loField, 'margin_left', 0) && etc 1867 | AddProperty(loField, 'margin_right', 0) && etc 1868 | 1869 | oRenderOrder = CreateObject('Collection') 1870 | AddProperty(loField, 'oRenderOrder', oRenderOrder) 1871 | 1872 | loMatches = loRegEx.Execute(tcParam) 1873 | 1874 | If Type('loMatches') = 'O' and !llHasCodeLine 1875 | Dimension laPositions[loMatches.Count + 1] && Create and array of position matches ------------ 1876 | lnX = 1 1877 | For Each loMatch In loMatches FOXOBJECT 1878 | laPositions[lnX] = loMatch.firstindex 1879 | lnX = lnX + 1 1880 | EndFor 1881 | laPositions[lnX] = Len(tcParam) 1882 | *-- Loop over regex matches to pull out attribute/value pairs 1883 | lnX = 1 1884 | 1885 | For Each loMatch In loMatches FOXOBJECT 1886 | lcAttribute = Alltrim(loMatch.value, 1, Chr(32), Chr(9), Chr(10), Chr(13)) 1887 | lcAttribute = Substr(lcAttribute, Len(This.cAttributeNameDelimiterPattern) + 1, Len(lcAttribute) - Len(This.cAttributeNameDelimiterPattern + This.cAttributeValueDelimiterPattern)) 1888 | lcAttribute = Alltrim(lcAttribute, 1, Chr(32), Chr(9), Chr(10), Chr(13)) 1889 | lcAttribute = Strtran(lcAttribute, '-', '_') && Must convert dashes to underscores, as dashes are not allowed in Property names 1890 | 1891 | lcValue = Rtrim(Left(tcParam, laPositions[lnX + 1])) && Trim off everything to the right, starting at the NEXT match pos. 1892 | lcValue = Substr(lcValue, loMatch.firstindex + Len(loMatch.value) + 1) && Now pull out the value 1893 | lcValue = Alltrim(lcValue, ' ', ',', Chr(9), Chr(10), Chr(13), This.cFieldDelimiterPattern) && Trim off delimiters and white space 1894 | 1895 | *** JRN 9/13/2012 Properties to be EVAL'd are wrapped in ' (' + lcValue + ')' 1896 | *** note that they can be EVAL'd directly; the parentheses do not hurt 1897 | Do Case 1898 | Case Left(lcValue, 1) = '(' 1899 | lcValue = ' ' + lcValue 1900 | Case Left(lcValue, 1) $ '.0123456789-' && allows numbers and logicals 1901 | lcValue = ' (' + lcValue + ')' 1902 | *-- Removed support for this older syntax as of 2014-09-29 1903 | *Case Substr(lcValue, 2, 1) $ '.0123456789-' && old style numbers and logicals in quotes 1904 | * lcValue = ' (' + Substr(lcValue, 2, Len(lcValue) - 2) + ')' 1905 | Otherwise 1906 | lcValue = Substr(lcValue, 2, Len(lcValue) - 2) && Trim off first and last char, which would be some kind of string symbol 1907 | Endcase 1908 | 1909 | *-- If a dot is used in the attribute, we'll flag it will 2 underscores, as this is the format for a property assignment 1910 | *-- on a child object within the current control (i.e. optiongroup) 1911 | lcAttribute = Strtran(lcAttribute, '.', '__') 1912 | 1913 | *-- If this attribute name matches a property name on the RenderEngine class, then apply it to the RenderEngine. 1914 | Try 1915 | If PemStatus(this, lcAttribute, 5) 1916 | AddProperty(This, lcAttribute, This.GetValue(lcValue)) 1917 | EndIf 1918 | Catch 1919 | EndTry 1920 | 1921 | *-- If this attribute name begins with "form_" and matches a property on the oConatiner, then apply it to the oContainer's form. 1922 | If 'form_' $ Lower(lcAttribute) 1923 | lcProperty = Strtran(lcAttribute, 'form_', '', -1, -1, 1) 1924 | Try 1925 | AddProperty(This.oContainer.parent, lcProperty, This.GetValue(lcValue)) 1926 | Catch 1927 | Endtry 1928 | EndIf 1929 | 1930 | *-- If this attribute name begins with "container_" and matches a property on the oConatiner, then apply it to the oContainer. 1931 | If 'container_' $ Lower(lcAttribute) 1932 | lcProperty = Strtran(lcAttribute, 'container_', '', -1, -1, 1) 1933 | Try 1934 | AddProperty(This.oContainer, lcProperty, This.GetValue(lcValue)) 1935 | Catch 1936 | Endtry 1937 | EndIf 1938 | 1939 | Try 1940 | AddProperty(loField, lcAttribute, lcValue) 1941 | loField.oRenderOrder.Add(lcAttribute) && Store this property in the oRenderOrder collection. Used in ApplyAttributes to apply in the same order as they appear in the markup. 1942 | lnX = lnX + 1 1943 | Catch to loException 1944 | lcErrorMsg = 'Error on [' + lcControlSource + ']. Attribute: ' + lcAttribute + ' Value: ' + lcValue 1945 | AddProperty(loField, 'class', 'label') 1946 | AddProperty(loField, 'caption', 'Error in cMarkup. ' + tcParam) 1947 | AddProperty(loField, 'forecolor', ' (Rgb(255,0,0))') 1948 | AddProperty(loField, 'fontbold', .t.) 1949 | AddProperty(loField, 'wordwrap', .t.) 1950 | AddProperty(loField, 'width', 500) 1951 | AddProperty(loField, 'tooltiptext', lcErrorMsg) 1952 | This.AddError(lcErrorMsg, toField, loException) 1953 | Endtry 1954 | Endfor 1955 | EndIf 1956 | 1957 | Return loField 1958 | 1959 | EndProc 1960 | 1961 | *======================================================================================= 1962 | Procedure EscapeForRegex(tcString) 1963 | 1964 | Local lcString 1965 | 1966 | lcString = tcString 1967 | 1968 | lcString = Strtran(tcString, '\', '\\') 1969 | lcString = Strtran(lcString, '+', '\+') 1970 | lcString = Strtran(lcString, '.', '\.') 1971 | lcString = Strtran(lcString, '|', '\|') 1972 | lcString = Strtran(lcString, '{', '\{') 1973 | lcString = Strtran(lcString, '}', '\}') 1974 | lcString = Strtran(lcString, '[', '\[') 1975 | lcString = Strtran(lcString, ']', '\]') 1976 | lcString = Strtran(lcString, '(', '\(') 1977 | lcString = Strtran(lcString, ')', '\)') 1978 | lcString = Strtran(lcString, '$', '\$') 1979 | 1980 | lcString = Strtran(lcString, '^', '\^') 1981 | *lcString = Strtran(lcString, ':', '\:') 1982 | lcString = Strtran(lcString, ';', '\;') 1983 | lcString = Strtran(lcString, '-', '\-') 1984 | lcString = Strtran(lcString, '?', '\?') 1985 | lcString = Strtran(lcString, '*', '\*') 1986 | 1987 | Return lcString 1988 | 1989 | Endproc 1990 | 1991 | *--------------------------------------------------------------------------------------- 1992 | Procedure TrimIt(tcString) 1993 | 1994 | Return Alltrim(tcString, 1, ' ', Chr(9)) 1995 | 1996 | Endproc 1997 | 1998 | *--------------------------------------------------------------------------------------- 1999 | Procedure GetValue(tuExpression) 2000 | 2001 | If Vartype(tuExpression) = 'C' and Left(tuExpression, 2) = ' (' 2002 | Return Evaluate(tuExpression) 2003 | Else 2004 | Return tuExpression 2005 | Endif 2006 | 2007 | EndProc 2008 | 2009 | 2010 | *--------------------------------------------------------------------------------------- 2011 | *-- This method iterates over the fields in just the cBodyMarkup, and calls off the ParseField() 2012 | *-- method to do a little trick before the real rendering happens... The purpose of this is that we want 2013 | *-- to process any field definitions from the cBodyMarkup which are attempting to set RenderEngine class 2014 | *-- properties by using identically named attributes. 2015 | Procedure PreProcessBodyMarkup 2016 | 2017 | 2018 | Local lcField, lcMarkup, lnCount, lnLastPos, lnPos, lnX, loField 2019 | 2020 | lnCount = 1 2021 | lnLastPos = 1 2022 | 2023 | *-- The attribute :import-header, if present, will pull in header from the GetHeaderMarkup() 2024 | This.cBodyMarkup = Strtran(This.cBodyMarkup, This.cAttributeNameDelimiterPattern + 'import-header', This.GetHeaderMarkup(), -1, -1 ,1) 2025 | 2026 | lcMarkup = This.cBodyMarkup 2027 | 2028 | *-- Read notes at the top of this mehtod to see what this loop does... 2029 | For lnX = 1 to Len(lcMarkup) 2030 | lnPos = Atc(This.cFieldDelimiterPattern, lcMarkup, lnCount) 2031 | If lnPos = 0 2032 | Exit 2033 | Endif 2034 | lcField = Substr(lcMarkup, lnLastPos, lnPos - lnLastPos) 2035 | If !Empty(lcField) 2036 | loField = This.ParseField(lcField) 2037 | Endif 2038 | lnLastPos = lnPos + Len(This.cFieldDelimiterPattern) 2039 | lnX = lnLastPos 2040 | lnCount = lnCount + 1 2041 | EndFor 2042 | 2043 | Endproc 2044 | 2045 | 2046 | *--------------------------------------------------------------------------------------- 2047 | *-- This method parses the cMarkup items to convert each item into an object in This.oFieldList collection. 2048 | *-- To omit any property from processing, include it in the cSkipFields property. 2049 | Procedure BuildFieldList 2050 | 2051 | Local lcField, lcMarkup, lnCount, lnLastPos, lnPos, lnX, loField 2052 | 2053 | lcMarkup = This.cMarkup 2054 | lnCount = 1 2055 | lnLastPos = 1 2056 | 2057 | For lnX = 1 to Len(lcMarkup) 2058 | lnPos = Atc(This.cFieldDelimiterPattern, lcMarkup, lnCount) 2059 | If lnPos = 0 2060 | Exit 2061 | Endif 2062 | lcField = Substr(lcMarkup, lnLastPos, lnPos - lnLastPos) 2063 | If !Empty(lcField) 2064 | loField = This.ParseField(lcField) 2065 | This.oFieldList.Add(loField) 2066 | Endif 2067 | lnLastPos = lnPos + Len(This.cFieldDelimiterPattern) 2068 | lnX = lnLastPos 2069 | lnCount = lnCount + 1 2070 | EndFor 2071 | 2072 | EndProc 2073 | 2074 | *--------------------------------------------------------------------------------------- 2075 | *-- From. http.//www.berezniker.com/content/pages/visual-foxpro/shallow-copy-object 2076 | Procedure CopyObject(toObject) 2077 | 2078 | Local laProps[1], lnI, loNewObject, lcPropName 2079 | 2080 | If Vartype(toObject) = 'X' && or Type('toObject.Class') <> 'U' && For Empty class only and Not Null only 2081 | Return Null 2082 | Endif 2083 | 2084 | loNewObject = Createobject('Empty') 2085 | 2086 | For lnI = 1 To Amembers(laProps, toObject, 0) 2087 | lcPropName = Lower(laProps[lnI]) 2088 | If Type([toObject.] + lcPropName ,1) = "A" 2089 | AddProperty(loNewObject, lcPropName + "[1]", Null) 2090 | = Acopy(toObject.&lcPropName, loNewObject.&lcPropName) 2091 | Else 2092 | AddProperty(loNewObject, lcPropName, Evaluate("toObject." + lcPropName)) 2093 | Endif 2094 | Endfor 2095 | 2096 | Return loNewObject 2097 | 2098 | Endproc 2099 | 2100 | *--------------------------------------------------------------------------------------- 2101 | *-- The original value of each control was saved into This.aBackup[] array so it nca be restored, 2102 | *-- if this method is called by the consumer of this class. 2103 | Procedure RestoreData 2104 | 2105 | 2106 | For lnX = 1 to Alen(This.aBackup, 1) 2107 | 2108 | If !Empty(This.aBackup[lnX, 1]) 2109 | lcControlSource = This.aBackup[lnX, 1] 2110 | luData = This.aBackup[lnX, 2] 2111 | lcType = This.aBackup[lnX, 3] 2112 | 2113 | If lcType = 'Object' or lcType = 'Property' 2114 | Store m.luData to &lcControlSource 2115 | Else 2116 | lcField = JustExt(lcControlSource) 2117 | lcCursor = JustStem(lcControlSource) 2118 | Replace &lcField With m.luData In &lcCursor 2119 | Endif 2120 | Endif 2121 | 2122 | Endfor 2123 | 2124 | EndProc 2125 | 2126 | *--------------------------------------------------------------------------------------- 2127 | *-- Each time a control is added to the container, we will make a copy of the original value 2128 | *-- so the value can be restored later if the consumer of this class calls RestoreData(). 2129 | Procedure BackupData(tcControlSource, tuValue, tcType) 2130 | 2131 | 2132 | Dimension This.aBackup[This.nControlCount, 3] 2133 | 2134 | This.aBackup[This.nControlCount, 1] = tcControlSource 2135 | This.aBackup[This.nControlCount, 2] = tuValue 2136 | This.aBackup[This.nControlCount, 3] = tcType 2137 | 2138 | Endproc 2139 | 2140 | *--------------------------------------------------------------------------------------- 2141 | Procedure AddError(tcMessage, toField, toException) 2142 | 2143 | This.nErrorCount = This.nErrorCount + 1 2144 | 2145 | loError = CreateObject('Empty') 2146 | AddProperty(loError, 'cMsg', tcMessage) 2147 | AddProperty(loError, 'oField', This.CopyObject(toField)) 2148 | This.oErrors.Add(loError) 2149 | 2150 | EndProc 2151 | 2152 | *--------------------------------------------------------------------------------------- 2153 | Procedure GetErrorsAsString 2154 | 2155 | lcString = 'There were ' + Transform(This.nErrorCount) + ' Error(s) rendering the controls.' + Chr(13) + Chr(13) 2156 | 2157 | For each loError in This.oErrors 2158 | lcString = lcString + '[' + Iif(!IsNull(loError.oField), loError.oField.ControlSource, '') + ']. ' + loError.cMsg + Chr(13) 2159 | Endfor 2160 | 2161 | Return lcString 2162 | EndProc 2163 | 2164 | *--------------------------------------------------------------------------------------- 2165 | Procedure GetHeaderMarkup 2166 | 2167 | Local lcMarkup, lc1, lc2 2168 | 2169 | lc1 = This.cAttributeNameDelimiterPattern 2170 | lc2 = This.cAttributeValueDelimiterPattern 2171 | 2172 | Text to lcMarkup NoShow TextMerge 2173 | 2174 | <>class <> 'label' 2175 | <>render-if <> (!Empty(This.cHeading)) 2176 | <>caption <> (This.cHeading) 2177 | <>name <> 'lblHeading' 2178 | <>top <> 10 2179 | <>left <> 10 2180 | <>fontsize <> (This.nHeadingFontSize) 2181 | <>fontbold <> .f. 2182 | <>autosize <> .t. | 2183 | 2184 | <>class <> 'DF_HorizontalLine' 2185 | <>render-if <> (!Empty(This.cHeading)) 2186 | <>margin-top <> -10 2187 | <>left <> 10 2188 | 2189 | EndText 2190 | 2191 | Return lcMarkup 2192 | 2193 | EndProc 2194 | 2195 | *--------------------------------------------------------------------------------------- 2196 | *-- Loop through all properties on oDataObject and all fields on cALias to build BodyMarkup for all fields, 2197 | *-- skipping any fields that are listed in cSksipFields 2198 | Procedure GetBodyMarkupForAll 2199 | 2200 | 2201 | Local laProperties[1], lcBodyMarkup, lcPropertyFromObject, lnX 2202 | 2203 | lcBodyMarkup = '' 2204 | 2205 | If !IsNull(This.oDataObject) 2206 | AMembers(laProperties, This.oDataObject) 2207 | For lnX = 1 to ALen(laProperties) 2208 | lcPropertyFromObject = laProperties[lnX] 2209 | If !Upper(lcPropertyFromObject) $ Upper(This.cSkipFields) 2210 | lcBodyMarkup = lcBodyMarkup + lcPropertyFromObject + '|' 2211 | Endif 2212 | EndFor 2213 | Endif 2214 | 2215 | If !Empty(This.cAlias) 2216 | AFields(laProperties, This.cAlias) 2217 | For lnX = 1 to ALen(laProperties) Step 18 2218 | lcPropertyFromObject = laProperties[lnX] 2219 | If !Upper(lcPropertyFromObject) $ Upper(This.cSkipFields) 2220 | lcBodyMarkup = lcBodyMarkup + lcPropertyFromObject + '|' 2221 | Endif 2222 | EndFor 2223 | Endif 2224 | 2225 | Return lcBodyMarkup 2226 | 2227 | Endproc 2228 | 2229 | 2230 | *--------------------------------------------------------------------------------------- 2231 | Procedure GetFooterMarkup 2232 | 2233 | Local lcMarkup, lc1, lc2 2234 | 2235 | lc1 = This.cAttributeNameDelimiterPattern 2236 | lc2 = This.cAttributeValueDelimiterPattern 2237 | 2238 | Text to lcMarkup NoShow TextMerge 2239 | 2240 | <>class <> 'DF_HorizontalLine' 2241 | <>name <> 'lineFooter' 2242 | <>left <> 10 2243 | <>anchor <> 14 | 2244 | 2245 | <>class <> 'DF_SaveButton' 2246 | <>name <> 'cmdSave' 2247 | <>width <> 80 2248 | <>left <> (This.oContainer.Width - 200) 2249 | <>anchor <> 12 | 2250 | 2251 | <>class <> 'DF_CancelButton' 2252 | <>name <> 'cmdCancel' 2253 | <>width <> 80 2254 | <>left <> (This.oContainer.Width - 100) 2255 | <>row-increment <> 0 2256 | <>anchor <> 12 | 2257 | 2258 | EndText 2259 | 2260 | Return lcMarkup 2261 | 2262 | Endproc 2263 | 2264 | 2265 | *--------------------------------------------------------------------------------------- 2266 | Procedure GetPopupFormBodyMarkup 2267 | 2268 | Local lcMarkup, lc1, lc2 2269 | 2270 | lc1 = This.cAttributeNameDelimiterPattern 2271 | lc2 = This.cAttributeValueDelimiterPattern 2272 | 2273 | Text to lcMarkup NoShow TextMerge 2274 | 2275 | __ControlSource__ 2276 | <>class <> '<>' 2277 | <>left <> 10 2278 | <>width <> <> 2279 | <>height <> <> 2280 | <>anchor <> 15 2281 | <>label.caption <> '' | 2282 | EndText 2283 | 2284 | Return lcMarkup 2285 | 2286 | Endproc 2287 | 2288 | *--------------------------------------------------------------------------------------- 2289 | Procedure PrepareRegex 2290 | 2291 | Local lcPattern, loMathes 2292 | 2293 | *-- This method will analyze the cBodyMarkup block and attempt to assess wheter it uses the origial : and => delimiters 2294 | *-- for attributes and values, or the newer . and = delimiters. If neither of these appear to be used, it will use the 2295 | *-- properties for cAttributeNameDelimiterPattern and cAttributeValueDelimiterPattern from the class properties. 2296 | 2297 | *-- '\w*\.?-?\w*\s*' + ;&& This one only supported one dash. New one supports mulitples dashes in attribute names 2298 | 2299 | lcPattern = '\w*\.?[-?\w*]*\s*' && .Pattern allows for '-' in attribute name. Will be converted to underscore, when stored as a poperty on toField object 2300 | 2301 | With This.oRegEx && .Pattern allows for '-' in attribute name. Will be converted to underscore, when stored as a poperty on toField object 2302 | .IgnoreCase = .T. 2303 | .Global = .T. 2304 | .MultiLine = .T. 2305 | EndWith 2306 | 2307 | This.oRegEx.Pattern = '(:' + lcPattern + '=>\s*)' 2308 | loMathes = This.oRegEx.Execute(This.cBodyMarkup) 2309 | 2310 | If loMathes.count > 0 && First, look for the original markup delimieters (: and =>) 2311 | This.cAttributeNameDelimiterPattern = ':' 2312 | This.cAttributeValueDelimiterPattern = '=>' 2313 | Else 2314 | This.oRegEx.Pattern = '(.' + lcPattern + '=\s*)' 2315 | loMathes = This.oRegEx.Execute(This.cBodyMarkup) && First, look for the newer markup delimieters (. and =) 2316 | If loMathes.count > 0 2317 | This.cAttributeNameDelimiterPattern = '.' 2318 | This.cAttributeValueDelimiterPattern = '=' 2319 | Endif 2320 | Endif 2321 | 2322 | *-- If about two test had not mathes, then use whatever properties are on the class 2323 | If loMathes.count = 0 2324 | This.oRegEx.Pattern = '(' + This.EscapeForRegex(This.cAttributeNameDelimiterPattern) + ; 2325 | lcPattern + ; 2326 | This.EscapeForRegex(This.cAttributeValueDelimiterPattern) + ; 2327 | '\s*)' 2328 | Endif 2329 | 2330 | EndProc 2331 | 2332 | 2333 | EndDefine 2334 | 2335 | 2336 | *======================================================================================= 2337 | Define Class DF_ErrorContainer as Container 2338 | 2339 | BorderStyle = 1 2340 | BackStyle = 0 2341 | BorderWidth = 1 2342 | BorderColor = Rgb(255,0,0) 2343 | 2344 | cErrorMsg = '' 2345 | DataType = '' 2346 | row_increment = 1 2347 | controlsource = '' 2348 | 2349 | 2350 | Add Object lblError as label with; 2351 | Top = 5, ; 2352 | Left = 5, ; 2353 | AutoSize = .t., ; 2354 | FontName = 'Arial', ; 2355 | FontSize = 9, ; 2356 | Name = 'lblError', ; 2357 | Forecolor = Rgb(255,0,0), ; 2358 | Caption = '' 2359 | 2360 | *--------------------------------------------------------------------------------------- 2361 | Procedure cErrorMsg_Assign(tcMessage) 2362 | 2363 | This.lblError.Caption = tcMessage 2364 | This.Refresh() 2365 | 2366 | Endproc 2367 | 2368 | Enddefine 2369 | 2370 | *======================================================================================= 2371 | Define Class DF_ResultButton as CommandButton 2372 | 2373 | *--------------------------------------------------------------------------------------- 2374 | Procedure Click 2375 | 2376 | AddProperty(Thisform, 'cReturn', This.Tag) 2377 | 2378 | If thisform.WindowType = 0 && If Modeless, Release the form. 2379 | Thisform.Release() 2380 | Else 2381 | Thisform.Hide() && If Modal, just hide the form. Calling code should release it when ready to do so. 2382 | EndIf 2383 | 2384 | EndProc 2385 | 2386 | *--------------------------------------------------------------------------------------- 2387 | Procedure Init 2388 | 2389 | This.Tag = This.Caption 2390 | 2391 | Endproc 2392 | 2393 | 2394 | *--------------------------------------------------------------------------------------- 2395 | Procedure Caption_Assign(tcCaption) 2396 | 2397 | This.Caption = tcCaption 2398 | This.Tag = Strtran(tcCaption, '\<', '') 2399 | 2400 | Endproc 2401 | 2402 | 2403 | Enddefine 2404 | 2405 | *======================================================================================= 2406 | Define Class DF_SaveButton as DF_ResultButton 2407 | 2408 | Width = 50 2409 | Height = 30 2410 | Default = .t. 2411 | Caption = 'Save' 2412 | 2413 | *--------------------------------------------------------------------------------------- 2414 | Procedure Init 2415 | 2416 | AddProperty(Thisform, 'lSaveClicked', .f.) 2417 | This.Caption = Nvl(This.parent.oRenderEngine.cSaveButtonCaption, This.Caption) 2418 | DoDefault() 2419 | 2420 | Endproc 2421 | 2422 | *--------------------------------------------------------------------------------------- 2423 | Procedure Click 2424 | 2425 | Local llReturn, loRenderEngine 2426 | loRenderEngine = Thisform.oRenderEngine 2427 | 2428 | *-- If a BusinessObject and Save method is assigned, call the BO to save the data 2429 | If Vartype(loRenderEngine.oBusinessObject) = 'O' and !Empty(loRenderEngine.cBusinessObjectSaveMethod) 2430 | lcSaveCommand = 'llReturn = Thisform.oRenderEngine.oBusinessObject.' + loRenderEngine.cBusinessObjectSaveMethod 2431 | Try 2432 | &lcSaveCommand 2433 | Catch 2434 | MessageBox('Error calling ' + loRenderEngine.cBusinessObjectSaveMethod + ' on oBusinessObject.', 0, 'Error:') 2435 | EndTry 2436 | Else 2437 | llReturn = .t. 2438 | EndIf 2439 | 2440 | If llReturn = .t. 2441 | Thisform.lSaveClicked = .t. 2442 | Thisform.Save() && Call this in case the Form classs has code in it, and so any BindEvent can be triggered. 2443 | DoDefault() 2444 | Else 2445 | This.SaveError() 2446 | EndIf 2447 | 2448 | EndProc 2449 | 2450 | 2451 | *--------------------------------------------------------------------------------------- 2452 | Procedure SaveError 2453 | 2454 | Local loRenderEngine 2455 | 2456 | loRenderEngine = Thisform.oRenderEngine 2457 | 2458 | If loRenderEngine.lShowSaveErrors = .t. 2459 | MessageBox(loRenderEngine.cSaveErrorMsg, 0, loRenderEngine.cSaveErrorCaption) 2460 | Endif 2461 | 2462 | EndProc 2463 | 2464 | Enddefine 2465 | 2466 | 2467 | *======================================================================================= 2468 | Define Class DF_EditButton as CommandButton 2469 | 2470 | Caption = '...' 2471 | Width = 20 2472 | Height = 20 2473 | 2474 | *--------------------------------------------------------------------------------------- 2475 | Procedure Click 2476 | 2477 | This.oEditBox.SetFocus() 2478 | This.oEditBox.EditData() 2479 | 2480 | Endproc 2481 | 2482 | Enddefine 2483 | 2484 | 2485 | *======================================================================================= 2486 | Define Class DF_CancelButton as DF_ResultButton 2487 | 2488 | Width = 50 2489 | Height = 30 2490 | Cancel = .t. 2491 | Caption = 'Cancel' 2492 | 2493 | *--------------------------------------------------------------------------------------- 2494 | Procedure Init 2495 | 2496 | This.Caption = Nvl(This.parent.oRenderEngine.cCancelButtonCaption, This.Caption) 2497 | AddProperty(Thisform, 'lCancelClicked', .f.) 2498 | DoDefault() 2499 | 2500 | Endproc 2501 | 2502 | *--------------------------------------------------------------------------------------- 2503 | Procedure Click 2504 | 2505 | If PemStatus(Thisform, 'lRestoreDataOnCancel', 5) and Thisform.lRestoreDataOnCancel = .t. 2506 | Thisform.RestoreData() 2507 | Endif 2508 | 2509 | Thisform.lCancelClicked = .t. 2510 | 2511 | DoDefault() 2512 | 2513 | Endproc 2514 | 2515 | Enddefine 2516 | 2517 | *======================================================================================= 2518 | Define Class DF_Label as Label 2519 | 2520 | BackStyle = 0 && Transparent 2521 | 2522 | EndDefine 2523 | 2524 | 2525 | *======================================================================================= 2526 | Define Class DF_Checkbox as Checkbox 2527 | 2528 | BackStyle = 0 && Transparent 2529 | 2530 | Enddefine 2531 | 2532 | *======================================================================================= 2533 | Define Class DF_HorizontalLine as Line 2534 | 2535 | Height = 0 2536 | Left = -1 2537 | Width = 10000 2538 | Anchor = 10 2539 | 2540 | Enddefine 2541 | 2542 | 2543 | *======================================================================================= 2544 | Define Class DF_MemoFieldEditBox as EditBox 2545 | 2546 | cDF_Class = 'DynamicForm' 2547 | cEditboxClass = 'Editbox' 2548 | cSaveButtonCaption = 'OK' 2549 | nEditboxWidth = 500 2550 | nEditboxHeight = 300 2551 | nEditboxAnchor = 15 2552 | oRenderEngine = .null. && Will be set by GenerateControl() method in DF Render Engine 2553 | 2554 | *--------------------------------------------------------------------------------------- 2555 | Procedure DblClick 2556 | 2557 | This.EditData() 2558 | 2559 | Endproc 2560 | 2561 | *--------------------------------------------------------------------------------------- 2562 | Procedure EditData 2563 | 2564 | Local lcBodyMarkup, lcDF_Class, loForm, loParentForm, loParentRenderEngine 2565 | 2566 | lcDF_Class = This.cDF_Class 2567 | Do Case 2568 | Case ! Upper(lcDF_Class) == Upper('DynamicForm') 2569 | 2570 | Case ThisForm.DeskTop = .t. 2571 | lcDF_Class = Thisform.Class 2572 | Case ThisForm.ShowWindow # 0 2573 | lcDF_Class = Thisform.Class 2574 | Endcase 2575 | loForm = CreateObject(lcDF_Class) 2576 | 2577 | *-- Theses setting control the visual layout of the pop-up form... 2578 | loForm.Caption = 'Edit ' + This.cLabelCaption 2579 | 2580 | loParentRenderEngine = This.oRenderEngine 2581 | 2582 | *-- Pass properties from original Render Engine to new form... 2583 | With loParentRenderEngine 2584 | loForm.oDataObject = .oDataObject 2585 | loForm.oBusinessObject = .oBusinessObject 2586 | loForm.cDataObjectRef = .cDataObjectRef 2587 | loForm.cBusinessObjectSaveMethod = .cBusinessObjectSaveMethod 2588 | loForm.cAlias = .cAlias 2589 | EndWith 2590 | 2591 | lcBodyMarkup = Nvl(loParentRenderEngine.cPopupFormBodyMarkup, loParentRenderEngine.GetPopupFormBodyMarkup()) 2592 | lcBodyMarkup = Strtran(lcBodyMarkup, '__ControlSource__', This.ControlSource, 1, 1 ,1) 2593 | lcBodyMarkup = Strtran(lcBodyMarkup, 'Thisform.oDataObject.', '', 1, 1000 ,1) 2594 | loForm.cBodyMarkup = lcBodyMarkup 2595 | loForm.oRenderEngine.lGenerateEditButtonForEditBoxes = .f. 2596 | 2597 | *-- Find the parent form so we can center new form in parent form 2598 | loParentForm = This.Parent 2599 | Do While Lower(loParentForm.baseclass) # 'form' 2600 | loParentForm = loParentForm.Parent 2601 | EndDo 2602 | 2603 | loForm.Show(Thisform.WindowType, loParentForm) 2604 | 2605 | EndProc 2606 | 2607 | *--------------------------------------------------------------------------------------- 2608 | Procedure Destroy 2609 | 2610 | This.oRenderEngine = .null. 2611 | 2612 | Endproc 2613 | 2614 | Enddefine 2615 | 2616 | *======================================================================================= 2617 | Define Class DynamicFormDeskTop As DynamicForm 2618 | 2619 | Desktop = .T. 2620 | 2621 | Enddefine 2622 | 2623 | *======================================================================================= 2624 | Define Class DynamicFormShowWindow As DynamicForm 2625 | 2626 | ShowWindow = 1 2627 | 2628 | Enddefine 2629 | --------------------------------------------------------------------------------