Learn how easy it is to sync an existing GitHub or Google Code repo to a SourceForge project! See Demo

Close

[r9377]: docs / trunk / oodguide / en-US / Chapter07.xml Maximize Restore History

Download this file

Chapter07.xml    1175 lines (1147 with data), 80.3 kB

   1
   2
   3
   4
   5
   6
   7
   8
   9
  10
  11
  12
  13
  14
  15
  16
  17
  18
  19
  20
  21
  22
  23
  24
  25
  26
  27
  28
  29
  30
  31
  32
  33
  34
  35
  36
  37
  38
  39
  40
  41
  42
  43
  44
  45
  46
  47
  48
  49
  50
  51
  52
  53
  54
  55
  56
  57
  58
  59
  60
  61
  62
  63
  64
  65
  66
  67
  68
  69
  70
  71
  72
  73
  74
  75
  76
  77
  78
  79
  80
  81
  82
  83
  84
  85
  86
  87
  88
  89
  90
  91
  92
  93
  94
  95
  96
  97
  98
  99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 123
 124
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
 139
 140
 141
 142
 143
 144
 145
 146
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
 157
 158
 159
 160
 161
 162
 163
 164
 165
 166
 167
 168
 169
 170
 171
 172
 173
 174
 175
 176
 177
 178
 179
 180
 181
 182
 183
 184
 185
 186
 187
 188
 189
 190
 191
 192
 193
 194
 195
 196
 197
 198
 199
 200
 201
 202
 203
 204
 205
 206
 207
 208
 209
 210
 211
 212
 213
 214
 215
 216
 217
 218
 219
 220
 221
 222
 223
 224
 225
 226
 227
 228
 229
 230
 231
 232
 233
 234
 235
 236
 237
 238
 239
 240
 241
 242
 243
 244
 245
 246
 247
 248
 249
 250
 251
 252
 253
 254
 255
 256
 257
 258
 259
 260
 261
 262
 263
 264
 265
 266
 267
 268
 269
 270
 271
 272
 273
 274
 275
 276
 277
 278
 279
 280
 281
 282
 283
 284
 285
 286
 287
 288
 289
 290
 291
 292
 293
 294
 295
 296
 297
 298
 299
 300
 301
 302
 303
 304
 305
 306
 307
 308
 309
 310
 311
 312
 313
 314
 315
 316
 317
 318
 319
 320
 321
 322
 323
 324
 325
 326
 327
 328
 329
 330
 331
 332
 333
 334
 335
 336
 337
 338
 339
 340
 341
 342
 343
 344
 345
 346
 347
 348
 349
 350
 351
 352
 353
 354
 355
 356
 357
 358
 359
 360
 361
 362
 363
 364
 365
 366
 367
 368
 369
 370
 371
 372
 373
 374
 375
 376
 377
 378
 379
 380
 381
 382
 383
 384
 385
 386
 387
 388
 389
 390
 391
 392
 393
 394
 395
 396
 397
 398
 399
 400
 401
 402
 403
 404
 405
 406
 407
 408
 409
 410
 411
 412
 413
 414
 415
 416
 417
 418
 419
 420
 421
 422
 423
 424
 425
 426
 427
 428
 429
 430
 431
 432
 433
 434
 435
 436
 437
 438
 439
 440
 441
 442
 443
 444
 445
 446
 447
 448
 449
 450
 451
 452
 453
 454
 455
 456
 457
 458
 459
 460
 461
 462
 463
 464
 465
 466
 467
 468
 469
 470
 471
 472
 473
 474
 475
 476
 477
 478
 479
 480
 481
 482
 483
 484
 485
 486
 487
 488
 489
 490
 491
 492
 493
 494
 495
 496
 497
 498
 499
 500
 501
 502
 503
 504
 505
 506
 507
 508
 509
 510
 511
 512
 513
 514
 515
 516
 517
 518
 519
 520
 521
 522
 523
 524
 525
 526
 527
 528
 529
 530
 531
 532
 533
 534
 535
 536
 537
 538
 539
 540
 541
 542
 543
 544
 545
 546
 547
 548
 549
 550
 551
 552
 553
 554
 555
 556
 557
 558
 559
 560
 561
 562
 563
 564
 565
 566
 567
 568
 569
 570
 571
 572
 573
 574
 575
 576
 577
 578
 579
 580
 581
 582
 583
 584
 585
 586
 587
 588
 589
 590
 591
 592
 593
 594
 595
 596
 597
 598
 599
 600
 601
 602
 603
 604
 605
 606
 607
 608
 609
 610
 611
 612
 613
 614
 615
 616
 617
 618
 619
 620
 621
 622
 623
 624
 625
 626
 627
 628
 629
 630
 631
 632
 633
 634
 635
 636
 637
 638
 639
 640
 641
 642
 643
 644
 645
 646
 647
 648
 649
 650
 651
 652
 653
 654
 655
 656
 657
 658
 659
 660
 661
 662
 663
 664
 665
 666
 667
 668
 669
 670
 671
 672
 673
 674
 675
 676
 677
 678
 679
 680
 681
 682
 683
 684
 685
 686
 687
 688
 689
 690
 691
 692
 693
 694
 695
 696
 697
 698
 699
 700
 701
 702
 703
 704
 705
 706
 707
 708
 709
 710
 711
 712
 713
 714
 715
 716
 717
 718
 719
 720
 721
 722
 723
 724
 725
 726
 727
 728
 729
 730
 731
 732
 733
 734
 735
 736
 737
 738
 739
 740
 741
 742
 743
 744
 745
 746
 747
 748
 749
 750
 751
 752
 753
 754
 755
 756
 757
 758
 759
 760
 761
 762
 763
 764
 765
 766
 767
 768
 769
 770
 771
 772
 773
 774
 775
 776
 777
 778
 779
 780
 781
 782
 783
 784
 785
 786
 787
 788
 789
 790
 791
 792
 793
 794
 795
 796
 797
 798
 799
 800
 801
 802
 803
 804
 805
 806
 807
 808
 809
 810
 811
 812
 813
 814
 815
 816
 817
 818
 819
 820
 821
 822
 823
 824
 825
 826
 827
 828
 829
 830
 831
 832
 833
 834
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
<?xml version='1.0' encoding='utf-8' ?>
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN" "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd"[
<!ENTITY % BOOK_ENTITIES SYSTEM "oodguide.ent">
%BOOK_ENTITIES;
]>
<!--#########################################################################
#
# Description: Open Object Rexx: ooDialog User Guide XML file
#
# Copyright (c) 2013-2013 Rexx Language Association. All rights reserved.
#
# This program and the accompanying materials are made available under
# the terms of the Common Public License v1.0 which accompanies this
# distribution. A copy is also available at the following address:
# http://www.oorexx.org/license.html
#
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
# conditions are met:
#
# Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the distribution.
#
# Neither the name of Rexx Language Association nor the names
# of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
#########################################################################
-->
<!-- Chapter07 - A Working Application v01-00 21Mar13
Changes:
v01-00 16Mar13: First version.
21Mar13: Very minor adjustments to spacing and links.
15Aug13: Corrected folder names after folder structure change.
21Aug13: Put some text into a tag (computeroutout) to see if this
fixes overlapping Publican output in section 7.3.1.
7 * Towards A Working Application chapSeven
7.1 * Introduction chap07-intro
7.2 * The 'Model-View Framework' chap07-mvf
7.2.1 * MVF Objective chap07-mvf-obj
- What has to happen to open dialog (get data etc.)
- Try PersonModel (use MsgSender - more detail later) and Wow4
- Look at code.
- now look at OrderMgr - how dialogs now opened.
7.2.2 * MVF Overview chap07-mvf-oview
- Consists of superclasses (Model, View and GenericFile)
and "manager" objects.
7.2.3 * An Example - The Person Component chap07-mvf-person
7.2.4 * MVF Classes chap07-mvf-classes
7.2.4.1 * ObjectMgr - Overview only - see appendix 5 for details. chap07-mvf-classes-objmgr
Summary of methods used
- list - Lists a file on the console.
7.2.4.2 * Model chap07-mvf-classes-model
7.2.4.3 * View chap07-mvf-classes-view
- offsetting: use MsgSender (refer to section 7.3 for more details)
e.g. PersonModel - must set Visible = False in resEdit else get a
flicker - try it - add WS_VISIBLE to .rc file (default is FALSE)
then re-open. Watch the flicker as it moves from default position
to offset position. (Note - not used for other dialogs)
(Note - it offsets relative to MsgSender - check out why!)
7.2.4.4 * Data chap07-mvf-classes-data
- Data components are subclassed from GenericFile
(in Support file 'genericfile.rex').
7.3 * Components and Data *** chap07-cmpntdata
7.3.1 Diff kinds of component; how different kinds use MVF. *** chap07-cmpntdata-kinds
- Anonymous and Singleton Components
- also Wow4 - shows how MVF can be used when doesn't conform
with entity or list.
7.3.2 Data formats - standard data formats - getRecord, *** chap07-cmpntdata-data
getFile on Data component
- different for Order since have to compose data from two files
(no DBMS!)
- Order Data - two files, OrderHeadersFile.txt and OrderLinesFile.txt
The OrderData component merges the two (??)
7.3.3 * Compound Data *** chap07-cmpntdata-compdata
7.4 Message Sender - get rid of "stand-alone" testing code. chap07-msgsender
7.5 OrderMgr - re-sizing. chap07-ordermgr
7.6 The Order Form - chap07-orderform
7.7 Next Version chap07-next
- Say this app not completed - e.g. query method not in super, OrderForm not complete, etc.
Things to add:
- drag/drop
- Complete the OrderForm
- Dialog offsetting
-->
<chapter id="chapSeven">
<title>Towards A Working Application</title>
<!-- *********************************************************************************************** -->
<section id="chap07-intro">
<title>Introduction</title>
<!-- Section 7.1 -->
<para>This chapter, and the accompanying Exercise, provides much of the infrastructure for an
application that uses model-view-data component concepts. The infrastructure implements a
pattern called the "Model-View Framework" which removes from the application developer most of
the work involved in instantiating View, Model and Data components, reading data from disk,
and providing that data to a dialog. </para>
<para>Open the <computeroutput>Exercise07</computeroutput> folder and start the Order
Management application by double-clicking on <computeroutput>startup.rex</computeroutput>. Try
it out. Explore the function which, while not complete, is much more so than in the previous
exercise. In particular, note that application data is now read from files. For example, the
Customer data is read from the file <computeroutput>CustomerFile.txt</computeroutput>.
However, although data can be changed in some dialogs, the changed data does not (in this
exercise) update the files. </para>
<para>Also, note that the <emphasis role="bold">Help</emphasis> menu on the main Sales Order
Management dialog now includes an option <emphasis role="bold">Message Sender</emphasis>
(discussed in <xref linkend="chap07-msgsender"/>). Click this option and a
"Message Sender" dialog opens. This sends messages to (invokes methods on) the various
components, and is a useful debugging tool that replaces the "stand-alone" function used
in Exercise 6. For example, try sending a "query" message to the Customer whose key is BA0314.
To do this, key "CustomerModel BA0314" (without the quotes) in the <emphasis role="bold">Target</emphasis> field ,
"query" in the <emphasis role="bold">Method</emphasis> field,
and then press <emphasis role="bold">Send</emphasis>.
The customer's data is returned in the <emphasis role="bold">Reply</emphasis> field as a
name-value string. </para>
<para>Now try using the Message Sender to surface a Product dialog - say the view for Product
CU003. To do this, select <emphasis role="bold">ObjectMgr The</emphasis> in the
<emphasis role="bold">Target</emphasis> combo-box pull-down, <emphasis role="bold">showModel</emphasis>
in the <emphasis role="bold">Method</emphasis> field, and type <computeroutput>ProductModel CU003</computeroutput>
in the <emphasis role="bold">Data</emphasis> field.
Now press <emphasis role="bold">Send</emphasis>. The Product dialog for instance CU003 appears.</para>
<para>The "Object Manager" (which could also be called "ComponentManager" since it manages
application components as opposed to any old ooRexx object) is a support class that
instantiates a component by invoking its <emphasis role="bold">newInstance</emphasis> class
method. It also keeps track of which components have been instantiated. For more detail see
<xref linkend="chap07-mvf-classes-objmgr"/></para>
<para>Consider now what has to happen to display a Product dialog. First, a
<computeroutput>ProductData</computeroutput> instance must be created, and its data is read
from disk. Then the appropriate <computeroutput>ProductModel</computeroutput> component must
be instantiated and its data retrieved from the <computeroutput>ProductData</computeroutput>
component. Finally, an instance of <computeroutput>ProductView</computeroutput> has to be
created and its data retrieved from the <computeroutput>ProductModel</computeroutput>
component by invoking its <computeroutput>query</computeroutput> method. </para>
<para>This sequence is a pattern - the "Model-View Framework" pattern - that can be applied to
most business components, and hence can be handled by superclasses instead of by duplicated
code in application components. </para>
<para>The next section in this chapter introduces the Model-View Framework - its objectives, a
brief overview of its function, an example of use, and the classes that implement it. Then
<xref linkend="chap07-cmpntdata"/> discusses first the different "kinds" of application
component and also the data formats returned by data components. The fourth section provides
additional detail on the <xref linkend="chap07-msgsender"/>, and then <xref
linkend="chap07-ordermgr"/> for the Order Manager dialog is re-visited. Finally the use of
Control Dialogs for the <xref linkend="chap07-orderform"/> is described.</para>
</section>
<!-- End of Section 7.1 -->
<!-- *********************************************************************************************** -->
<section id="chap07-mvf" xreflabel="Model-View Framework">
<title id="ch7-mvf.title">The Model-View Framework</title>
<!-- Section 7.2 -->
<para>This section presents the externals of the MVF. For some additional detail,
see <xref linkend="apx5-mvf"/>.</para>
<section id="chap07-mvf-obj">
<title id="ch7-mvf-obj.title">MVF Objective</title>
<!-- Section 7.2.1 -->
<para>The objective of the Model-View Framework (MVF) is to provide a mechanism whereby
application components can read and write data and display views without needing to be aware
of <emphasis role="italic">how</emphasis> this is done. Thus the MVF supports
view-model-data separation of concerns in application components. The MVF comprises
three superclasses for application components called <computeroutput>Model</computeroutput>,
<computeroutput>xxView</computeroutput> (where "xx" is "Rc", "Res" or "Ud")
<footnote><para>The three "xxView" classes are identical except for their superclasses - RcDialog, ResDialog
and UserDialog respectively (in the next exercise, it is planned that these will be merged into a
single mixin class).</para></footnote>, and <computeroutput>GenericFile</computeroutput>
(for data components), plus two "manager" objects:
<computeroutput>ObjectMgr</computeroutput> and
<computeroutput>ViewMgr</computeroutput>.</para>
<para>When a user double-clicks on an item in say the Customer List dialog, the confident
expectation is that a Customer dialog that shows the data associated with the list item will
be displayed. Now consider what has to happen to make that product dialog appear:</para>
<orderedlist>
<listitem>
<para>Create the appropriate data component, which...</para>
</listitem>
<listitem>
<para>... opens the correct file and reads the data.</para>
</listitem>
<listitem>
<para>Create the model component and provide it with its "key" (e.g. customer
number).</para>
</listitem>
<listitem>
<para>In the model component, get a reference to the data component.</para>
</listitem>
<listitem>
<para>Invoke a method on the data component to retrieve the data associated with the
model's key.</para>
</listitem>
<listitem>
<para>Create a view component.</para>
</listitem>
<listitem>
<para>Provide the view component with the model's data.</para>
</listitem>
<listitem>
<para>Make the dialog (including data) visible.</para>
</listitem>
</orderedlist>
<para>This sequence set of actions assumes that none of the component instances involved are
yet activated. However, the MVF must also work when some or all are activated. For example,
if a Customer dialog exists but is minimized, and the user double-clicks on that customer in
a Customer List dialog, then the MVF need only surface the Customer dialog. Thus the MVF
distinguishes between a number of different states, and relieves the programmer from having
to code the logic for each component and for each possible state. </para>
<para>Consider, for example, the code in <computeroutput>OrderMgrView</computeroutput> that
launches a List View. On the left is the Exercise 6 code, and on the right is the Exercise 7
code (equivalent or identical statements have been placed on the same line for comparison). <programlisting><![CDATA[ OrderMgrView
Exercise 6 Exercise 7
---------- ----------
::METHOD showModel UNGUARDED ::METHOD showModel UNGUARDED
expose idObjectMgr
use arg record use arg record
className = record~ID className = record~ID
viewClassName = className||"View"
interpret "."||viewClassName||- r = idObjectMgr~showModel(classname, -
"~newInstance(self)" "a", self)
]]> </programlisting> The key difference is that in Exercise 6 the Customer List is launched
without any concern for the data - because the data is hard-coded in the List View. In
Exercise 7, on the other hand, the data is read from disk and provided to the List View.
This is done by the <computeroutput>showModel</computeroutput> method of the Object Manager
(<computeroutput>idObjectMgr</computeroutput>), its id having
been retrieved from <computeroutput>.local</computeroutput> in the
<computeroutput>init</computeroutput> method.</para>
<para>In the Exercise 7 code, the second parameter in the
<computeroutput>showModel</computeroutput> method is "a". This indicates that the List
View instance is "anonymous". Instance names and "kinds" of component are discussed in <xref
linkend="chap07-cmpntdata-kinds"/>. </para>
<para>Now that the Customer List View has been created, consider what happens when the user
double-clicks on an item in the list. In Exercise 6, the list data was hard-coded. In
Exercise 7 it is read from disk by the <computeroutput>CustomerData</computeroutput> class.
In both exercises,
launching a Customer View from the list (when the user double-clicks on a list item) is done
by the <computeroutput>showCustomer</computeroutput> method as follows (excluding code
common to both): <programlisting><![CDATA[ CustomerListView
Exercise 6 Exercise 7
---------- ----------
::METHOD showCustomer UNGUARDED ::METHOD showCustomer UNGUARDED
... ...
.local~my.idCustomerData = .CustomerData~new
.local~my.idCustomerModel = .CustomerModel~new
.local~my.idCustomerData~activate
.local~my.idCustomerModel~activate
objectMgr = .local~my.ObjectMgr
.CustomerView~newInstance(rootDlg,"CU003") objectMgr~showModel("CustomerModel", -
info~text, rootDlg)
... ...
]]></programlisting>In Exercise 6, before the CustomerView is instantiated, the CustomerData and
CustomerModel components must be instantiated, and their object IDs stored in
<computeroutput>.local</computeroutput> for later access by the CustomerView and
CustomerModel objects. This is needed because, although no data is actually read from disk, the
CustomerView invokes a method on CustomerModel which invokes CustomerData. </para>
<para>In Exercise 7, on the other hand, the value of
<computeroutput>info~text</computeroutput> (the index read from the selected ListView row)
is the Customer Number, and is used as the Customer object's instance name. The Object Manager's
<computeroutput>showModel</computeroutput> method then manages or "choreographs" the
sequence of method invocations required to surface the Customer View dialog, and (if
necessary) create the appropriate model and data objects. This choreography, using function in the
<computeroutput>Model</computeroutput>, <computeroutput>xxView</computeroutput> and
<computeroutput>GenericData</computeroutput> superclasses, results in the Customer View
being surfaced with its data read from disk and displayed. See <xref linkend="apx5-mvf-ops"/>
for a full description of the way in which this is done.</para>
<para>Finally, note that in Exercise 7, the names of the possible List Model classes (e.g.
<computeroutput>CustomerListModel</computeroutput>) are hard-coded in the
<computeroutput>initRecords</computeroutput> method of
<computeroutput>OrderMgrView</computeroutput> (although these would arguably be better
placed in some configuration file).</para>
</section>
<!-- End of Section 7.2.1 -->
<section id="chap07-mvf-oview" xreflabel="MVF Overview">
<title id="ch7-mvf-oview.title">MVF Overview</title>
<!-- Section 7.2.2 -->
<para> The MVF consists of five classes: <computeroutput>ObjectMgr</computeroutput>,
<computeroutput>ViewMgr</computeroutput>, <computeroutput>Model</computeroutput>,
<computeroutput>xxView</computeroutput> (i.e. RcView, ResView, and UdView), and
<computeroutput>GenericFile</computeroutput> (see <xref linkend="chap07-mvf-classes"/>
for more detail). These are all located in the folder
<computeroutput>ooRexx\samples\oodialog\userGuide\exercises\Exercise07\Support</computeroutput>.
Together, they provide for the three different types of application component - view, model, and
data. Each model gets its data from its data component, and each model has a single view. (A
production-strength MVF could support multiple views of the same model by providing an "Open
as..." option for a given icon or list item. This would display a selection of views,
similar to the "Open with..." function provided by a button-2 click on an item in Windows
Explorer.)</para>
<para>MVF requires that each component has a text name, the name being the class name of the
main class (such as "CustomerModel", "ProductView" or "OrderData") together with an instance
name.<indexterm>
<primary>Component</primary>
<secondary>Name</secondary>
</indexterm>
<indexterm>
<primary>Name</primary>
<secondary>Component</secondary>
</indexterm> For components with a "key" such as Customer Number, the instance name is the
key (e.g. "CustomerModel BA0314"). For components that are "singletons" - that is, there can
logically be only one instance, the name is "The". An example of a singleton is a data
component (e.g. "CustomerData The"). Finally, some components - such as lists - are
anonymous: when an anonymous component is instantiated, its instance name is given as "a" or "A",
and its real instance name - such as a number as in "CustomerList 3" - is assigned by the
<computeroutput>Model</computeroutput> super-class (part of the MVF) during instantiation.
The instance name is important to MVF since its internal logic differs slightly depending on which kind of
instance name is used - a "key" name, a singleton name, or an anonymous name. (Note that
this naming convention could be relaxed if components were named in a configuration file;
however, the distinctions between the different kinds of component would remain.)
See <xref linkend="chap07-cmpntdata-kinds"/> for further discussion. </para>
</section>
<section id="chap07-mvf-oview-person" xreflabel="The 'Person' Component"> <!-- Section 7.2.3 -->
<title id="ch7-mvf-oview-person.title">An Example - The 'Person' Component</title>
<indexterm>
<primary>Person component</primary>
</indexterm>
<indexterm>
<primary>Component</primary>
<secondary>Person</secondary>
</indexterm>
<para>A key question is, what does the MVF look like to the programmer? This section answers
this question using the Person component (which has minimal function) as an example.
But first, using the Message Sender, try sending a
<computeroutput>showModel</computeroutput> message to <emphasis role="bold">ObjectMgr The</emphasis>
with the data <computeroutput>PersonModel PA150</computeroutput>.
The Person dialog appears, and the MVF has handled the task of ensuring that both the Data
and Model components are active before the View is launched. First, MVF checks to see if
they are already activated; if not, it instantiates them (the Data instance first, then
the Model). Second, on instantiation, <computeroutput>PersonModel</computeroutput>'s
superclass asks the <computeroutput>PersonData</computeroutput> instance for its data.
Third, <computeroutput>PersonView</computeroutput>'s superclass asks
<computeroutput>PersonModel</computeroutput> for its data. Finally, the dialog for
Person PA150 appears. </para>
<para>The code required to conform with the MVF is shown in the Person component in
<computeroutput>ooRexx\samples\oodialog\userGuide\exercises\Exercise07\Extras\Person</computeroutput>,
and those requirements are as follows:</para>
<itemizedlist>
<listitem>
<para><emphasis role="bold">A Data Component </emphasis>(a subclass of
<computeroutput>GenericFile</computeroutput>)</para>
<para><programlisting><![CDATA[::METHOD newInstance CLASS PUBLIC
...
-- Check if an instance has already been created; if so, return .false.
...
idData = self~new()
return idData
::METHOD init PRIVATE
...
records = self~init:super(fileName, columns)
...]]></programlisting>Data components such as <computeroutput>PersonData</computeroutput> are
required to provide a <computeroutput>newInstance</computeroutput> class method,
which is invoked by the MVF. No parameters are provided. This method first checks if
an instance has already been created. If not, it is created, and its object ID is
returned to the MVF (i.e. to the caller). </para>
<para>In the <computeroutput>init</computeroutput> method, the superclass'
<computeroutput>init</computeroutput> method is invoked with the filename and the
number of columns in the file as parameters. Invocation of super with these
parameters is an MVF requirement.</para>
</listitem>
<listitem>
<para><emphasis role="bold">A Model Component</emphasis> (a subclass of
<computeroutput>Model</computeroutput>)</para>
<para><programlisting><![CDATA[::METHOD newInstance CLASS PUBLIC
use strict arg instanceName
forward class (super) continue
modelId = RESULT
return modelId
::METHOD init
use strict arg myData]]></programlisting>Model components such as
<computeroutput>PersonModel</computeroutput> are required to provide a
<computeroutput>newInstance</computeroutput> class method with one required argument - the
model's instance name. The method must be forwarded to the
<computeroutput>Model</computeroutput> superclass, which first retrieves the intended
instance's data from its data component, and then creates an instance of itself with the
instance data as a parameter. The new instance must then be returned. </para>
</listitem>
<listitem>
<para><emphasis role="bold">A View Component</emphasis> (a subclass of
<computeroutput>RcView</computeroutput>, <computeroutput>ResView</computeroutput>,
or <computeroutput>UdView</computeroutput>)</para>
<para><programlisting><![CDATA[::METHOD newInstance CLASS PUBLIC
use strict arg modelId, rootDlg
-- create dialog, e.g. "dlg = .PersonView~new(...)"
dlg~activate(modelId, rootDlg)
return dlg
::METHOD activate UNGUARDED
use strict arg modelId, rootDlg
forward class (super) continue
personData = RESULT
]]></programlisting>View components such as <computeroutput>PersonView</computeroutput> must provide
a <computeroutput>newInstance</computeroutput> class method and an
<computeroutput>activate</computeroutput> method. The
<computeroutput>newInstance</computeroutput> method is invoked by MVF with the
view's model id (and also the root dialog - that is, the Order Manager dialog).
After the dialog is created, MVF requires that
<computeroutput>activate</computeroutput>, with the model's id as the first
argument, be invoked on the new dialog. In the
<computeroutput>activate</computeroutput> method, the superclass must be invoked
using <computeroutput>forward</computeroutput>. The superclass returns the Model's
data in <computeroutput>RESULT</computeroutput>. Finally, the id of the new dialog
must be returned.</para>
</listitem>
</itemizedlist>
<para>The above is how a "named" component uses MVF. By "named" is meant a component whose
identity is a combination of its class and a specific "key" such as a Customer Number, or
Product Number. However, there are three other kinds of component: a "singleton" such as
a data component, a "form" such as the Order Form, and "anonymous" such as a Customer
List. These are discussed below in <xref linkend="chap07-cmpntdata-kinds"/>.</para>
</section>
<!-- End of 7.2.3 -->
<section id="chap07-mvf-classes" xreflabel="MVF Classes"> <!-- Section 7.2.4 -->
<title id="ch7-mvf-classes.title">MVF Classes</title>
<para>This section describes each of the classes that comprise the MVF. They are, the Object Manager,
the View Manager, plus three superclasses, one for each of Model, View, and Data components.
</para>
<section id="chap07-mvf-classes-objmgr" xreflabel="The Object Manager">
<title id="ch7-mvf-classeso-objmgr.title">The Object Manager</title>
<!-- Section 7.2.4.1 -->
<indexterm>
<primary>Object Manager</primary>
<secondary>Methods</secondary>
</indexterm>
<indexterm>
<primary>MVF</primary>
<secondary>Object Manager</secondary>
</indexterm>
<para>The Object Manager (<computeroutput>ObjectMgr.rex</computeroutput> in the
<computeroutput>userGuide\exercises\Support</computeroutput> folder) is a "singleton"
class (there can only logically be one of them) and has the external name "ObjectMgr The".
It maintains a table (called the "Object Bag") of all instantiated components. The public
methods of the Object Manager are:</para>
<para>
<itemizedlist>
<listitem>
<para><computeroutput>getComponentId</computeroutput> (with parameters
<emphasis role="italic"><emphasis role="bold">className</emphasis></emphasis> and
<emphasis role="italic"><emphasis role="bold">instanceName</emphasis></emphasis>)
- Returns the id of the requested
component. If the id is not in the Object Bag, then it sends
<computeroutput>newInstance</computeroutput> to the class object
<emphasis role="italic"><emphasis role="bold">className</emphasis></emphasis>.
If the class does not not exist,
<computeroutput>.false</computeroutput> is returned.</para>
</listitem>
<listitem>
<para><computeroutput>list</computeroutput> - Lists the contents of the ObjectBag on
the Command Prompt. The following shows the
results of sending <emphasis role="bold">list</emphasis> to the Object Bag after
(a) the application was started, then (b) the
<emphasis role="bold">Customer List</emphasis> icon was double-clicked,
(c) a Customer in the list was double-clicked, and (d) the
<link linkend="chap07-msgsender">Message Sender</link> was used to send a
<computeroutput>query</computeroutput> message to <emphasis role="bold">ProductModel LM400</emphasis>:
<programlisting><![CDATA[Object Bag List:
----------------------------------------------------------------------------
Class-Instance Model Id ViewClass-Inst
------------------------ ------------------------ ------------------------
CUSTOMERLISTVIEW-26701042 a CUSTOMERLISTVIEW .nil
PRODUCTMODEL-LM400 a PRODUCTMODEL .nil
CUSTOMERVIEW-267047652 a CUSTOMERVIEW .nil
CUSTOMERLISTMODEL-1 a CUSTOMERLISTMODEL CUSTOMERLISTVIEW-26701042
CUSTOMERDATA-THE a CUSTOMERDATA .nil
PRODUCTDATA-THE a PRODUCTDATA .nil
CUSTOMERMODEL-BA0314 a CUSTOMERMODEL CUSTOMERVIEW-267047652
----------------------------------------------------------------------------
]]>
</programlisting>The instance name for a View component is derived by invoking
<computeroutput>identityHash</computeroutput> on its id.</para>
</listitem>
<listitem>
<para><computeroutput>showModel</computeroutput>(className, instanceName) - Shows the
View for the specified Model. If the View exists, then it is surfaced. If not, then
if the Data component is not already instantiated, it is instantiated. If the Model
component is not already instantiated, it is instantiated. Then the View is
instantiated. All instantiations use the <emphasis role="italic"
>newInstance</emphasis> method. All dialogs except the "application" dialog (that
is, Order Manager) and the Message Sender are created and/or surfaced using the
<computeroutput>showModel</computeroutput> method. </para>
</listitem>
</itemizedlist>
</para>
<para>For further information on how the <computeroutput>showModel</computeroutput> method of
the Object Manager works, see <xref linkend="apx5-mvf-ops"/>.
</para>
</section>
<!-- End of Section 7.2.4.1 -->
<section id="chap07-mvf-classes-model" xreflabel="The 'Model' Superclass">
<title>The 'Model' Superclass</title>
<!-- Section 7.2.4.2 -->
<indexterm>
<primary>Model</primary>
<secondary>Methods</secondary>
</indexterm>
<indexterm>
<primary>MVF</primary>
<secondary>Model</secondary>
</indexterm>
<indexterm>
<primary>Superclasses</primary>
<secondary>Model</secondary>
</indexterm>
<para><computeroutput>Model</computeroutput> is the MVF superclass for all model components,
and provides key methods for subclasses as follows:</para>
<itemizedlist>
<listitem>
<para><computeroutput>newInstance</computeroutput>
<indexterm>
<primary>newInstance</primary>
</indexterm> - invoked by the Object Manager (which ensures that the required data
component is instantiated) with an instance name as the single parameter. The id of
the data component is retrieved from Object Manager, after which
<computeroutput>getRecord</computeroutput> (or
<computeroutput>getFile</computeroutput> for a "list" component) is invoked on the data
component. Then <computeroutput>self~new</computeroutput> is invoked with model's
data as a parameter. <computeroutput>Model</computeroutput> also provides an instance attribute
<computeroutput>myData</computeroutput> that contains the instance data returned from
the data component.</para>
</listitem>
<listitem>
<para><computeroutput>getInstanceName</computeroutput>
<indexterm>
<primary>getInstanceName</primary>
</indexterm> is invoked by the Object Manager's
<computeroutput>showModel</computeroutput> method for anonymous components only
(such as a <computeroutput>CustomerListModel</computeroutput>). It adds 1 to a class
variable and returns it. (However, for <computeroutput>OrderFormModel</computeroutput>,
<indexterm><primary>OrderFormModel</primary></indexterm>
the method is over-ridden and <computeroutput>OrderFormModel</computeroutput> itself
returns a new order number (see <xref linkend="chap07-orderform"/>).</para>
</listitem>
<listitem>
<para><computeroutput>query</computeroutput>
<indexterm>
<primary>Query method</primary>
</indexterm> - A component framework generally requires that components provide
specific methods defined by the framework. Aside from instance creation methods, a
"well-known" method is required for MVF to access a model component's data. This
method has the name "query", and it must conform to a specific protocol as follows:<itemizedlist>
<listitem>
<para>If a component's <computeroutput>query</computeroutput> method is invoked
with no parameters, then it must return a directory containing all the
"public" data it has. The directory indexes are the labels for the data as
defined in the "database" (although this is not usually the case for real
production-strength systems, where the data dictionary for application-level
components often differs from the column names in an SQL database).</para>
<para>For example, use the Message Sender to send <emphasis role="bold"
><emphasis role="italic">query</emphasis></emphasis> to <emphasis
role="bold"><emphasis role="italic">PersonModel PA150</emphasis></emphasis>.
A directory is returned by <computeroutput>PersonModel</computeroutput>, and
the Message Sender presents the directory in name-value form in its <emphasis
role="bold">Reply</emphasis> field as follows: <programlisting><![CDATA[dob: 751513; baseSalary: 38000; number: PA150; jobDescr: Packer;
familyName: James; firstName: Alfred;
]]></programlisting></para>
</listitem>
<listitem>
<para>If a component's <computeroutput>query</computeroutput> method is invoked
with one parameter, and when that parameter is a directory, an array, or a
string, then only those fields specified by name are returned. </para>
<para>For example, use the Message Sender to query the first name and family
name for "PersonModel PA150". To do this, specify the fields by name
(case-sensitive) in the <emphasis role="bold">Data</emphasis> edit field, as
follows: <emphasis role="bold"><emphasis role="italic">firstName
familyName</emphasis></emphasis> (note - field names are case-sensitive).
On pressing the <emphasis role="bold">Send</emphasis> button, the data
<emphasis role="bold"><emphasis role="italic">firstName: Alfred; familyName:
James;</emphasis></emphasis> is returned as a directory which Message
Sender unpacks and presents as a string in the <emphasis role="bold"><emphasis
role="italic">Reply</emphasis></emphasis> field. </para>
<para>For debugging purposes,<indexterm><primary>Debugging</primary></indexterm>
<indexterm><primary>Message Sender</primary><secondary>Debugging</secondary></indexterm>
you can use MessageSender to send a set of data names as a string
(as in the example just given), a directory, or an array. To send as a
directory, enclose each name in square brackets, e.g.:
<emphasis role="bold">[firstName] [familyName]</emphasis>.
To send as an array, place a vertical bar before each name,
e.g: <emphasis role="bold">| firstName | familyName</emphasis>. </para>
</listitem>
</itemizedlist></para>
</listitem>
</itemizedlist>
</section>
<!-- End of Section 7.2.4.2 -->
<section id="chap07-mvf-classes-view" xreflabel="The 'View' Superclass">
<title>The 'View' Superclass</title>
<!-- Section 7.2.4.3 -->
<indexterm><primary>View</primary><secondary>Methods</secondary></indexterm>
<indexterm><primary>MVF</primary><secondary>View</secondary></indexterm>
<indexterm><primary>Superclasses</primary><secondary>View</secondary></indexterm>
<indexterm><primary>RcView</primary></indexterm>
<indexterm><primary>ResView</primary></indexterm>
<indexterm><primary>UdView</primary></indexterm>
<para>There are three View superclasses in the
<computeroutput>Support</computeroutput> folder:
<computeroutput>RcView</computeroutput>, <computeroutput>ResView</computeroutput>, and
<computeroutput>UdView</computeroutput>. They are identical except for their
superclasses - <computeroutput>RcDialog</computeroutput>,
<computeroutput>ResDialog</computeroutput> and
<computeroutput>UserDialog</computeroutput> respectively (their function could be
provided by a single mixin class, and this is planned for a later version of the User
Guide exercises). Important methods are:</para>
<itemizedlist>
<listitem>
<para><computeroutput>activate</computeroutput>
<indexterm><primary>activate method</primary><secondary>in View superclass</secondary></indexterm>
<indexterm><primary>View superclass</primary><secondary>activate method</secondary></indexterm>
- This is invoked by the subclass,
which must provide the view's model id as the single argument. A
<computeroutput>query</computeroutput> message is invoked on the model, which
returns the model's data as a directory. In addition, this method saves information
used to tidy up the view in the <computeroutput>leaving</computeroutput> method.
</para>
</listitem>
<listitem>
<para><computeroutput>leaving</computeroutput>
<indexterm><primary>leaving method</primary></indexterm>
<indexterm><primary>View superclass</primary><secondary>leaving method</secondary></indexterm>
- This method is automatically invoked by ooDialog when a dialog
closes. Its only function is to inform the Object Manager that the view is about
to close. The Object Manager then removes the view from the Object Bag (otherwise
it might later try to surface a non-existant view!).
</para>
</listitem>
<listitem>
<para><computeroutput>offset</computeroutput>
<indexterm><primary>offset method</primary></indexterm>
<indexterm><primary>View superclass</primary><secondary>offset method</secondary></indexterm>
- This method offsets dialogs from the Order Management dialog when
first opened. Although not used elsewhere in this exercise, the effect can be seen
using the "Person" component in the <computeroutput>Exercise07\Extras\Person</computeroutput>
folder. First, use the Message Sender to launch a Person dialog (for example send
<emphasis role="bold"><emphasis role="italic">showModel</emphasis></emphasis> to
<emphasis role="bold"><emphasis role="italic">ObjectMgr The</emphasis></emphasis>
with the data <emphasis role="bold"><emphasis role="italic">PersonModel
PA150</emphasis></emphasis>). The Person dialog appears in the center
of the screen. Now un-comment the last line
(<computeroutput>--self~offset:super</computeroutput>) in the
<computeroutput>initDialog</computeroutput> method of
<computeroutput>PersonView</computeroutput>, save, re-start the application, and
launch the Person dialog as before. Note that the dialog "flickers" when opened - it
seems to open for a fraction of a second in the centre of the screen, then
re-appears offset from Message Sender. The flicker results from the .rc file
containing the dialog property WS_VISIBLE (in ResEdit the behavior property
"visible" is set to true). First it appears in the center of the screen, then moves
to the offset position. Now remove "<computeroutput>| WS_VISIBLE</computeroutput>"
from the .rc file, save, and re-run. The Person dialog appears without a flicker but
offset (from the Message Sender).</para>
</listitem>
</itemizedlist>
</section>
<!-- End of Section 7.2.4.3 -->
<section id="chap07-mvf-oview-data" xreflabel="The 'GenericFile' Superclass">
<title>The 'GenericFile' Superclass</title>
<!-- Section 7.2.4.4 -->
<indexterm><primary>Generic File</primary></indexterm>
<indexterm><primary>MVF</primary><secondary>GenericFile</secondary></indexterm>
<indexterm><primary>Superclasses</primary><secondary>GenericFile</secondary></indexterm>
<indexterm><primary>Superclasses</primary><secondary>GenericFile</secondary></indexterm>
<para>The data superclass is called "GenericFile" since it acts on any file having the
defined format. Open any of the .txt data files (e.g. <computeroutput>PersonFile.txt</computeroutput>
to see the format. Essentially,
the first line in the file must be the column names (or labels). A label must not
contain spaces. Lines 2 to <emphasis role="italic">n</emphasis> are the data values,
each separated by a vertical bar character.
The main methods are:</para>
<itemizedlist>
<listitem>
<para><computeroutput>getRecord</computeroutput><indexterm>
<primary>getRecord method</primary>
</indexterm> - Invoked with a record key (e.g. a Customer Number) as its single
argument, reads the record from the file defined in the subclass, and returns a
"record directory".</para>
</listitem>
<listitem>
<para><computeroutput>getFile</computeroutput><indexterm>
<primary>getFile method</primary>
</indexterm> - Returns the file in "file as directory" format.</para>
</listitem>
<listitem>
<para><computeroutput>list</computeroutput><indexterm>
<primary>list method</primary>
</indexterm> - Lists the file on the console.</para>
</listitem>
</itemizedlist><para>See <xref linkend="chap07-cmpntdata-data"/> for a description of the
"record directory" and "file as directory" formats.</para>
</section> <!-- End of section 7.2.4.4 -->
</section>
<!-- End of Section 7.2.4 -->
</section>
<!-- End of Section 7.2 -->
<!-- *********************************************************************************************** -->
<section id="chap07-cmpntdata" xreflabel="Components and Data">
<title id="ch7-cmpntdata.title">Components and Data</title>
<!-- Section 7.3 -->
<section id="chap07-cmpntdata-kinds">
<title id="ch7-cmpntdata-kinds.title">Kinds of Component</title>
<!-- Section 7.3.1 -->
<indexterm>
<primary>Kind of component</primary>
</indexterm>
<indexterm>
<primary>Component</primary>
<secondary>Kinds of</secondary>
</indexterm>
<para>There are four "kinds" of components in Exercise07: "named", "singleton", "anonymous",
and "form". <itemizedlist>
<listitem>
<para>A "<emphasis role="bold">named</emphasis>" component instance is identified by a
unique name derived from the instance's data (analogous to a database key). An example
is <computeroutput>CustomerModel</computeroutput>, where each instance is identified
by its Customer Number. The external name for such an instance is of the form model
class name, model instance name - e.g. "CustomerModel AB0784". Note that a "Form"
component such as the Order Form is of the named component kind, since although it
starts out without a name, it is (and must be) given its name (such as an order
number) when first instantiated. This is done when the Object Manager invokes its
<computeroutput>getInstanceName</computeroutput> class method. A View component is
named by its object reference number (that is, the number returned by invoking
<computeroutput>identityHash</computeroutput> on the view instance). </para>
</listitem>
<listitem>
<para>"<emphasis role="bold">Singleton</emphasis>" instances are those for which there
can logically only be a single instance - for example, data components such as
<computeroutput>CustomerData</computeroutput>, or the Order Manager (which in
Exercise07 is a view-only component). Their instance name is always "The". </para>
</listitem>
<listitem>
<para>An "<emphasis role="bold">anonymous</emphasis>" component is one for which there
can logically be more than one instance, but which do not have any obvious
distinguishing name. Thus they are initially given the instance name "A". Examples are
list components such as <computeroutput>CustomerListModel</computeroutput>. Instances
of an anonymous component are provided with a system-generated number. For example,
the name of a Customer List Model is a unique number generated by its superclass,
starting at '1'. For example, to create an instance of
<computeroutput>CustomerListModel</computeroutput>, the message
<computeroutput>getComponentId("CustomerListModel","A")</computeroutput> is sent to
<computeroutput>ObjectMgr</computeroutput> which, on seeing instance name "a" or
"A", invokes <computeroutput>getInstanceName</computeroutput> on the ListModel class
object, which is handled by the <computeroutput>Model</computeroutput> superclass.
<computeroutput>Model</computeroutput> returns a number starting at "1".</para>
</listitem>
<listitem>
<para>A "<emphasis role="bold">form</emphasis>" instance such as a Sales Order Form or a
Purchase Request form is a special kind of component. Initially it is anonymous, and
although when created there is no database record of it, there will be after it's
completed and the user hits OK. A new number (e.g. an order number) is assigned to a
form when it is created, and this number is used as the database key when, after
completion, the form is committed to the database. For example, the Order Form
component assumes that this will happen, and so its instance name is a unique Sales
Order number. This is created by the <computeroutput>getInstanceName</computeroutput>
class method of <computeroutput>OrderFormModel</computeroutput> which over-rides the
same method in its superclass (<computeroutput>Model</computeroutput>).</para>
</listitem>
</itemizedlist>The above classification covers almost all the kinds of dialog found in a
typical UI environment handling business systems. To test this, consider the "Words of
Wisdom" business component in Exercise03, which was implemented as a view, a model, and a
data component. The <computeroutput>...Exercise07\Extras\Wow4</computeroutput> folder
contains the same set of components, but modified to use the MVF. Code no longer required is
commented out with the comment <computeroutput>v01-00</computeroutput>; methods or
statements added for MVF use are commented with <computeroutput>MVF</computeroutput>;
modified statements are commented <computeroutput>v01-00-->MVF</computeroutput>; and
unchanged statements are commented <computeroutput>v01-00 &amp; MVF</computeroutput>.<footnote>
<para>Actually, Wow is somewhat schizophrenic, in that it can be launched either as a
singleton or as anonymous - that is, with an instance name of "The" or "A". If "The",
then only one instance is allowed. If "A", then multiple instances can be
created.</para>
</footnote>As mentioned above, this whole approach of having component names define the type
of component is not particularly scalable. A better approach - certainly for
production-strength apps - is to provide a configuration file that names the classes and
states what type they are. Such a file might look something like this, and would remove the
need for using class names as the basis for managing instances: <programlisting><![CDATA[<modelClass name="Product", type="named", dataClass="ProductDB"/>
<modelClass name="NewOrder", type="form"/>
<modelClass name="CustomerList",type="anonymous"/>
<modelClass name="SalesOrder", type="named><viewClass name="MySpecialView"/></modelClass>
<modelClass name="Wow", type="singleton"
<viewClass name="WowView">
<dataClass name="WowData" source="sql">
</modelClass>]]></programlisting></para>
</section>
<!-- End of Section 7.3.1 -->
<section id="chap07-cmpntdata-data" xreflabel="GenericFile Data Formats">
<title id="ch7-cmpntdata-data.title">GenericFile Data Formats</title>
<!-- Section 7.3.2 -->
<para>Fields in a file record are separated by a vertical bar character, and field names are
defined in the first line of the file. All data files have the file extension ".txt".
<computeroutput>GenericFile</computeroutput>'s <computeroutput>getRecord</computeroutput> method
returns a single record (for example a
Customer record) in a "record directory", whose format is as follows:<programlisting><![CDATA[ "Record Directory" format (using sample data values):
Indexes Items
+- - - - - - - - - - - - - -+
| CustNo | BA0314 |
+- - - - - + - - - - - - - -+
| CustName | LMN & Partners |
+- - - - - + - - - - - - - -+
| Zip | 84394 |
+- - - - - + - - - - - - - -+
+ ... | ... |
+- - - - - - - - - - - - - -+]]> </programlisting> A complete file (for example the
Customer File retrieved and displayed by the CustomerList component) is returned by
<computeroutput>GenericFile</computeroutput>'s <computeroutput>getFile</computeroutput> method
in "File as Directory" format, as follows: <programlisting><![CDATA[ "File Directory" format (using sample data values):
Indexes Items
+- - - - - - - - - - - - - - - - - - - - - - - - +
|Headers | CustNo | CustName | .... | 1D array
|- - - - + - - - - + - - - - - - - - - - -+ - - -|
|Records | AB0784 | ABC Enterprises Inc. | .... | 2D array
| | - - - - + - - - - - - - - - - -+ - - -|
| | AC0027 | Frith Motors Inc. | .... |
| | - - - - + - - - - - - - - - - -+ - - -|
| | ... + ... + ... |
|- - - - + - - - - - - - - - - - - - - - -+ - - -+
|Count | n | Integer
+- - - - + - +]]> </programlisting></para>
<para>The data format for an Order is a combination of data from three files - OrderData,
CustomerData, and ProductData - and is described in <xref linkend="chap07-cmpntdata-compdata"/></para>
</section>
<!-- End of Section 7.3.2 -->
<section id="chap07-cmpntdata-compdata" xreflabel="Compound Data">
<title id="cmpntdata-cmpntdata-compdata.title">Compound Data</title> <!-- Section 7.3.3 -->
<para>"Compound data" is data assembled from two or more files. In Relational Data Base terms,
this is a "join". The one example of compound data in Exercise 7 is the
<computeroutput>OrderData</computeroutput> class, where the
<computeroutput>init</computeroutput> method uses the superclass's
<computeroutput>getFile</computeroutput> method to read each of the two Order files
(OrderHeader.txt and OrderDetail.txt). This data is stored in two attributes,
<computeroutput>dirOrderHeaders</computeroutput> and
<computeroutput>dirOrderLines</computeroutput>. Then the
<computeroutput>addCustomerInfo</computeroutput> method is called to get selected Customer
data (e.g. Customer Addresses) from the <computeroutput>CustomerData</computeroutput>
component, and this data is then added to the
<computeroutput>dirOrderHeaders</computeroutput> attribute. Finally, the
<computeroutput>addProductInfo</computeroutput> method accesses the
<computeroutput>ProductData</computeroutput> component to get selected Product data (e.g.
Product Names) which is added to the <computeroutput>dirOrderLines</computeroutput>
attribute.</para>
<para>The <computeroutput>dirOrderHeaders</computeroutput> format is
as follows:<programlisting><![CDATA[ Indexes Items
+---------------------------------------------------------------------+
|Headers|OrderNo|CustNo|Date |Disc|Cmtd|Cust |Cust|CustAddr | Zip | <a 1D array>
| | | | | | |Name |Disc| | |
|- - - -+- - - -+ - - -+ - - -+- - +- - +- - -+- - +- - - - -+ - - - -|
|Records|SO-1234|AB0784|120821| 2 | N |ABC..| B1 |2154 En..|FL 37043| <a 2D array>
+- - - -+- - - -+ - - -+ - - -+- - +- - +- - -+- - |- - - - -+- - - - |
|SO-2345|BA0314|110815| 1.5| Y |LMN..| C2 |116 Hig..|NV 84394|
|- - - -+ - - -+ - - -+- - +- - +- - -+- - |- - - - -+- - - - |
|SO-3456|BA0314|120527| 0 | Y |LMN..| C2 |116 Hig..+NV 84394|
|- - - -+- - - +- - - +- - +- - +- - -+- - +- - - - -|- - - - |
|SO-4567|CU0003|120630| 0 | Y |Red..| A1 |43 Main..|AR 48231|
|- - - -+ - - -+ - - -+- - +- - +- - -+- - +- - - - -+- - - - |
|SO-4569|AC0027|120824| 5 | N |Fri..| B1 |124 Fre..|TX 78254|
+-------------------------------------------------------------+]]></programlisting> And the
format of the <computeroutput>dirOrderLines</computeroutput> attribute is as follows:
<programlisting><![CDATA[ Indexes Items
+-------------------+
| Count | 12 |
|- - - - -+- - - - -+- - - - -+- - -+- - - - - - -+
| Headers | OrderNo | ProdNo | Qty | ProdName | <a 1D array>
|- - - - -+- - - - -+- - - - -+- - -+- - - - - - -|
| Records | SO-1234 | AB100/W | 5 | Baffle | <a 2D array>
+- - - - -+- - - - -+- - - - -+- - -+- - - - - - -|
| SO-1234 | CF300/X | 6 | Widget Box |
|- - - - -+- - - - -+- - -+- - - - - - -|
| SO-1234 | EF500/W | 15 | Slodget |
|- - - - -+- - - - -+- - -+- - - - - - -|
... ... ... ...
|- - - - -+- - - - -+- - -+- - - - - - -|
| SO-4569 | XY200 | 12 | Blad Anchor |
+- - - - -+- - - - -+- - -+- - - - - - -+]]></programlisting>Since the Order data is
held in two attributes of <computeroutput>OrderData</computeroutput>, the
<computeroutput>getRecord</computeroutput> and <computeroutput>getFile</computeroutput>
methods are over-ridden, and handled entirely by the
<computeroutput>OrderData</computeroutput> class. While
<computeroutput>getFile</computeroutput> is very simple - merely returning
<computeroutput>dirOrderHeaders</computeroutput> (which is sufficient for the Product
List component), the <computeroutput>getRecord</computeroutput> method needs to build the
data for a single Order from both <computeroutput>dirOrderHeaders</computeroutput> and
<computeroutput>dirOrderLines</computeroutput>. Thus it also over-rides its superclass
method, and builds a directory called "dirOrderRecord" whose format is as follows: <programlisting><![CDATA[ "dirOrderRecord" format (using data values from Order No SO-1234):
Indexes Items
+- - - - - - - - - - - - - - - - - - - - - - - - +
| OrderNo | SO-1234 |
+- - - - - - - -+- - - - - - - - - - - - - - - - +
| CustNo | AB0784 |
+- - - - - - - -+- - - - - - - - - - - - - - - - +
| CustName | ABC Enterprises Inc. |
+- - - - - - - -+- - - - - - - - - - - - - - - - +
| CustAddr | 2145 Engle Blvd,Hardtown,FL |
+- - - - - - - -+- - - - - - - - - - - - - - - - +
| Zip | 37043 |
+- - - - - - - -+- - - - - - - - - - - - - - - - +
| Cmtd | N |
+- - - - - - - -+- - - - - - - - - - - - - - - - +
| CustDisc | B1 |
+- - - - - - - -+- - - - - - - - - - - - - - - - +
| Disc | 2 |
+- - - - - - - -+- - - - - - - - - - - - - - - - +
| Date | 120821 |
+- - - - - - - -+- - - - - - - - - - - - - - - - +
| OrderLineHdrs | OrderNo ProdNo Qty ProdName | <a 1D array>
+- - - - - - - -+- - - - - - - - - - - - - - - - +
| OrderLines | SO-1234 AB100/W 5 Baffle | <a 2D array>
| | SO-1234 CF300/X 6 Widget Box |
| | SO-1234 EF500/W 15 Slodget |
+- - - - - - - - - - - - - - - - - - - - - - - - +]]></programlisting></para>
<para>Finally, a <computeroutput>listOrders</computeroutput> method is provided, since the
<computeroutput>list</computeroutput> method of
<computeroutput>GenericFile</computeroutput> cannot list data from more than one file. </para>
</section>
<!-- End of Section 7.3.3 -->
</section>
<!-- End of Section 7.3 -->
<!-- *********************************************************************************************** -->
<section id="chap07-msgsender" xreflabel="Message Sender">
<title id="ch7-msgsender.title">The Message Sender</title>
<!-- Section 7.4 -->
<para>The Message Sender <indexterm><primary>Message Sender</primary></indexterm>
is launched from the <emphasis role="bold">Help</emphasis> menu of the
<emphasis role="bold">Sales Order Management</emphasis> dialog and is used to "send messages
to" (aka "invoke methods on") components. It illustrates a useful kind of debugging aid, and
replaces the special component-specific "startup" scripts provided in Exercise 6. While a
useful aid, it is provided as merely a demonstration of the kind of debugging aid that can be
deployed when using a component-based architecture with a Model-View Framework. Thus it does
not pretend to be all-encompassing, and the results of sending some messages may be
unpredictable. In addition, its display of data returned is limited. For example, a "query"
message sent to a List component only displays the directory indexes and items as follows:
<programlisting><![CDATA[ RECORDS: an Array; COUNT: 5; HEADERS: an Array;]]> </programlisting></para>
<para>Some commonly-used target objects and messages are provided in the two combo boxes
<emphasis role="bold">Target</emphasis> and <emphasis role="bold">Method</emphasis>. For
repetitive testing of a given component, additional targets and messages can be temporarily
"stored" in the combo boxes, so saving in typing time. However, such additions to the combo
boxes are thrown away when the Message Sender is closed. </para>
<para>The "Data" section can be used to send data to a component. The data formats sent must be
either a string, an array, or a directory. To send an array, place a "|" (vertical bar)
character before each array element. To send a directory, use square brackets for indexes- for
example, [Name] Jim Brooks [Age] 34. </para>
<para>The Message Sender is located in the <computeroutput>Support</computeroutput> folder. It is
implemented as two ooRexx classes and a routine. The classes are a main dialog class and a separate
data-only class for visible strings. The routine sends
a message by constructing an instance of the ooRexx <computeroutput>Message</computeroutput>
class, then invoking its <computeroutput>send</computeroutput> method. Multiple copies of the
Message Sender can be launched concurrently. </para>
</section>
<!-- End of Section 7.4 -->
<!-- *********************************************************************************************** -->
<section id="chap07-ordermgr" xreflabel="Dialog Re-sizing">
<title id="ch7-ordermgr.title">Revisiting Re-sizing</title> <!-- Section 7.5 -->
<indexterm><primary>Re-sizing</primary><secondary>Dialogs</secondary></indexterm>
<indexterm><primary>Re-sizing</primary><secondary>Controls</secondary></indexterm>
<indexterm><primary>Control</primary><secondary>Re-sizing</secondary></indexterm>
<indexterm><primary>Dialog</primary><secondary>Re-sizing</secondary></indexterm>
<para>In Exercise 6, the Order Manager is a re-sizeable dialog, However, when re-sized, all controls
were also enlarged (or shrunk). Normally, to have all controls change size is not a requirement;
rather controls such as push buttons and edit fields should usually not change their size.
</para>
<para>In Exercise 7, only the "container" for the icons is required to re-size. Other controls should not change.
This is achieved by "pinning"
the other controls so that they do not move or expand/contract. For example, the "Reset Icons" button
is pinned to the left side and to the bottom side
of the dialog, so preventing the button from moving away from the bottom-left of the dialog. In addition,
to prevent the button changing its size, the top side of the button is pinned to the bottom side, and the
right side is pinned to the left side. Code to define such constraints must be placed in a
<computeroutput>defineSizing</computeroutput>
<indexterm><primary>defineSizing method</primary></indexterm>
<indexterm><primary>Methods</primary><secondary>defineSizing</secondary></indexterm>
method which is automatically invoked by ooDialog before the underlying dialog is created. If nothing is defined,
the default is to do nothing. Note that in this method, no other method that requires the underlying dialog
to exist can be invoked. Note also that this method must return <computeroutput>.false</computeroutput>.</para>
<para>Specifying how controls behave when the dialog is re-sized is done by invoking the
<computeroutput>controlSizing</computeroutput> method (a method of ooDialog's
<computeroutput>ResizingAdmin</computeroutput> class) on 'self'. As an example, the
following code defines the resizing behavior of the "Reset Icons" button on the Order
Management dialog:<programlisting><![CDATA[
::METHOD defineSizing
...
self~controlSizing(IDC_ORDMGR_RESET, -
.array~of('STATIONARY', 'LEFT' ), -
.array~of('STATIONARY', 'BOTTOM'), -
.array~of('MYLEFT', 'LEFT' ), -
.array~of('MYTOP', 'TOP' ) -
)]]></programlisting>
The <computeroutput>controlSizing</computeroutput>
method takes five arguments: a control's resource ID (e.g. the Reset button) and four arrays.
The first array addresses the left side of the control, the second the top, the third the
right, and the fourth the bottom. So: left, top, right, bottom. For each side there is an
array, and each array has three items. First the type of pin. Second the edge of the "other"
window (remembering that each dialog and each control is actually a "window"). Third the id of
the other window to which this control is pinned, the default being the resource id of the
main dialog (in our case the Order Management dialog.</para>
<para>So, taking the second parameter as an example, "STATIONARY" means that the left side of the Reset button
must not move away (or towards) the second parameter, i.e. the "LEFT" side of the dialog.</para>
<para>Consider the last parameter - the array 'MYTOP', 'TOP'. The keyword 'MYTOP' is a special keyword that
can only be used for the bottom edge of a control. It pins the bottom edge of the control to its top
edge. This has the effect of keeping the height of the control constant. Similarly, 'MYLEFT' pins the right side
of the control to the left side, so keeping the width of the control constant. Note that
in this case the second parameter
is ignored, although it must be a valid sizing parameter.</para>
<para>The result of all this is that the Reset button does not move from the bottom left corner of the dialog,
and its size remains constant.
</para>
<para>For further information about the ResizingAdmin class, see the ooDialog Reference. In addition, the folder
<computeroutput>Program Files\ooRexx\samples\oodialog\</computeroutput> contains examples of re-sizeable dialogs in
<computeroutput>resizableDialogs\ResizingAdmin</computeroutput>. Here, the program
<computeroutput>augmentedResize.rex</computeroutput> has copious and excellent comments on the various aspects
of re-sizing. Worth a read!</para>
</section> <!-- End of Section 7.5 -->
<!-- *********************************************************************************************** -->
<section id="chap07-orderform" xreflabel="Order Form">
<title id="ch7-orderform.title">The Order Form</title><!-- comment: Section 7.6 -->
<indexterm><primary>Order Form dialog</primary></indexterm>
<para>Open an Order Form from the icon in the Order Management dialog. Although still not
functional, the format of the dialog <emphasis role="italic">looks</emphasis> much more like a
usable sales order form than in the previous exercise. The main part of the form is a Tab
Control with two tabs - one for entering customer details, the other for product details.
<indexterm><primary>Tab Control</primary><secondary>Property Sheet</secondary></indexterm>
<indexterm><primary>Property Sheet</primary></indexterm>
<indexterm><primary>Property Sheet Page</primary></indexterm>
<indexterm><primary>Control Dialog</primary></indexterm>
<indexterm><primary>Tab Control</primary><secondary>Control Dialog</secondary></indexterm>
ooDialog supports two approaches to embedding content in a Tab Control: a
Property Sheet Dialog with a Property Sheet Page for each tab, and a Resource Dialog with a
Control Dialog for each tab.<footnote>
<para>By "resource dialog" is meant an RcDialog, a ResDialog or a UserDialog. The two former
have resources defined in a resource file, whereas the latter's resources are defined
programmatically. See the ooDialog Reference for a full description.</para></footnote>
<indexterm><primary>Control Dialog</primary></indexterm>One important difference is that
while the Property Sheet Dialog approach is
simpler (more is handled by the operating system), it does not allow for interesting controls
to be placed on the main dialog outside the Tab Control. Thus the alternate approach - Control
Dialogs - is used for the Order Form. </para>
<para>The instance name for the new Order Form is the Order Number. When the icon on the Order
Management dialog is double-clicked, the MVF is used to surface the OrderForm dialog. The
sequence of operation is as follows (with detail irrelevant to the Order Form omitted):</para>
<itemizedlist>
<listitem>
<para><computeroutput>OrderMgrView</computeroutput> sends
<computeroutput>showModel("OrderFormModel", "a")</computeroutput> to the Object
Manager.</para>
</listitem>
<listitem>
<para>The Object Manager sees that the instance name is "anonymous", and so sends
<computeroutput>getInstanceName</computeroutput> to the class object
(<computeroutput>.OrderFormModel</computeroutput> in this case).</para>
</listitem>
<listitem>
<para><computeroutput>OrderFormModel</computeroutput> provides this class method, and
returns the next Order Number.</para>
</listitem>
<listitem>
<para>The Object Manager uses the new Order Number as the instance name for the Order
Form, and...</para>
</listitem>
<listitem>
<para>... sends <computeroutput>newInstance(instanceName,...)</computeroutput> to
<computeroutput>.OrderFormModel</computeroutput>, which then instantiates an
<computeroutput>OrderModel</computeroutput> instance.</para>
</listitem>
<listitem>
<para>The Object Manager then sends <computeroutput>newInstance</computeroutput> to
<computeroutput>.OrderFormView</computeroutput>, which then creates an instance of the
<computeroutput>OrderFormView</computeroutput> dialog.</para>
</listitem>
</itemizedlist>
<para>The Order Form consists of three dialogs - the main Resource File Dialog (an <computeroutput>RcDialog</computeroutput>)
plus two Control Dialogs (<computeroutput>RcControlDialog</computeroutput>) in a Tab Control. The two Control Dialogs -
one for Customer Details and one for details of Products ordered, are
created in the <computeroutput>activate</computeroutput> method as follows:
<programlisting><![CDATA[ cd1 = .CustDetailsDlg~new("Order\OrderFormView.rc", IDD_ORDFORM_CUST_DIALOG)
cd2 = .OrderLinesDlg~new("Order\OrderFormView.rc", IDD_ORDFORM_ORDLINES_DIALOG)
tabContent = .array~of(cd1, cd2)
cd1~ownerDialog = self
self~prep(tabContent)
]]></programlisting>The <computeroutput>prep</computeroutput> method is then called to set up the tabs:
<programlisting><![CDATA[ ::METHOD prep
expose tabContent lastSelected havePositioned
use strict arg tabContent
havePositioned = .array~of(.false, .false)
lastSelected = 0
self~connectTabEvent(IDC_ORDFORM_TABS, SELCHANGE, onNewTab)
]]></programlisting>The 'havePositioned' array is used to determine if the page dialogs have been
positioned, and both are marked as not positioned. Then "no tab yet selected" is set. Finally, an
event handling method is connected to the event <computeroutput>onNewTab</computeroutput>
(which is invoked when the user clicks on a tab).</para>
<para>Next, in the <computeroutput>initDialog</computeroutput> method, the tabs are added to the tab control,
their size is calculated, and the control dialog used for the first page is positioned and
shown:
<programlisting><![CDATA[ ::METHOD initDialog
expose ... tabContent lastSelected ...
...
tabControl = self~newTab(IDC_ORDFORM_TABS)
tabControl~addSequence("Customer Details", "Order Lines")
...
self~calculateDisplayArea
self~positionAndShow(1)
]]></programlisting>
</para>
<para>The two methods <computeroutput>calculateDisplayArea</computeroutput> and <computeroutput>positionAndShow</computeroutput>
are well-commented, and are copied from the ooDialog sample program <computeroutput>oodListViews.rex</computeroutput>
- one of the excellent
samples in the <computeroutput>samples\oodialog\propertySheet.tabControls</computeroutput> folder, which is in the
<computeroutput>Program Files\ooRexx</computeroutput> folder.
</para>
<para>
Note that it's essential to properly close the control dialogs in the <computeroutput>cancel</computeroutput> and/or
<computeroutput>ok</computeroutput> methods. This must be done using the <computeroutput>endExecution</computeroutput>
method.</para>
<para>Finally, the Order Form illustrates one use of the <computeroutput>DateTimePicker</computeroutput>
control.
<indexterm><primary>DateTime class</primary></indexterm>
<indexterm><primary>DateTimePicker</primary></indexterm>
<indexterm><primary>OrderForm</primary><secondary>DateTime class</secondary></indexterm>
<indexterm><primary>OrderForm</primary><secondary>DateTimePicker</secondary></indexterm>
This is a very fully-featured control, providing many ways of displaying and manipulating date and time.
For the Order Form it allows the user easily to specify the Order Date. It's partly configured in the
.rc file (in ResEdit, <emphasis role="bold">Use Spin Control</emphasis> is set to <emphasis role="bold">false</emphasis>
and <emphasis role="bold">Format</emphasis> is set to <emphasis role="bold">Short Date</emphasis>), and partly in code,
as follows:
<programlisting><![CDATA[ ::METHOD initDialog
...
orderDate = self~newDateTimePicker(IDC_ORDFORM_DATE);
orderDate~setFormat("MMM dd',' yyyy")
today = .DateTime~today
maxOrderDate = today~addYears(1)
orderDate~setRange(.array~of(today,maxOrderDate))]]></programlisting>
First the format of the edit field is set. Then the ooRexx <computeroutput>DateTime</computeroutput>
class is used to set the allowable range of dates that the user can enter: the range is from "today" (i.e. the day on
which the dialog is used) to a year from "today".
</para>
</section>
<!-- End of Section 7.6 -->
<!-- *********************************************************************************************** -->
<section id="chap07-next">
<title id="ch7-next">Completing the Application</title>
<!-- Section 7.7 -->
<para>At this point, there is more to do to complete the application. For example, generic function
such as that found in the List components could be moved to a superclass; the three view superclasses could be
made into a single mixin class; the Order Form needs to provide for data to be entered and stored;
SQL could be used for data-on-disk; updates could properly implemented, and it would be nice if the user
could use drag-and-drop to enter data into the Order Form. It is planned that some or all of these functions
will be addressed in the next version of this Guide.</para>
</section>
<!-- End of Section 7.7 -->
</chapter>