![]() |
Telelogic DOORS (steve huntington) | ![]() |
new topic :
profile :
search :
help :
dashboard :
calendar :
home
|
||
Latest News:
|
|
Topic Title: General programming strategies for building complex dialog boxes ? Topic Summary: Created On: 29-Jan-2008 19:08 Status: Post and Reply |
Linear : Threading : Single : Branch |
![]() |
![]()
|
![]() |
|
I have been working on a very large, complex dialog box, and I continue to struggle with making the DXL code manageable, organized, and error free. I am hoping some of you have built similar complex dialog boxes, and may be able to offer some general guidelines or "lessons learned".
The DB I am building is less of a dialog box, and more of a user interface. It has a main tab strip, and each tab contains dozens of DBEs, including treeViews, listViews, canvases, sub-tab strips, spawned child DBs, etc. As this "dialog box" gets more and more complex, I run into a some general "challenges" with managing the DXL code. I have built quite a large library of functions. And, I use a large Skip list to manage the showing and hiding of DBEs on the tabs. But, here are some of the gerenal issues I struggle with: 1.) The tendency towards global variables I've tried to organize my code by defining functions that will build parts of the dialog box. However, as I define DBEs within functions, I run into cases where the only solution is to declare global variables. One type of issue is that for every DBE that requires a string array in its definition (choice, tab, list, multiList, listView, etc.) and which is defined within a function, the string arrays must be defined globally. Another issue is any DBE that needs to receive or transfer data with another DBE. If the DBEs are defined locally in different functions, they can't talk to each other. One solution is to declare many of the DBEs globally, but this defeats the purpose of functions, and makes it hard to keep the code organized. Another solution I've though of is to build a Skip list, load the DBEs into the skip list, and have some lookup scheme to access the data in each DBE. Maybe someone has some thoughts on how best to manage this. 2.) The organization of all the constrained placement The only way to organize all the DBEs in the DB is through constrained placement. With so many DBEs, the contrained placement gets voluminous. Has anyone built some clever functions that simplify all the placement code? 3.) DBE definition order, placement, and parent/child DBEs DBEs must be placed in the order in which they are defined. Yet, when defining parent/child relationships (with setParent), the child DBE must be declared before the parent DBE. Thus, when you combine these two DXL requirements, you are pretty much forced to place child DBEs before parent DBEs, and maybe that's not how you want things placed in the dialog box. 4.) Functions within functions and limitations of local variables Local variables are not accessable by local functions. In many cases, this can be worked around by passing local variables into local functions. However, this is hard to accomplish with callbacks, since callbacks have pre-defined syntax. For example, if you are building a whole frame of DBEs within a function, and you want some button click to populate data into a field DBE, you can only access the button DBE in the button callback. One solution is to use setParent, make the field the parent of the button, then use getParent within the button callback. But now if I want a button click to do something to a dozen fields, I have to use a lot of setParent and getParent functions. Is there a better way? In summary, I am trying to break-up the code for a large DB into manageable functions. However, some of the issues above make this challenging. Does anyone have some general helpful hints? |
|
![]() |
|
![]() |
|
I know what you mean. I've done one tool that has over 60 dialog box elements. You can break the program into different files that using includes. But yeah the trick is making sure everything you need is in scope.
Have a file with all your globals and make that your top level include. Have a utilities file next (non-DBE specific functions). Then break down your different tabs into different files. Last have your file where you define the dialog box. Yes constrained placement can get tedious and huge. Unfortunately its the only way. You could overload some of the standard DXL shortcuts like below(DBE d) with your own but you'll have to have those statements. And you'd have to have a bunch of functions defined to account for every possibility. I've just kind of gotten used to it and can knock out a fair sized DB fairly quickly with constrained placement. ------------------------- David Pechacek AAI Services Textron dpechacek@sc-aaicorp.com David.Pechacek@gmail.com |
|
![]() |
|
![]() |
|
Thanks Dave. It's nice to know someone else has had the same struggles. I have pretty much done what you've suggested.
The first line in the script is an include of a library of files (#include "libAll.dxl"). libAll.dxl then includes 35 other files. The first of that 35 is a list of global variables (libGlobals.dxl). The other 34 files are lists of functions that I have built. I have broken my functions into related groups, and saved each group to a separate file (things like: libMath.dxl, libText.dxl, libDBE.dxl, you get the idea). Those 34 files are included in order, from lowest-level to highest-level, so that higher level functions can use lower level functions. I have 65 DBEs and growing. One thing I've done is to group my DBEs into frames. Then I have functions that build each frame of DBEs. In this way, DBEs within the same frame are defined within the same function, and can generally talk to each other (with a little creativity). Then I can build up the tabs by "including" some number of frames, which are simply calls to those functions. This has also allowed me to somewhat break up the placement code. At a high level, I can just place the frames, with the placement of the DBEs done within the functions for the frames. I still struggle with a few things. In this method, DBEs are not global, but local to each frame. Thus, if I want one DBE on one tab to be able to talk to a DBE on another tab, I have a problem. I've also struggled with the initialization of data in listViews and treeViews. You can only populate data after the DB is realized. But, in my method, none of the DBEs are global, so you can't directly refer to them once the DB is realized. My way around this was to build a skip list of initialization functions. So, I have a master skip list to manage the main tab strip, with the tab name as the key and a skip list for each element. Those sub-skip lists then have DBEs as the key, and functions as the elements. This allows me to not only manage the showing and hiding of DBEs, but also allows me to associate a function with each DBE, which I use to initialize data. So, after the DB is realized, I can cycle through the skip list and call an initialization function for each DBE. Before trying to define DBEs in functions, I had just a separate include file for each tab of DBEs. This worked OK, and sometimes I think I should go back to that. All DBEs were global and everything was easily accessible. However, the code started to get unmanageable, as I had so many DBEs, placement, callbacks, etc. Which reminds me.... in this method, if you want one DBE #1 to be able to talk to DBE #65, you need to put the callbacks near the end of all your code; you can't put the callbacks in the include files which define each tab. Before you know it, you have a bunch of include files which use a bunch of global varaiables all over the place, and it gets difficult to keep track of everything. Anyway, thanks for the input. If you, or anyone else, has any more thoughts, it would be great. Thanks. EDIT: As I contemplate this more... in summary, one of the strategies to be determined is, in general, should DBEs be defined globally, or is it better to define them within functions? Either way has its pros and cons. Global DBEs make it easy for them all to communicate with each other, but it leads to spagetti code. Local DBEs (within functions) allows for somewhat cleaner code, but makes communication between DBEs challenging. Edited: 29-Jan-2008 at 23:01 by David Jakad |
|
![]() |
|
![]() |
|
I'm working on a large dialog box myself. Here are some of the strategies I've chosen to go with:
1) The tendency toward global variables. DxlObjects are my savior here. I have an entire heirarchy of DxlObjects of DxlObjects of DBEs. You could do it with skip lists, too. Skip lists can be iterated over, but DxlObjects don't blow up at you as much or require as much care and feeding. I actually have a suite of functions that operate on DxlObjects and turn them into stacks, which is my preferred solution. Regaurding global arrays, I used to declare string moduleNames[30] and populate it before putting it in my choice DBE. Nowadays my preference is to create the choice DBE with an empty array, construct a DxlObject indexed by "1", "2", . . ., along with a "max" value for the real values, and update the DBE to its real values with a locally declared array at my whim and convenience. Since DxlObjects are allocated on the heap, not the stack, it's safe to allocate them from subfunctions. This also gives more flexibility in terms of the size of the array, and changing it after the fact. The DxlObject still has to be accessible globally--but I usually access it via other DxlObjects. One disadvantage of using DxlObjects: you do have to do memory management yourself. 2) Constrained placement. The constrained placement syntax is *miserable*. I ended up creating a suite of functions for managing it, following Tk philosophy of "specify things with lots of strings". My syntax takes "l" => "left", "r" => "right", "f" => "flush", and so forth, and accepts a series of DBEs to be processed as targets in order. It took about half a day and a hundred lines of DXL, but now instead of writing list_view->"left"->"spaced"->button_1 list_view->"top"->"form" list_view->"bottom"->"unattached" list_view->"right"->"unattached" I write attach(list_view, "lstfburu", button_1) WELL worth the effort, and much more maintainable. I swear by my #include attach.dxl file. I don't think I'm allowed to publish my code, but I highly recommend doing something similar. It's not very hard to do. 3) Child/parent relationships. Can't say I've used this, so I can't comment here. 4) Limitations of local variables. If I want a button to update a few fields, I don't use child/parent relationships or strings; I just hard-code the button's callback to do the right thing. I use a hierarchy of DxlObjects that eventually caches references to all the DBEs, so it's pretty easy to get directly at the fields I want. --------------- Some other comments: - I've been playing around with attaching a callback to listview column headings. Can't be done, I know. What I did was turn on the sort function, set the sort_function to cache the column that was clicked, execute my callback, and then disable itself until a different column is clicked (or a full second passes). I love this--column headings on list views are nice. I just wish I could make a right-click menu. - I've been having trouble getting listviews to sort all the way (unrelated to the above comment--I promise!). My current strategy is to use an ordered DxlObject of rows (indexed by "1", "2", "3"...) of DxlObjects of columns (sometimes indexed by number, other times by column title). I use the column-click callback to sort this whole mess, and then simply re-show the listview. One big advantage of this is MASSIVE flexibility in terms of how things sort, changing display values, and so forth. I highly recommend it. - I hate that callbacks have to be declared before the DBE using them. In some instances, this just isn't feasible. My current solution is to use dummy callbacks: they do nothing but call the real function. DOORS seems to be okay with this. Mostly. |
|
![]() |
|
![]() |
|
Catherine... WOW! Thanks!!!
You gave me a lot of good info there. DXLObjects is not something I've tinkered with yet. I've seen it mentioned elsewhere, and based on your suggestions, I will look into it. I also don't like that you have to declare callbacks before the DBEs. Like you, I've created a bunch of "do nothing" functions, to be used as dummy functions that you can use when you need to declare the DBE. Then, usually closer to the end of the code, is where I define all the real callbacks. Afterall, usually you want the callbacks to interact with other DBEs, and thus you need to declare all the DBEs first, before defining the callback functions. Another thing I don't like is that most built in callbacks have a hard-coded syntax. For example, the callback for a button must be in the form "void fnButtonClick(DBE xDBE)". And the xDBE that is passed in is the button clicked. But, it seems in most cases, you'd want to pass other info to the callback. I wish you could pass other parameters to the callback. Another topic I am not that familiar with is the overloading of functions. I'm not sure how overloading really helps with much. But, maybe someone can explain some good uses for it. On the sorting of listViews, I too have found cases where the listView doesn't sort all the way. My observation has been, if the listView only has one column, for some reason the sort function doesn't complete through all the rows. But, if I have at least 2 columns, even if the second column is empty, the sort functions work just fine. Odd. As for right-click menus, there is an undocumented function: getRightClicked(DBE). I believe it only works with treeViews, though. You could test to see if it works with listViews or other DBEs. This is a good discussion. Thanks for the input. And I welcome more comments, thoughts, and ideas. Thanks again! Edited: 30-Jan-2008 at 16:23 by David Jakad |
|
![]() |
|
![]() |
|
I agree that the hard-coded callback syntax is a pain. I often find myself wishing for CGI's "hidden fields". I have a lot of code that looks like this:
void real_callback(string s1, DBE d1, DBE d2) { // Do Stuff } void fake_callback_cow(DBE unused) { real_callback("Cow", d1, d2) } void fake_callback_frog(DBE unused) { real_callback("Frog", d1, d3) } button(theBox, "Cow", fake_callback_cow) button(theBox, "Frog", fake_callback_frog) Or like this: string sort_mode = "by module type" void some_callback(DBE unused) { if(sort_mode == . . .) } void some_other_callback(DBE unused) { sort_mode = "by name" } On the sorting of listviews, I'm not quite sure where it's going wrong. I actually wrote a sort function that printed out all of the intermediate states of a listview being sorted, and it definitely doesn't always finish the job. It appears to sort groups of 5 and 10 elements in a merge sort that has been optimized to the point where it's broken. I can get it to sort eventually--you just have to click the column header a half dozen times. I don't know how to get it to behave, so I got all superstitious about it. I sort my own darn listviews. Overloading functions is awesome, though. I use it all the time, for stuff like this: string get_relative_full_name(Item i, Project p) { // Complex code for getting fullname of i starting at non-immediate parent p } string get_relative_full_name(Module m, Project p) { get_relative_full_name(item(fullName(m)), p) } string get_relative_full_name(Project p1, Project p) { get_relative_full_name(item(fullName(p1)), p) } string get_relative_full_name(Folder f, Project p) { get_relative_full_name(item(fullName(f)), p) } It's also good for setting default parameters. void do_something_interesting(bool with_pride, bool with_gusto, bool with_cunning) { } void do_something_interesting() { do_something_interesting(true, false, true) } Edited: 30-Jan-2008 at 22:26 by Catherine Darrow |
|
![]() |
|
![]() |
|
I want to thank Catherine and David for their thoughts and insights on this topic. With their hints, I've come up with some good solutions for my issues.
Master Skip List of DxlObjects to Manage DBEs I now use one large Skip list to manage all the DBEs in the dialog box. But, instead of storing the DBEs directly into the Skip list, I store DxlObjects in the Skip List, and store the DBEs in the DxlObjects. Conceptually, my Skip list is something like this: Master Skip List (with integer as key) Key Contents 1 DxlObject1 2 DxlObject2 3 DxlObject3 ... ....etc. where the DxlObjects are like: DxlObject1->"ID" = string "DBE # 1" // any id you want DxlObject1->"Type" = string "button" // the type of DBE as a string DxlObject1->"Tab" = string "Tab # 1" // the name of the tab you want the DBE on DxlObject1->""DBE" = DBE theDBE // store the actual DBE here DxlObject2->"ID" = string "DBE # 2" // any id you want DxlObject2->"Type" = string "choice" // the type of DBE as a string DxlObject2->"Tab" = string "Tab # 2" // the name of the tab you want the DBE on DxlObject2->""DBE" = DBE theDBE // store the actual DBE here Then, I have a number of functions that can work with this Skip List/DxlObject architecture. I can register a new DBE into the Skip List. I can quickly lookup and return any DBE. I can quickly show/hide DBEs for any tab. I can check the type of DBE, when needed. Etc., etc. This also allows me to build functions which construct parts of the dialog box, and my DBEs don't need to be global.... just the Skip list needs to be global. I also have functions which add functions/callbacks to buttons, listViews, etc., by looking them up in the Skip List and associating the callback with the DBE. Overall, this is making the code much more manageable. Simplified Placement Function I have created by own simplified DBE placement functions, although I've taken a slightly different approach. I find that, while there may seem to be an infinite number of permutations, there are only a handful that you normally use. So, I have something like this: posDBE(myLabel,"inside",myFrame) posDBE(myChoice,"below",myLabel) posDBE(myTextBox,"right",myChoice) where posDBE is something like (but with more options): if(xPos=="inside") { xDBE1->"top"->"inside"->xDBE2 xDBE1->"left"->"inside"->xDBE2 xDBE1->"right"->"unattached" xDBE1->"bottom"->"unattached" } elseif(xPos=="below") { xDBE1->"top"->"flush"->xDBE2 xDBE1->"left"->"aligned"->xDBE2 xDBE1->"right"->"unattached" xDBE1->"bottom"->"unattached" } elseif(xPos=="right") { xDBE1->"top"->"aligned"->xDBE2 xDBE1->"left"->"flush"->xDBE2 xDBE1->"right"->"unattached" xDBE1->"bottom"->"unattached" } } This greatly simplifies the placement code and makes it much more intuitive. Although, as was mentioned in another thread, the constrainted placement doesn't always act the way you'd expect... it doesn't place all DBEs in the same way and there are odd differences if the DBE has a label or not (such as choice, text, richText). Generalized Function to Add DBEs to Dialog Box As was hinted to by others, I first build the dialog box with empty data and no functionality, then I add the functions/callbacks, then I initialize/add the data to the DBEs. Along these lines, I've built a generic function to add DBEs to the dialog box. So rather than have.... frame(DB,"This is my frame,200,300) field(DB,"This is my field","no data yet",20,false) choice(DB,"This is my choice,choices,1,0,16,false) I have: DBE dbe01 = addDBE("DBE # 1","frame","This is my frame",200,300,"inside",xTab,"Tab #1") DBE dbe02 = addDBE("DBE # 2","field","This is my field",20,0,"inside",dbe01,"Tab #1") DBE dbe03 = addDBE("DBE # 3","choice","This is my choice",16,0,"below",dbe02,"Tab #1) where addDBE is: DBE thisDBE if(xType=="choice") { thisDBE = choice(xDB,xLabel,NullArray,1,0,xWidth,false) } elseif(xType=="label") { thisDBE = label(xDB,xLabel) } elseif(xType=="field") { thisDBE = field(xDB,xLabel,NullString,xWidth,false) } elseif(xType=="frame") { thisDBE = frame(xDB,xLabel,xWidth,xHeight) } posDBE(thisDBE,xPos,xDBE) // using simplified placement function from above fnRegisterDBE(thisDBE,xID,xType,xTabName) // Register DBE as DxlObject in Skip List return thisDBE } This greatly simplifies the adding of DBEs My dialog box now has over 80 DBEs and growing. With these concepts, it is much easier to create, add, edit, change features in the dialog box and keep the code manageable. No more spaghetti code. Thanks again for the ideas. |
|
![]() |
FuseTalk Standard Edition v3.2 - © 1999-2009 FuseTalk Inc. All rights reserved.