2 Attachment(s)
Main form too large - how to split up?
Hey
First, some background info.
My program consists of several forms, of which the main one now has around 2300 controls used in over 200 frames. There are about 15 main "sections" in the program - each section has its own root frame which contains up to 30 other frames. They work like tabs, but instead of a tab-strip the user navigates via a main menu and via buttons in these frames. The UI part alone of the MainForm.frm file is 2MB in size.
Editing the main form in the VB6 IDE editor used to be fast, but over the years as the program grew is has become slow. Opening the main form in the VB6 IDE editor used to take about 5 seconds when I used Microsoft Windows Common Controls(-2) 6.0. After switching over to VBCCR16 a week ago, it now takes about 22 seconds to show, and about 10 seconds to close the main form in the IDE. Saving also takes a few seconds. It was bad before, and it's worse now.
Apart from the main form, there are several other much smaller forms (in terms of file size and control count), and they open, close and save instantly. Would be nice if the main form was like that.
The main form is designed much like a website - it has a header frame at the top, a menu frame on the left, and the sections open in the large space that's left.
Attachment 173799
The "Home View" in this screenshot is one such section. Another example shows the "Finances" section:
Attachment 173801
Splitting the main form up into smaller forms (in terms of file size and control count) makes sense not only from for performance reasons, but also from a logical perspective. I have already isolated each section as much as possible - each section's controls are prefixed with that section's abbreviated title, e.g. the finances section uses FinFrame, FinTextBox, FinListView, etc., and each of these is an indexed control, so there could be FinFrame(1) up to FinFrame(25), FinTextBox(300), etc. So there is only one FinTextBox in my whole program, but it's an array with hundreds of indexed whatever-you-call-them - instances? The same for the "Home" section, for the "Pharmacy", etc.
Now on to the questions.
How can I keep the menu and header layout, but have each section as a separate form, without having to re-implement the menu and header in every form? If I make a change in the menu or header, the change should apply to all forms - I don't want to have to edit 15 or more forms every time I make a change in the menu or header.
Is it possible to embed one form within another?
Is there a different solution, to break up the MainForm.frm into multiple smaller forms?
Re: Main form too large - how to split up?
Well, the standard way of embed forms within a parent form is to set the main form to be a MDI form, and then set the MDIChild property of the other forms that you want to embed in that form to True.
You would probably align a picturebox at the top as a container for the permanent controls that go in that area, and another one aligned to the left (and change its width to what you want), to host the controls that are over there. The rest of the area can be open and used to display the various MDI child forms you wish to display.
Re: Main form too large - how to split up?
You might also want to consider UserControls - each of your inner frame areas could be it's own UserControl and you could dynamically load/unload them as needed.
Re: Main form too large - how to split up?
Search the forum or the web for "vb6 docking windows".
Re: Main form too large - how to split up?
You can do it with either UserControls or Forms (or MDI forms, but that's a totally different style). UserControls are a bit more of a native VB solution - with child forms you need to use the SetParent and MoveWindow APIs. I would start with multiple UserControls set up on the form in design mode and just hide/show them as needed - if that is still too painful in the IDE, then you can look at dynamically loading them on startup, or waiting until the corresponding menu option is selected to load a particular sub-screen.
Re: Main form too large - how to split up?
Hi OldClock,
Several possibilities come to mind to me. I hesitate to suggest this, but the MDI approach does look like it has possibilities here. Possibly just have an MDI form as a "shell", and then header and left-side-bar MDI-child forms that are "fixed" to those specific areas.
Then you might have a separate MDI-child form for each of the options in your side-bar form. Again, you could "fix" the size of these forms, possibly resizing them if/when the parent MDI form resizes.
The MDI approach has the advantage that you would still drag-around and minimize the whole application with the MDI-parent form's controls.
----------
Another option (one that I personally would strongly consider), is to just take several forms and "glue" them together (no MDI). Basically, you'd be building several forms that "looked like" a single form. Again, I'd split those side-bar options into separate forms.
Using this approach, you'd have to have the top form control all the others when moving, maximizing, minimizing, etc. Resizing would also be a bit of a challenge.
----------
Yet a third option is to possibly have separate forms for things in the IDE. However, during runtime, when you click different options on the left-side-bar, you could change the parent of all the controls for the "view" you wanted to see, shifting the controls to a "shell form" that was the view you wanted. I've done this before and it works fine, but it's a bit strange. Others may chime in and discourage you from this approach, and I won't make a strong counter-argument. It's just an idea I had.
----------
Personally, I don't know how you continue to develop this thing. How do "view" all the different "looks" while in the IDE?
Also, I'm guessing you may have a substantial amount of code in this massive form. Whichever approach you take, you'll probably have to move much of that code to a BAS module, and make many things Public, so that all your different forms see it the same way.
--
Good Luck,
Elroy
1 Attachment(s)
Re: Main form too large - how to split up?
I like to use UserControls for this. It makes it easier to group the logic and controls needed for a given pane of information, you can make splitter bars, tabbed areas, etc:
Attachment 173857
Re: Main form too large - how to split up?
Thank you everyone for your suggestions, I'm considering all of them.
Quote:
take several forms and "glue" them together (no MDI)
How would you glue them together?
Quote:
How do "view" all the different "looks" while in the IDE? (...) Also, I'm guessing you may have a substantial amount of code in this massive form. Whichever approach you take, you'll probably have to move much of that code to a BAS module, and make many things Public, so that all your different forms see it the same way.
The MainForm contains one uniquely named and indexed frame per "section". Each frame at index 0 is the section container, and all the higher-numbered frames of the same section are children of the section container. So, for example looking at the pharmacy section, PharmaFrame(0) is the container, then PharmaFrame(1) could be the "drug manager" and is a child of PharmaFrame(0), PharmaFrame(2) could be some stock manager and also a child of PharmaFrame(0), etc. The same approach for other sections, e.g. AdminFrame(0) is the container, and it has AdminFrame(1)...AdminFrame(42).
To view a section in the IDE, e.g. the Pharmacy, I just find and select PharmaFrame(0) in the Properties Window (F4), then the control box nodes appear in the form even if that frame is covered by other frames and therefore not visible, so I just right-click a node and "Bring To Front". I have a constant for each indexed control in my program, the constant is structured as so: SECTION_CONTROLTYPE_SUBFRAME_DESCR. So, for example, the main pharmacy container frame is PHARMA_FRAME_MAIN = 0, the frame where you can manage drugs is PHARMA_FRAME_DRUGMAN = 1, and a listview which shows drug batches within that drug manager frame is PHARMA_LV_DRUGMAN_BATCHES. To quickly find the right PharmaFrame out of a total of 30 pharmacy frames, I can either look at the frame captions in the Properties Window as I describe the frame in the caption, or I can look at the constants to find the index of the frame that I want to view.
Each control's event is obviously caught in the MainForm since it has to, but all MainForm does is forward that event to the relevant module. So e.g. Private Sub PharmaBtn_Click(Index As Integer) just passes the event on: Call PharmacyModule.btnClicked(Index). This keeps code in the MainForm to a minimum.
This approach has the following benefits:
- VB6 has a limit of 255 uniquely named controls per form. Using indexed controls means I can have way more than 255 controls. Each section uses only about 5 to 10 uniquely named indexed controls, e.g. PharmaFrame(), PharmaTextBox(), PharmaButton(), PharmaListView(), etc.
- Controls for each section are clearly distinct and isolated, allowing me to easily do things to one section without affecting any other section.
- It allows for teamwork on the same form, to a certain degree. I could have a friend working concurrently on the same form but on a different section. Let's say I'm working on the Pharmacy and the friend is working on the Admin section, and we both need to make changes to the GUI. When it comes time to merge the code, it's just a matter of firing up two instances of the IDE, one with my branch and one with his, deleting AdminFrame(0) from my instance, copying AdminFrame(0) from his instance and pasting it into mine (yes, VB6 allows you to copy-paste across two IDE windows). Since AdminFrame(0) contains not only all other AdminFrames but also all AdminTextBox, AdminListView, etc., then when copying it over everything remains intact, the index numbers don't change, so the code still works. Well, it's a stupid workaround to the problem of having more than one person work on a form at the same time, but it works very well. Though I'm trying to solve this more properly by breaking the main form up into smaller forms.
- It allows any new coder to jump in and find their way around the program quickly, no need to remember what TextBox63(123) is.
Re: Main form too large - how to split up?
Quote:
Originally Posted by
OldClock
...
This approach has the following benefits:
- VB6 has a limit of 255 uniquely named controls per form. Using indexed controls means I can have way more than 255 controls. Each section uses only about 5 to 10 uniquely named indexed controls, e.g. PharmaFrame(), PharmaTextBox(), PharmaButton(), PharmaListView(), etc.
- Controls for each section are clearly distinct and isolated, allowing me to easily do things to one section without affecting any other section.
- It allows for teamwork on the same form, to a certain degree. I could have a friend working concurrently on the same form but on a different section. Let's say I'm working on the Pharmacy and the friend is working on the Admin section, and we both need to make changes to the GUI. When it comes time to merge the code, it's just a matter of firing up two instances of the IDE, one with my branch and one with his, deleting AdminFrame(0) from my instance, copying AdminFrame(0) from his instance and pasting it into mine (yes, VB6 allows you to copy-paste across two IDE windows). Since AdminFrame(0) contains not only all other AdminFrames but also all AdminTextBox, AdminListView, etc., then when copying it over everything remains intact, the index numbers don't change, so the code still works. Well, it's a stupid workaround to the problem of having more than one person work on a form at the same time, but it works very well. Though I'm trying to solve this more properly by breaking the main form up into smaller forms.
- It allows any new coder to jump in and find their way around the program quickly, no need to remember what TextBox63(123) is.
For all of those reasons, I'd probably go with the UserControl option. I would put each section, along with its subsections into a project.... and then the main form would then dynamically load the appropriate control when it needs to, and drops it onto the form. A couple lifetimes ago, that's how we built an entire CRM system. Each screen was an OCX, with an accompanying entry in the database. The listing was used to build the navigaiton menu, and when a screen was needed, looked up the classid, instanciated it, dropped it onto a form, and showed the form. When we needed a new screen, we'd send out the OCX along with a script to add it to the table... and bam! new feature added w/o needing to rebiuld the entire application. We could disable features the same way, by removing it from the database. You may or may not want to go quite that far with it, but the concept is the same.
-tg
Re: Main form too large - how to split up?
I'm experimenting (for the first time) with UserControl, and I like it.
MDI vs UserControl for this purpose, what are the advantages of one over the other?
Re: Main form too large - how to split up?
Probably preference more than anything else. One advantage of a form over a user control is that you can just create the form, whereas with the UC, you need to create it and then put it on a form. You could combinet he two and put the UC on a form at design time, and then your app would just create the forms as needed... so IMHO it comes down preference.
-tg
Re: Main form too large - how to split up?
I don't remember why, but I did find that forms worked better for me than user controls if you are dividing your application into separate activex Dlls instead of one big exe. If all of the screens are in one project, I'd stick with user controls.
Re: Main form too large - how to split up?
Quote:
Originally Posted by
OldClock
How would you glue them together?
It sounds like you're going another direction, so I won't go into great detail ... but, I'd just have a form across the top that actually had a titlebar. Then, all the rest of my forms would be borderless.
Then, when you drug the top-titlebar form around, it automatically drug the other forms around too, to their correct (sort of tetris) positions. It would take a bit of math to figure it all out, but not much. With a touch of creativity, you could also work out resizing, and splitter-bars as well, if you needed those.
IDK, that'd probably be my favored approach.
I don't see my way through how to do it with User Controls (UCs). Well, I see it, but not without a great deal of work. It was just brought up in another thread that it's not trivial to "get at" the child controls on a UC. You've got to create UC properties for everything you want. I suppose if you're using control arrays for child controls on the UC, it might not be too bad. But it sounds like you'll be dealing with 1000s of child controls.
Also, if you go the UC route, I typed up a document years ago that was basically notes to myself about these things. At first, they're a bit of a mind-warp. However once you get used to them, they're not bad at all. Here's a quick attempt at shoving my old document into this post:
---
Code:
Keywords to be addressed:
UserControl_InitProperties Event / Sub within a UserControl
UserControl_ReadProperties Event / Sub within a UserControl
UserControl_WriteProperties Event / Sub within a UserControl
PropertyBag Object with UserControl parent
ReadProperty Method of PropertyBag
WriteProperty Method of PropertyBag
PropertyChanged Method of UserControl directed at form (container).
Get [PropertyName] Procedure associated with a property
Let/Set [PropertyName] Procedure associated with a property
Ambient Object with UserControl parent
Extender Object with UserControl parent
EditAtDesignTime Property of UserControl
Fields: This is not listed above because it is more of a concept rather than an actual keyword. In the context here, a field is a property of a sub-control that is placed on our UserControl. For instance, we may have a UserControl with a single VB label on it. This label will have its own properties (like .Caption, .BackColor, etc.) These will be referred to as fields of the UserControl. You could also have other fields that are just module level variables of the UserControl.
PropertyBag: This is an internal object associated with a UserControl. It is quite similar to the Properties Window, but not the same thing. It can best be thought of as a memory object that is quite similar to the properties for a control in the .FRM file. It is used to set up the Properties Window, but other things can also create fields in the Properties Window (which should ultimately be included in the PropertyBag). Data is moved into and out of this PropertyBag memory object with the .ReadProperty and .WriteProperty methods.
The PropertyBag object is available to us only when the UserControl_ReadProperties or UserControl_WriteProperties fires (as a passed argument), unless we chose to save a copy of it (from within UserControl_ReadProperties) as a module level object.
We must guarantee that things stay in-sync between the PropertyBag object, the Properties Window, and the actual UserControl fields.
The one thing we need not concern ourselves with is the PropertyBag getting written to and read from the actual .FRM. This is done independent of anything here.
This PropertyBag is entirely read-only during runtime, although the UserControl fields are not.
UserControl_InitProperties: This is fired in only one instance, while in design mode when a control instance is first created by selecting the control from the toolbox and placing it on a form. It is not fired during runtime, nor when the form is opened in either the IDE or runtime. It’s not even fired when copying a control while in design mode. Strictly speaking, this event is not needed (and shouldn’t be used, if possible). It is a way to set initial fields for a new instance of a UserControl, but this is possibly better done by editing the actual UserControl and setting these defaults in the sub-controls of the UserControl.
UserControl_ReadProperties: This is fired anytime VB feels that the PropertyBag object should be read, with its properties placed in the fields of the UserControl. It fires when the form is opened from the project window in design mode, and also when the form is opened during runtime. Disappointingly, it does NOT fire when a new UserControl is created from the Toolbox (which causes a problem with snagging a copy of the PropertyBag). I suppose VB sees no reason to fire it since the UserControl and the PropertyBag were just created, so there is (in theory) no reason to fire it. It DOES fire when a control is copied. The PropertyBag is passed as an argument.
UserControl_WriteProperties: This is fired anytime VB feels that the fields of the UserControl should be written back into the PropertyBag. It is fired when a form is closed in design mode (if properties need to be saved from UserControl fields). It may also be fired when saving the project to disk (if the form with the UserControl is open and properties need to be saved from UserControl fields). The PropertyBag is passed as an argument.
ReadProperty: This is a method of the PropertyBag. This method is primarily (if not exclusively) used within the ReadProperties event. It allows the reading of data from the PropertyBag object in memory (not the Properties Window). These properties are typically written to the fields of the UserControl (but they might be written to module level variables).
WriteProperty: This is a method of the PropertyBag. This method is primarily (if not exclusively) used within the WriteProperties event. It allows the writing of data to the PropertyBag object in memory (not the Properties Window). This data is typically read from the UserControl fields (but it might be contained in module level variables).
PropertyChanged: This is a method of the UserControl , but it is more aimed at saving and loading the UserControl when the actual form is saved and loaded (from the project window). A PropertyBag property name must be specified when it is called. It can be called anywhere in the UserControl, but it is typically called anytime one of the user defined UserControl fields is changed. It notifies VB that the UserControl_WriteProperties event must be fired when the form is closed (or saved). Calling PropertyChanged does NOT cause UserControl_WriteProperties to immediately fire. This method is completely ignored during runtime.
Properties Window: This is not listed in the initial list of keywords, but it is mentioned several times. This is sometimes called a Property Page in other literature. This refers to the actual window that comes up in VB and lists the properties for a control (or possibly also the custom window). This Properties Window is partially constructed from the PropertyBag and partially constructed from the Get/Let/Set properties listed in the UserControl code. In fact, there can be some overlap in certain cases. The default properties (from the PropertyBag) that are always in the Properties Window are: (Name), CausesValidation, DragIcon, DragMode, Height, HelpContextID, Index, Left, TabIndex, Tag, ToolTipText, Top, Visible, WhatsThisHelp, & Width. All of these will automatically transfer between the Properties Window and the PropertyBag with no need for any coding. It is possible to create Get/Let/Set property procedures for these default properties that would do extra stuff (which would require that PropertyChanged be used), but this is not recommended. It is best if these properties were left alone.
In addition to these default properties, the coder can create as many additional properties on the Properties Window as he desires, by simply creating Get/Let/Set property procedures in the UserControl code. When the coder does this, he is responsible for deciding what to do with this data. Typically, it goes directly into (or out of) one of the UserControl fields, but it could go into a module level variable for other use. As a notice, to keep things in-sync, anytime a Let or Set it called, the coder should make sure that PropertyChanged is called with the name of the new property specified. That way, when it comes time to save the field to the PropertyBag, it will be saved.
The entire concept of a Properties Window disappears during runtime.
Get [PropertyName]: There are basically two circumstances when this fires. One, when the VB design mode Properties Window opens and needs to know the value for the property. Two, when the user programmatically reads the property during runtime. In the first instance (design mode), it fires anytime the Properties Window is opened and the UserControl is selected. It will also fire when moving from one property to the next on the Properties Window, even if we’re moving off of the property of interest.
Let/Set [PropertyName]: This fires in two instances. One, when the property is changed on the VB design mode Properties Window (upon the specific property losing focus). Two, when the property is changed during runtime in code. It is within this event that the coder is responsible for placing a PropertyChanged call with the PropertyName specified. It is assumed that this is ignored during runtime, but it notifies VB that the PropertyBag needs updating during design time.
Extender: This is an implicit object that is always available in the module level code of a UserControl. Some properties of a UserControl are supplied by the container rather than the actual UserControl. These are the properties supplied by the Extender object. They always appear in the PropertyBag and the Properties Window, and typically need not be of concern to the coder. For instance, Top, Left, Width, & Height are examples of Extender properties. There are a plethora of properties available in the Extender object. See “Extender Object” in the MSDN help for all of them.
Ambient: As with the Extender object, this is an implicit object that is always available in the module level code of a UserControl. This one actually has an associated class (AmbientProperties). (For MSDN help, look for AmbientProperties). This Ambient object provides properties that are specific to the UserControl, but not related to the sub-controls on the UserControl. For instance, the UserControl has its own BackColor. This BackColor is a property of the Ambient object.
One property of the Ambient object that is of particular use is the UserMode property. If it’s true, the project is actually running (in IDE or compiled). If it’s false, the IDE is in design mode. One must remember that a UserControl will possibly execute its code even when the IDE is in design mode.
EditAtDesignTime: This is just a native property of any UserControl. It states whether or not the control can fire it’s events while in design mode of the IDE. Even if it is set, the UserControl does not automatically fire its events. It only becomes active when the user right-clicks and clicks “Edit” from the design mode context menu of the UserControl. And then it goes out of this “active” mode when the user clicks off of that particular instance of the UserControl. There are several ways to detect when “Edit” was clicked, but the easiest is by monitoring the UserControl_EnterFocus event. This will always fire. The coder may also want to place “If Ambient.UserMode Then Exit Sub” at the top of this event so that it is ignored during runtime.
Figuring out when the coder is ready to get out of “Edit” mode is a bit tricky. If a separate “Edit” interface (with a separate form) is being used, that form can be loaded modally, and the code in the UserControl_EnterFocus event will freeze until that external form is either hidden or closed. The external form isn’t fully modal, but the UserControl code does freeze.
If the coder goes into “Edit” mode with hidden sub-controls actually on the UserControl, it’s best to have a module level flag to this effect. If this is done, be sure to check this flag when the UserControl_WriteProperties event fires. The coder may close the form with the UserControl without clicking off of the UserControl first. In other words, the exposed hidden “Edit” mode sub controls will not get a chance to re-hide (and also save their values) if you don’t check this flag in the UserControl_WriteProperties event. But even this isn’t always true. A Property “” will guarantee that the UserControl_WriteProperties event fires.
Example: For an example, we will create a simple UserControl that only contains one VB label. The following code (within the UserControl) would completely get this label up and going. The coder can work out their own resizing issues. The label also has a plethora of properties that might be “exposed” via the UserControl properties. This is entirely up to the coder. The default UserControl properties have nothing to do with this label’s properties.
Option Explicit
Public Property Get LabelCaption() As String
LabelCaption = Label1.Caption
End Property
Public Property Let LabelCaption(ByVal sCaption As String)
Label1.Caption = sCaption
PropertyChanged "LabelCaption"
End Property
Private Sub UserControl_ReadProperties(PropBag As PropertyBag)
Label1.Caption = PropBag.ReadProperty("LabelCaption")
End Sub
Private Sub UserControl_WriteProperties(PropBag As PropertyBag)
PropBag.WriteProperty "LabelCaption", Label1.Caption
End Sub
Copy-and-paste it into something like Word to get the paragraphs to wrap. Also, I could probably improve it with my current knowledge, but that's a good start.