Joachim is director of application development at Academic Advantages and a consultant with Crossbridge Connections. He can be contacted on CompuServe at 72122,144 or on the Internet as [email protected]
Program architecture lies on the fault line between the analysis of functional logic and its actual implementation as a program. While the design of a program is driven by a functional requirement, the architecture is shaped by constraints of technology. It bridges the gap between pure logic and its concrete manifestation as machine code. This makes it a fascinating topic for examination.
While a program architecture provides constraints and services that appear desirable for a particular project, they are neither practical to enforce through standards nor supported by the implementation technology.
It is interesting to note that there seems to be a correlation between the versatility of an implementation technology and the need to limit its flexibility by rules imposed on its use. If you build a house from scratch, the range of possible shapes is limited only by the building material and the intended use of the structure. If the building material is very versatile, like brick and mortar, there are many different shapes and layouts that can satisfy a desired functionality. It is therefore a good idea to agree on a specific architecture even before drawing up the blueprints. On the other hand, if you use prefabricated elements, the range of choices is limited: A Quonset hut is a Quonset hut is a Quonset hut.
From assembler to OOP, the enforcement of constraints to limit complexity progressively changes from being a set of programming rules (standards), to favoring the architectural approach, to ultimately becoming part of the implementation technology itself; see Figure 1. Anywhere between assembler and OOP the architecture plays an important role in making the technology an effective and usable implementation tool.
The factors that determine the scope of a program architecture include the program usage that the architecture supports, the implementation technology, and the processing mode.
If a program is a one-shot pop that is unlikely to ever get changed, spending time to develop a specific architecture is hard to justify. Often, however, seemingly insignificant programs grow into crucial components of major systems. In such cases, it may become necessary to rewrite the program. A more cautious approach is to use a proven architecture. This will require more overhead than just coding a program without any architectural constraints, but it proves useful if the program turns out to be more important than anticipated.
The other extreme of program usage is full integration into a complex data-processing system. No matter what the implementation technology, specific constraints and services are probably necessary to tie program elements together into a single system. These common constraints and services are at the heart of the system and may justify spending as much as 50 percent of the total system-implementation time on the architecture.
The architecture must provide services and constraints not supported by the implementation technology. For example, if a system provides unsatisfactory communication between calling modules (such as CICS), insufficient error protection (MS-DOS), or unsatisfactory printing routines (Cobol), chances are that an architecture will fill the gap. Of course, if a technology lacks certain fundamental capabilities--such as the ability to process sound files, or to distribute transactions across a network--it may be impractical to rely on an architecture for corrective actions.
Depending on the processing mode, an architecture may have to perform different duties. A batch architecture, even for a complex system, will never need to provide more than just batch services. On the other hand, an architecture for an online, cooperative system must furnish real-time services and possibly extend across different system platforms.
The scope of an architecture depends mostly on functional and systems-related criteria. Its constraints, however, are defined predominantly by the human factor.
First, the architecture has the precise function to enforce simplicity and thus achieve maintainability of even complex programs. It must blot as much complexity from the application logic as possible and impose a documentable, recognizable structure upon the logic that it serves. An architecture must also support program usability by limiting inconsistencies of the user interface within a system and across programs. Users like program access to follow a predictable and reliable pattern.
Finally, the architecture must monitor the use of resources in order to preserve the efficiency of execution. This is particularly important for online programs, as insufficient response time is a major obstacle to the system's acceptance.
In short, an architecture is the result of conflicting requirements (see Figure 2). It takes experience and much attention to detail to achieve a workable compromise. Because of the importance of an architecture to the success of a system, you should spare no effort to achieve the best solution possible for any given set of circumstances.
An architecture handles data used across several programs or modules. The definition of this data and its structure is the first important component of an architecture. It is equally important to identify the program logic that should be provided by the architecture. This logic will be available from anywhere within the system and is likely to define how all system components interact with each other. Hence, program logic and data definitions are the two dimensions you have to manage to build an effective program architecture.
This is tantamount to requiring that, in order to build a good program architecture, you must know exactly what the complete system will look like and how it will be put together. While system designers generally are perceptive, they are rarely omniscient. It's a good idea then to look for a more practical approach to the design of an architecture.
Bottom up or top down is the question. Do you first design the system, then devise the architecture, or do you establish the architecture first, then assemble the system to fit the architecture?
The answer isn't simple. We're likely to do neither, or rather, a little bit of both. The process is iterative, with continuous corrective measures. We establish an architecture that provides most of the services and support that we think is needed, and then we proceed with the detail. While designing and implementing the program detail, you may notice additional requirements and go back to modify the architecture. This may affect the programs already designed and written and cause additional modifications. The further along you get, the more far-reaching consequences any change to the program architecture will have. This is a strong incentive to invest a lot of time up front, especially when large programming teams are involved.
There are two notable exceptions to this approach. The first is the commercial availability of established architectures. These include the Hogan umbrella, Foundation, and others. These architectures are likely to be well proven, providing all data handling and all elements of common logic that could possibly be required. Typically, they address complex-systems situations for which adequate implementation technologies are not available. In Figure 1, this situation would be placed somewhere left of the center on the time line.
Moving to the right on that axis, the importance of the architecture itself decreases and the incidence of the implementation technology becomes more relevant. In the second exception, a sufficiently strong implementation technology is used, making the existence of an architecture incidental in the overall scheme of things. Since the architecture has ceased to be a strong component of the application, you are unlikely to invest a lot of effort into it.
Advanced programming languages provide a strong incentive for prototyping and experimentation. If any architectures are needed, they may be developed along the way. Common requirements will come into focus when we experiment with the functionality and implementation techniques.
Visual Basic (VB) is a fairly advanced programming language. Like Basic, it is an interpreted language that can also be compiled. Unlike Basic, however, VB is event driven and does not adhere to the traditional procedural model.
The procedural model assumes that a central piece of logic has the ultimate control over the program. The concepts of "program start," "program end," and "mainline" are strongly associated with this model. There may be questions on how to break down the logic and manage the data, but control definitely resides within the program logic and all actions and activities are initiated from within that logic.
An event-driven structure, however, relies on distributed logic. Actions and activities are associated with objects that generate events. A screen is an object, a field on the screen is an object, a button in a field on a screen is an object, and so on. Each object generates events through some sort of interaction with the world (a mouse click, for example). This starts the execution of any logic previously associated with that event. The important difference from the procedural model is that the logic associated with this event is self-contained; no centralized piece of logic is aware of all events being triggered. The event-driven program operates like an ecology: All elements of the whole act separately, but work together to achieve a common goal. The fact that events can be started independently leads to their potential concurrence. Hence, program execution is never quite deterministic, as separate events may start at different times and interact with each other in different ways.
All these interactions can grow very complex. For this reason, an event-driven programming language has a more-solid structure and clearer constraints than any procedural language. Much of the logic and structure that traditional systems enforce via an architecture are intrinsic to the event-driven approach and thus to the programming language itself. A good example is the way data is handled. Some data is temporary within the event procedure, other data is shared across procedures or even within the whole system and remains available throughout the life of a program. The definition and management of the various types of data is inherent to Visual Basic and does not have to be provided by an architecture.
You might rightfully ask, if the structure is so strong, does VB really need a program architecture? The answer depends on what you want to do. I found VB to be fairly good at handling most structural requirements, even of a complex system with multiple screens. Where an architecture came in handy, though, was in ensuring consistency across screens.
Visual Basic places no limitations on what goes where on the screen, and no areas are reserved for any special purpose. Nevertheless, in one recent project, we wanted to provide a navigation aid to users. We decided to reserve the top area of all major screens for information about the data being processed. To look stable and reassuring, that area had to remain consistent and perfectly aligned across multiple screens. This required a special mechanism not available through the programming language.
An architecture might also be necessary because VB forms (which define a unit of logic consisting of a window, its controls, and associated logic and variables) do not have an intrinsic awareness of where they are called from. They are loaded and activated, then perform whatever logic is necessary, and relinquish control to whatever code started them. If the execution logic is supposed to vary depending on where the form was called from, some customized code needs to be devised.
Another example is the the management of asynchronous processing. Simply put, VB asynchronous processing lacks finesse. If you plan to rely on asynchronous processing in any way at all, allow yourself extra time to develop a managing mechanism that handles conflicts arising from concurrent and recursive execution and enforces dependencies.
I was recently part of a team that designed a reasonably complex, commercial information-management program encompassing about 35,000 lines of code written in Visual Basic.
When we started out, we assumed that VB would be able to support all our structural requirements. It was only during the protoyping of the screens that we realized the usefulness of a mechanism that would ensure a consistent look and feel across several of our screens.
In particular, we decided that at any given time, any of the three major screens were to provide information about the data being processed. To make things easy and natural, this information was to remain displayed in the same location--the upper portion of the screen--independently from the data window opened at any given time. User interaction was most successful when the area containing that information looked as if it were hovering above, almost as if it were no part of the screen proper. A change of data windows had to leave the information area unaffected, unless, of course, the data being displayed changed.
Figure 3 shows one of the three screens that displays this information area, located immediately below the menu bar. There is one panel containing two fields. The right field provides information about the file that is currently open. The left field provides details about the item being accessed. Since the entire application relies heavily on graphical symbols, these fields must be capable of displaying icons, in addition to character information.
The status of both fields must be clear to the user. Figure 4 shows a progression of information areas captured in various situations. When no file is open, the Item field is invisible, since no item can be selected. If a file is open, the Item field either shows the name of a selected item or conveys the request that an item is to be selected. Whenever valid information is associated with the field, the background color is yellow. If no information is selected or the information is incomplete, the background of the field is white. When an action request is stated, like <click> to select a DATABASE, this request is displayed in red on white background, and the field must be clickable. In addition, for user convenience, either field is clickable when associated with a screen that allows the selection of a database or of an item. The field always contains an image that varies depending on the situation being addressed.
All this sounds complicated. However, as a user interface it comes across quite naturally, communicating through colors, images, and characters, making the structure of the message very intuitive.
Once we defined our intentions, we had to seek a way to implement them. We explored several alternatives and found that only a few of them were suitable for implementation. I'll briefly discuss the options that we discarded.
The first (and possibly most obvious) way to manage the screens would be to display the information area in a window of its own. This window would be visible together with any of the data windows. This would require that for every screen change, not just one but two windows would need to be managed and synchronized. While this adds complexity, it can be implemented, but there are other difficulties to be reckoned with.
The menu bar and title are always displayed along the top of a window. While we chose not to use a menu bar or to suppress the title, we didn't have the option of displaying it in other than the standard system location.
If we had chosen to display the information fields in a window of their own and programmatically placed this window above the data window, the title and menu bar of the data window would be sandwiched between the two windows. This would be an entirely wrong location. To remain consistent with the general windows interface, a menu must be above the area that a user recognizes as a work area and not in the middle of it.
An alternative was to place the menu and its management within the confines of the information window. In this case, the logic of the information window would need to associate the appropriate menu choices with the active data windows and convey menu selections to the program logic of that window. This was feasible but too complex for the simple goal that we had in mind.
We were then tempted to define a window so that it seems to float on top of all other windows displayed on the screen. To do that, you use the TOPMOST option of the SetWindowPos API, which works like the Always on Top option of Windows help. We considered defining a separate information window in this manner. This solution was aesthetically pleasing, leaving the user in full control of where to drag the information window.
The flexibility of that approach, however, also carried a price: The user would have to worry about placing the information window on the screen and moving it to where data would not be covered, thus making the interface more complex rather than less.
Additionally, there was an architectural problem with this solution. The topmost window will remain topmost even if the associated application ceases to have the focus. In other words, if we Alt+Tab from our information-management application with an open topmost window into an unrelated application, the deactivated topmost window would remain visible on the screen, on top of all other windows. This can be addressed programmatically but requires extra logic for proper management. More importantly, we found that in the case of system messages or the display of any other small modal windows, deadlock situations could arise. A topmost window can hide a modal window, preventing the latter from being accessed. Yet, the modal window requires service before the topmost window can be moved. At this point, the application is in deadlock and needs to be restarted. Any preventive mechanism would be quite delicate, so we preferred to avoid the risk of locking up the system entirely. We also did not like that an API call would be needed, hence bypassing VB. This may have affected future portability of the code, so we decided to abandon this approach, as well.
As we considered the problem further, a less-sophisticated solution ultimately prevailed. Technically straightforward, it has the advantage of being very efficient and easy to use. In essence, it requires that the major screens be equipped with a predefined information area and that some common method be found to align and display data in those areas.
Contrary to the other alternatives, the information area is now part of the data window; however, the managing mechanism is centralized and interacts with each form in identical fashion. Besides managing the data displayed and the characteristics of the display fields, this mechanism also manages the relative location of the fields within the open window. By ensuring that the relative position is the same across different windows, we achieve the impression that the window changes, but the information field does not.
It would have been nice if VB had stronger object orientation, because, if an object on a form were being reused elsewhere, we would only need to refer to that object at run time and use its data and logic as required. Unfortunately, this isn't possible with VB because the only objects for which new instances can be created are forms. Fortunately, a little more coding yields an equally satisfactory result.
Any of the windows that are to contain an information area require the component objects to be explicitly defined. Their location, size, content, and the like, do not have to be preset because properties and data can be manipulated at run time.
Referring again to Figure 4, you have to decide what type of objects are required. The information area consists of one panel with a three-dimensional look and two fields that contain both an image and some characters.
Functionally, the panel is optional. However, we decided that the improvement to the look and feel would be worth the added overhead. A solid gray 3-D Panel Control suited our needs.
The two fields embedded within the panel are Grid Controls displaying a single cell. The Grid is the only basic control (an object associated with a Visual Basic form) that displays character information and images with equal ease. At the same time, it intercepts Click and MouseMove events and supports the BackColor and ForeColor properties, which we can use to conveniently change the color of the fields.
At this point, we determined that each information area would consist of one 3-D Panel Control and two Grid Controls within the panels. Listing One shows the code that defines these controls. It identifies the information area with the name panTopPanel and specifies the controls grdItemName and grdFileName as contained within that panel. Since the properties of these tools will be set at run time, their initialization is not strictly necessary. It is just a sensible precaution against possible oversights.
In addition to the mere definition of the tools, code must be provided to manage the information area. Prior to window activation, the information area must be initialized; after the deactivation of the screen, the then-current information area must be preserved and passed on to the next screen. Contrary to the display controls themselves, the code for this logic does not have to be included in every form but can be shared from a central library. Figure 5 shows the modules of this architecture and their relationship with the rest of the program.
Remember that the data from an information area needs to be passed from form to form. There are various ways to accomplish this. Intuitively, you might think of copying the information at every screen change directly from one window to the next window. This avoids the use of internal storage, but it requires the copying logic to have an awareness of both the source and the target screen. In addition, both screens must be present in memory at the same time. We found that updating fields upon activation and saving fields upon deactivation would provide a more flexible and consistent approach. Therefore, we allocated a storage area for the information data.
Listing Two is the definition of the variables in which the properties of panTopPanel and its two controls are being stored. There are two interesting anomalies you may notice when looking at these definitions. The first regards the variables for the column width and row height of the two grid controls. While we know from Figure 4 that only one cell of the grid control is being displayed, the variable for the column width and height is an array of two, allowing you to store values for two separate rows and columns. There is a good reason for this: The grid is not defined as a single cell but rather as a two-by-two. Row 0 and column 0 are defined with a nominal width and height that renders them invisible. Only the cell at the intersection of row 1 with column 1 is visible. This is because, in a grid, the active cell is always highlighted, meaning that the bitmap of the content is inverted (white to black, red to green, and so on). If there is only one cell in the grid, this is the only cell that can be active, and an occasional inversion of the bitmap does not look good. Therefore, we defined the additional row and column of negligible height and width and made sure that only the invisible cells are ever activated.
Note that there's no field in which to store the images from grdItemName and grdFileName. Figure 4 shows that there are always images in the information field. They must be saved somewhere, but rather than saving them in a variable, we found it easier to write them to a temporary file. The VB language has the commands to do so. Performance is essentially unaffected by this choice, since the bit string containing the images remains in the data buffer most of the time, remaining available for immediate access. Writing the file seems to be asynchronous so it's not on the critical path to efficient performance.
Figure 5 shows that the variables that store the architecture's properties must be initialized before any display logic is executed. This is implemented in Listing Three . These settings define the defaults. Note that there are no defaults for the text and the image displayed in the information field. The grid content is initialized by the program logic of the first form executed.
All properties set through this procedure can be modified by the program code. The architecture will accept these modifications and propagate them to other windows. In reality, however, only a very few properties of the controls defined by these variables will be changed programmatically. The defaults set here define the look and feel of the information area across the program and, therefore, have to be chosen with care.
The architecture consists of three program modules: gpTopPanelSave writes the data from the information area to memory, gpTopPanelInit uses the data stored in memory to initialize the information area of a screen, and gpTopPanelItem allows controlled changes to the Item field of the information area.
Listing Four is the gpTopPanelInit code, which consists of three parts that initialize the panel and two grids, respectively. Each part is preceded by a statement that determines how much of the panel and the grids needs to be initialized. If the format is already established, only the data is restored. This eliminates unnecessary overhead without reducing functionality.
This procedure requires the form of the calling program to be passed as a parameter. It is invoked by gpTopPanelInit Me. The Me identifies the form with which the calling logic is associated. The invoking statement should be triggered by the activation of every form that contains an information area. This ensures that the information area is refreshed whenever any window with an information area receives user focus.
The grids are defined as having four cells, out of which only the bottom-right cell with the coordinates 1,1 is visible. You must be careful to use the row and col properties to position the logic on the valid cell before moving text into the grid. Also interesting, and very important, is the way we are dealing with the images. Remember that we decided not to store the images in memory variables, but to place them in temporary files. This isn't a problem with LoadPicture. However, the present logic might be engaged to initially load the information area, before any image is stored on file. You must therefore allow for enough resiliency to correctly deal with a situation where no file can be accessed. LoadPicture generates an error if a specified file is not found. Therefore, we use the dir$ command to determine whether the temporary file exists on file. If it does not, an empty string is passed to LoadPicture rather than the filename itself. An empty string does not trigger an error condition, it just causes an empty image to be generated and stored in the grid. After startup, the logic of the first form will load images into both grids.
gpTopPanelSave (see Listing Five) is the counterpart to gpTopPanelInit. Instead of restoring the properties of the information area, it saves them. We don't have to worry about nonexisting files, nor do we allow saving partial data. This simplifies the code somewhat. This procedure is invoked by gpTopPanelSave Me. In keeping with the symmetrical nature of this approach, the statement is triggered by the deactivation of a form that contains an information area.
In addition to these two procedures, we found it useful to have a third procedure to change the content of the Item field. Functionally, this isn't necessary, because the content and the definition of the information area can be changed directly from the program code. All such changes would be captured and saved by the gpTopPanelSave procedure. However, the information-area fields should be uniform, no matter when, and from where they are updated. The Item field is updated from different locations. Thus, we chose to build a common update procedure that changes or resets the Item-field text or replaces the image. It is called gpTopPanelItem (see Listing Six).
This procedure requires three parameters: the form from which it is called, an optional text, and the image to be displayed. The Text field can be either null, empty, or contain a valid string. When it is null, a red default string with the invitation to click on the field is displayed on a white background. When the text field is empty, but not null, no text is displayed, and the background is white. When it contains a valid string, the Item area is initialized to a yellow background, and the characters of text are displayed in black, using the default-character options selected by the user. There must always be a valid image, and that image is always displayed.
The approach to program architecture presented here is simple and straightforward. It allows for speedy processing: Properties and data are stored in main memory, and access is very fast. Pictures are stored in and retrieved from files, but buffer handling makes file access transparent.
All calls are very simple. Instead of a long series of parameters, we pass the whole form and let the called procedure sort out which parts to access. This makes the procedures easy to use, as well as cutting down on programming time.
Repetitive code is hidden by the calls to those procedures. The code is reused many times but exists only once and is stored in centralized procedures. This reduces redundancy, thus improving development time and simplifying maintenance.
Overall, we found that the architecture described in this article complemented the native Visual Basic services very well and provided just the enhancement needed.
Figure 1 Enforcement of constraints to limit complexity changes from being provided by standards to being embedded in the programming language.
Figure 2 Program analysis and implementation: An architecture is the result of conflicting requirements.
Figure 3 One of the three screens displaying the information area immediately below the menu bar.
Figure 4 A progression of information areas captured in various situations.
Figure 5 Modules of this architecture and their relationship with the rest of the program. Elements of the architecture are shown in blue.
Begin SSPanel panToppanel Alignment = 0 'Left Justify - TOP BackColor = &H00C0C0C0& BevelWidth = 2 BorderWidth = 4 FloodShowPct = 0 'False Font3D = 3 'Inset w/light shading ForeColor = &H00000000& Height = 612 HelpContextID = 2 Left = 60 TabIndex = 0 Top = 60 Width = 9612 Begin Grid grdItemName BackColor = &H00FFFFFF& BorderStyle = 0 'None FixedCols = 0 FixedRows = 0 FontBold = 0 'False FontItalic = 0 'False FontName = "Arial" FontSize = 9 FontStrikethru = 0 'False FontUnderline = 0 'False ForeColor = &H000000FF& GridLines = 0 'False Height = 435 HelpContextID = 3 HighLight = 0 'False Left = 120 ScrollBars = 0 'None TabIndex = 5 Top = 120 Width = 3300 End Begin Grid grdFileName BackColor = &H00FFFFFF& BorderStyle = 0 'None FixedCols = 0 FixedRows = 0 FontBold = -1 'True FontItalic = 0 'False FontName = "Arial" FontSize = 9 FontStrikethru = 0 'False FontUnderline = 0 'False ForeColor = &H00000000& GridLines = 0 'False Height = 435 HelpContextID = 4 HighLight = 0 'False Left = 4680 ScrollBars = 0 'None TabIndex = 4 Top = 120 Width = 3375 End End
'Global variables defining the TopPanel of the data gathering and ' management forms. TopPanel provides navigational aid to the user. ' Upon activation or deactivation of a form the TopPanel information is ' kept current by the architecture. If the processing status changes, ' program logic will update the fields directly. ' variables to store properties of TopPanel Global gvTopPanelAlign As Integer Global gvTopPanelAlignment As Integer Global gvTopPanelAutosize As Integer Global gvTopPanelBackColor As Long Global gvTopPanelBevelInner As Integer Global gvTopPanelBevelOuter As Integer Global gvTopPanelBevelWidth As Integer Global gvTopPanelBorderWidth As Integer Global gvTopPanelCaption As String Global gvTopPanelDragIcon As Integer Global gvTopPanelDragMode As Integer Global gvTopPanelEnabled As Integer Global gvTopPanelFloodColor As Long Global gvTopPanelFloodPercent As Integer Global gvTopPanelFloodShowPct As Integer Global gvTopPanelFloodType As Integer Global gvTopPanelFont3d As Integer Global gvTopPanelFontBold As Integer Global gvTopPanelFontItalic As Integer Global gvTopPanelFontName As String Global gvTopPanelFontSize As Single Global gvTopPanelFontStrikeThru As Integer Global gvTopPanelFontUnderline As Integer Global gvTopPanelForeColor As Long Global gvTopPanelHeight As Single Global gvTopPanelHelpContextID As Long 'note "hWnd" is read only at exec time; we shall not store it 'note "Index" is read only at exec time; we shall not store it Global gvTopPanelIndex As Integer Global gvTopPanelLeft As Single Global gvTopPanelMousePointer As Integer Global gvTopPanelName As String Global gvTopPanelOutline As Integer 'note "Parent" is read only at exec time; we shall not store it Global gvTopPanelRoundedCorners As Integer Global gvTopPanelShadowColor As Integer Global gvTopPanelTabindex As Integer Global gvTopPanelTag As String Global gvTopPanelTop As Single Global gvTopPanelVisible As Integer Global gvTopPanelWidth As Single 'Variables to store properties of grdFileName which is part of TopPanel 'grdFileName is a Grid and has the corresponding Variables Global gvFilenameBackColor As Long Global gvFilenameBorderStyle As Integer Global gvFileNameCellSelected As Integer Global gvFileNameClip As String Global gvFileNameCol As Integer Global gvFileNameColAlignment As Integer Global gvFilenameCols As Integer Global gvFilenameColWidth(0 To 1) As Long Global gvFileNameDragIcon As Integer Global gvFilenameDragmode As Integer Global gvFilenameEnabled As Integer Global gvFilenameFillstyle As Integer Global gvFileNameFixedAlignment As Integer Global gvFilenameFixedCols As Integer Global gvFilenameFixedRows As Integer Global gvFilenameFontBold As Integer Global gvFilenameFontItalic As Integer Global gvFilenameFontName As String Global gvFilenameFontSize As Single Global gvFilenameFontstrikethru As Integer Global gvFilenameFontUnderline As Integer Global gvFilenameForecolor As Long Global gvFilenameGridLines As Integer Global gvFilenameHeight As Single Global gvFilenameHelpContextID As Long Global gvFilenameHighlight As Integer Global gvFilenameLeft As Single Global gvFileNameLeftCol As Integer Global gvFileNameName As String 'note "Parent" is read only at exec time; we shall not store it Global gvFilenamePicture As Variant Global gvFileNameRow As Integer Global gvFilenameRowHeight(0 To 1) As Long Global gvFilenameRows As Integer Global gvFilenameScrollbars As Integer Global gvFileNameSelEndCol As Integer Global gvFileNameSelEndRow As Integer Global gvFileNameTag As String Global gvFileNameText As String 'Global goDataBase As String 'defined as global option Global gvFilenameTop As Single Global gvFileNameTopRow As Integer Global gvFilenameVisible As Integer Global gvFilenameWidth As Single 'Variables to store properties of grdItemName which is part of TopPanel 'grdItemName is a Grid and has the corresponding Variables Global gvItemNameBackColor As Long Global gvItemNameBorderStyle As Integer Global gvItemNameCellSelected As Integer Global gvItemNameClip As String Global gvItemNameCol As Integer Global gvItemNameColAlignment As Integer Global gvItemNameCols As Integer Global gvItemNameColWidth(0 To 1) As Long Global gvItemNameDragIcon As Integer Global gvItemNameDragmode As Integer Global gvItemNameEnabled As Integer Global gvItemNameFillstyle As Integer Global gvItemNameFixedAlignment As Integer Global gvItemNameFixedCols As Integer Global gvItemNameFixedRows As Integer Global gvItemNameFontBold As Integer Global gvItemNameFontItalic As Integer Global gvItemNameFontName As String Global gvItemNameFontSize As Single Global gvItemNameFontstrikethru As Integer Global gvItemNameFontUnderline As Integer Global gvItemNameForecolor As Long Global gvItemNameGridLines As Integer Global gvItemNameHeight As Single Global gvItemNameHelpContextID As Long Global gvItemNameHighlight As Integer Global gvItemNameLeft As Single Global gvItemNameLeftCol As Integer Global gvItemNameName As String 'note "Parent" is read only at exec time; we shall not store it Global gvItemNamePicture Global gvItemNameRow As Integer Global gvItemNameRowHeight(0 To 1) As Long Global gvItemNameRows As Integer Global gvItemNameScrollbars As Integer Global gvItemNameSelEndCol As Integer Global gvItemNameSelEndRow As Integer Global gvItemNameTag As String Global gvItemNameText As String Global gvItemNameTop As Single Global gvItemNameTopRow As Integer Global gvItemNameVisible As Integer Global gvItemNameWidth As Single
'Initialize variables that store properties of TopPanel gvTopPanelAlign = 0 gvTopPanelAlignment = 0 gvTopPanelAutosize = 0 gvTopPanelBackColor = &HC0C0C0 gvTopPanelBevelInner = 0 gvTopPanelBevelOuter = 2 gvTopPanelBevelWidth = 2 gvTopPanelBorderWidth = 4 gvTopPanelCaption = "" gvTopPanelDragMode = 0 gvTopPanelEnabled = True gvTopPanelForeColor = &H0& gvTopPanelFontName = "Arial" gvTopPanelFontSize = 9.6 gvTopPanelHeight = 615 gvTopPanelHelpContextID = 0 gvTopPanelLeft = 105 gvTopPanelOutline = False gvTopPanelRoundedCorners = True gvTopPanelShadowColor = 0 gvTopPanelTop = 75 gvTopPanelVisible = True gvTopPanelWidth = 9345 'Initialize variables that store properties of grdFileName ' which is part of TopPanel gvFilenameBackColor = &HFFFFFF gvFilenameBorderStyle = 0 gvFilenameCols = 2 gvFilenameColWidth(0) = 1 gvFilenameColWidth(1) = 3500 gvFilenameDragmode = 0 gvFilenameEnabled = True gvFilenameFillstyle = 0 gvFilenameFixedCols = 0 gvFilenameFixedRows = 0 gvFilenameFontBold = False gvFilenameFontItalic = False gvFilenameFontName = "Arial" gvFilenameFontSize = 9.75 gvFilenameFontstrikethru = False gvFilenameFontUnderline = False gvFilenameForecolor = &H0& gvFilenameGridLines = False gvFilenameHeight = 500 gvFilenameHelpContextID = 0 gvFilenameHighlight = False gvFilenameLeft = 3900 gvFilenameRows = 2 gvFilenameRowHeight(1) = 425 gvFilenameRowHeight(0) = 1 gvFilenameScrollbars = 0 gvFilenameTop = 75 gvFilenameVisible = True gvFilenameWidth = 3501 'Initialize variables that store properties of grdItemName ' which is part of TopPanel gvItemNameBackColor = &HFFFFFF gvItemNameBorderStyle = 0 gvItemNameCols = 2 gvItemNameColWidth(0) = 1 gvItemNameColWidth(1) = 3500 gvItemNameDragmode = 0 gvItemNameEnabled = True gvItemNameFillstyle = 0 gvItemNameFixedCols = 0 gvItemNameFixedRows = 0 gvItemNameFontBold = goListFontBold gvItemNameFontItalic = goListFontItalic gvItemNameFontName = goListFontName gvItemNameFontSize = goListFontSize gvItemNameFontstrikethru = False gvItemNameFontUnderline = False gvItemNameForecolor = &H0& gvItemNameGridLines = False gvItemNameHeight = 500 gvItemNameHelpContextID = 0 gvItemNameHighlight = False gvItemNameLeft = 120 gvItemNameRows = 2 gvItemNameRowHeight(1) = 425 gvItemNameRowHeight(0) = 1 gvItemNameScrollbars = 0 gvItemNameTop = 75 gvItemNameVisible = True gvItemNameWidth = 3501
Sub gpTopPanelInit (pForm As Form) 'define the path for temp files tPath$ = App.Path 'put temp files into application directory If Right$(tPath$, 1) <> "\" Then tPath$ = tPath$ & "\" 'terminate path with backslash 'panel proper If pForm.panToppanel.Width = gvTopPanelWidth And pForm.panToppanel.Height = gvTopPanelHeight And pForm.panToppanel.Top = gvTopPanelTop Then 'do nothing - panel is already established Else pForm.panToppanel.Align = gvTopPanelAlign pForm.panToppanel.Alignment = gvTopPanelAlignment pForm.panToppanel.AutoSize = gvTopPanelAutosize pForm.panToppanel.BackColor = gvTopPanelBackColor pForm.panToppanel.BevelInner = gvTopPanelBevelInner pForm.panToppanel.BevelOuter = gvTopPanelBevelOuter pForm.panToppanel.BevelWidth = gvTopPanelBevelWidth pForm.panToppanel.BorderWidth = gvTopPanelBorderWidth pForm.panToppanel.Caption = gvTopPanelCaption pForm.panToppanel.DragMode = gvTopPanelDragMode pForm.panToppanel.Enabled = gvTopPanelEnabled pForm.panToppanel.FloodColor = gvTopPanelFloodColor pForm.panToppanel.FloodPercent = gvTopPanelFloodPercent pForm.panToppanel.FloodShowPct = gvTopPanelFloodShowPct pForm.panToppanel.FloodType = gvTopPanelFloodType pForm.panToppanel.Font3D = gvTopPanelFont3d pForm.panToppanel.FontBold = gvTopPanelFontBold pForm.panToppanel.FontItalic = gvTopPanelFontItalic pForm.panToppanel.FontName = gvTopPanelFontName pForm.panToppanel.FontSize = gvTopPanelFontSize pForm.panToppanel.FontStrikethru = gvTopPanelFontStrikeThru pForm.panToppanel.FontUnderline = gvTopPanelFontUnderline pForm.panToppanel.ForeColor = gvTopPanelForeColor pForm.panToppanel.Height = gvTopPanelHeight pForm.panToppanel.HelpContextID = gvTopPanelHelpContextID pForm.panToppanel.Left = gvTopPanelLeft pForm.panToppanel.MousePointer = gvTopPanelMousePointer pForm.panToppanel.Outline = gvTopPanelOutline pForm.panToppanel.RoundedCorners = gvTopPanelRoundedCorners pForm.panToppanel.ShadowColor = gvTopPanelShadowColor pForm.panToppanel.TabIndex = gvTopPanelTabindex pForm.panToppanel.Tag = gvTopPanelTag pForm.panToppanel.Top = gvTopPanelTop pForm.panToppanel.Visible = gvTopPanelVisible pForm.panToppanel.Width = gvTopPanelWidth End If 'itemname: Name grid on Panel If pForm.grdItemname.Top = gvItemNameTop And pForm.grdItemname.Width = gvItemNameWidth And pForm.grdItemname.Height = gvItemNameHeight Then 'grid is already established - rewrite only content pForm.grdItemname.FontBold = gvItemNameFontBold pForm.grdItemname.FontItalic = gvItemNameFontItalic pForm.grdItemname.FontName = gvItemNameFontName pForm.grdItemname.FontSize = gvItemNameFontSize pForm.grdItemname.FontStrikethru = gvItemNameFontstrikethru pForm.grdItemname.FontUnderline = gvItemNameFontUnderline pForm.grdItemname.ForeColor = gvItemNameForecolor pForm.grdItemname.BackColor = gvItemNameBackColor pForm.grdItemname.Row = 1: pForm.grdItemname.Col = 1: pForm.grdItemname.Text = gvItemNameText temp$ = Dir$(tPath$ & "smarta01.tmp") ' empty if File does not exist If temp$ <> "" Then temp$ = tPath$ & temp$ ' Dir$ does not list subdirectory. ' Add it to name if found! pForm.grdItemname.Picture = LoadPicture(temp$) ' set picture to retrived info. Empty if none Else 'grid not established - set grid and rewrite content pForm.grdItemname.BackColor = gvItemNameBackColor pForm.grdItemname.BorderStyle = gvItemNameBorderStyle pForm.grdItemname.Cols = gvItemNameCols pForm.grdItemname.ColWidth(0) = gvItemNameColWidth(0) pForm.grdItemname.ColWidth(1) = gvItemNameColWidth(1) pForm.grdItemname.DragMode = gvItemNameDragmode pForm.grdItemname.Enabled = gvItemNameEnabled pForm.grdItemname.FillStyle = gvItemNameFillstyle pForm.grdItemname.FixedCols = gvItemNameFixedCols pForm.grdItemname.FixedRows = gvItemNameFixedRows pForm.grdItemname.FontBold = gvItemNameFontBold pForm.grdItemname.FontItalic = gvItemNameFontItalic pForm.grdItemname.FontName = gvItemNameFontName pForm.grdItemname.FontSize = gvItemNameFontSize pForm.grdItemname.FontStrikethru = gvItemNameFontstrikethru pForm.grdItemname.FontUnderline = gvItemNameFontUnderline pForm.grdItemname.ForeColor = gvItemNameForecolor pForm.grdItemname.GridLines = gvItemNameGridLines pForm.grdItemname.Height = gvItemNameHeight pForm.grdItemname.HelpContextID = gvItemNameHelpContextID pForm.grdItemname.HighLight = gvItemNameHighlight pForm.grdItemname.Left = gvItemNameLeft pForm.grdItemname.RowHeight(0) = gvItemNameRowHeight(0) pForm.grdItemname.RowHeight(1) = gvItemNameRowHeight(1) pForm.grdItemname.Rows = gvItemNameRows pForm.grdItemname.ScrollBars = gvItemNameScrollbars pForm.grdItemname.Row = 1: pForm.grdItemname.Col = 1: pForm.grdItemname.Text = gvItemNameText pForm.grdItemname.Top = gvItemNameTop pForm.grdItemname.Visible = gvItemNameVisible pForm.grdItemname.Width = gvItemNameWidth temp$ = Dir$(tPath$ & "smarta01.tmp") ' empty if File does not exist If temp$ <> "" Then temp$ = tPath$ & temp$ ' Dir$ does not list subdir. Add it if found! pForm.grdItemname.Picture = LoadPicture(temp$) ' set picture to retrived info. Empty if none End If 'Filename: Name grid on Panel If pForm.grdFilename.Width = gvFilenameWidth And pForm.grdFilename.Top = gvFilenameTop And pForm.grdFilename.Height = gvFilenameHeight Then 'grid is already established - rewrite only content pForm.grdFilename.FontBold = gvFilenameFontBold pForm.grdFilename.BackColor = gvFilenameBackColor pForm.grdFilename.FontItalic = gvFilenameFontItalic pForm.grdFilename.FontName = gvFilenameFontName pForm.grdFilename.FontSize = gvFilenameFontSize pForm.grdFilename.FontStrikethru = gvFilenameFontstrikethru pForm.grdFilename.FontUnderline = gvFilenameFontUnderline pForm.grdFilename.ForeColor = gvFilenameForecolor pForm.grdFilename.Row = 1: pForm.grdFilename.Col = 1: pForm.grdFilename.Text = gvFileNameText temp$ = Dir$(tPath$ & "smarta02.tmp") ' empty if File does not exist If temp$ <> "" Then temp$ = tPath$ & temp$ ' Dir$ does not list subdir. Add if found! pForm.grdFilename.Picture = LoadPicture(temp$) ' set picture to retrived info. Empty if none Else 'grid not established - set grid and rewrite content pForm.grdFilename.BackColor = gvFilenameBackColor pForm.grdFilename.BorderStyle = gvFilenameBorderStyle pForm.grdFilename.Cols = gvFilenameCols pForm.grdFilename.ColWidth(0) = gvFilenameColWidth(0) pForm.grdFilename.ColWidth(1) = gvFilenameColWidth(1) pForm.grdFilename.DragMode = gvFilenameDragmode pForm.grdFilename.Enabled = gvFilenameEnabled pForm.grdFilename.FillStyle = gvFilenameFillstyle pForm.grdFilename.FixedCols = gvFilenameFixedCols pForm.grdFilename.FixedRows = gvFilenameFixedRows pForm.grdFilename.FontBold = gvFilenameFontBold pForm.grdFilename.FontItalic = gvFilenameFontItalic pForm.grdFilename.FontName = gvFilenameFontName pForm.grdFilename.FontSize = gvFilenameFontSize pForm.grdFilename.FontStrikethru = gvFilenameFontstrikethru pForm.grdFilename.FontUnderline = gvFilenameFontUnderline pForm.grdFilename.ForeColor = gvFilenameForecolor pForm.grdFilename.GridLines = gvFilenameGridLines pForm.grdFilename.Height = gvFilenameHeight pForm.grdFilename.HelpContextID = gvFilenameHelpContextID pForm.grdFilename.HighLight = gvFilenameHighlight pForm.grdFilename.Left = gvFilenameLeft pForm.grdFilename.RowHeight(0) = gvFilenameRowHeight(0) pForm.grdFilename.RowHeight(1) = gvFilenameRowHeight(1) pForm.grdFilename.Rows = gvFilenameRows pForm.grdFilename.ScrollBars = gvFilenameScrollbars pForm.grdFilename.Row = 1: pForm.grdFilename.Col = 1: pForm.grdFilename.Text = gvFileNameText pForm.grdFilename.Top = gvFilenameTop pForm.grdFilename.Visible = gvFilenameVisible pForm.grdFilename.Width = gvFilenameWidth temp$ = Dir$(tPath$ & "smarta02.tmp") ' empty if File does not exist If temp$ <> "" Then temp$ = tPath$ & temp$ ' Dir$ does not list subdir. Add it if found! pForm.grdFilename.Picture = LoadPicture(temp$) ' set picture to retrived info. Empty if none End If End Sub
Sub gpTopPanelSave (pForm As Form) 'define the path for temp files tPath$ = App.Path 'put temp files into application directory If Right$(tPath$, 1) <> "\" Then tPath$ = tPath$ & "\" 'terminate path with backslash 'panel proper gvTopPanelAlign = pForm.panToppanel.Align gvTopPanelAlignment = pForm.panToppanel.Alignment gvTopPanelAutosize = pForm.panToppanel.AutoSize gvTopPanelBackColor = pForm.panToppanel.BackColor gvTopPanelBevelInner = pForm.panToppanel.BevelInner gvTopPanelBevelOuter = pForm.panToppanel.BevelOuter gvTopPanelBevelWidth = pForm.panToppanel.BevelWidth gvTopPanelBorderWidth = pForm.panToppanel.BorderWidth gvTopPanelCaption = pForm.panToppanel.Caption gvTopPanelDragMode = pForm.panToppanel.DragMode gvTopPanelEnabled = pForm.panToppanel.Enabled gvTopPanelFloodColor = pForm.panToppanel.FloodColor gvTopPanelFloodPercent = pForm.panToppanel.FloodPercent gvTopPanelFloodShowPct = pForm.panToppanel.FloodShowPct gvTopPanelFloodType = pForm.panToppanel.FloodType gvTopPanelFont3d = pForm.panToppanel.Font3D gvTopPanelFontBold = pForm.panToppanel.FontBold gvTopPanelFontItalic = pForm.panToppanel.FontItalic gvTopPanelFontName = pForm.panToppanel.FontName gvTopPanelFontSize = pForm.panToppanel.FontSize gvTopPanelFontStrikeThru = pForm.panToppanel.FontStrikethru gvTopPanelFontUnderline = pForm.panToppanel.FontUnderline gvTopPanelForeColor = pForm.panToppanel.ForeColor gvTopPanelHeight = pForm.panToppanel.Height gvTopPanelHelpContextID = pForm.panToppanel.HelpContextID gvTopPanelLeft = pForm.panToppanel.Left gvTopPanelMousePointer = pForm.panToppanel.MousePointer gvTopPanelOutline = pForm.panToppanel.Outline gvTopPanelRoundedCorners = pForm.panToppanel.RoundedCorners gvTopPanelShadowColor = pForm.panToppanel.ShadowColor gvTopPanelTabindex = pForm.panToppanel.TabIndex gvTopPanelTag = pForm.panToppanel.Tag gvTopPanelTop = pForm.panToppanel.Top gvTopPanelVisible = pForm.panToppanel.Visible gvTopPanelWidth = pForm.panToppanel.Width 'itemname: Name grid on Panel gvItemNameBackColor = pForm.grdItemname.BackColor gvItemNameBorderStyle = pForm.grdItemname.BorderStyle gvItemNameCols = pForm.grdItemname.Cols gvItemNameDragmode = pForm.grdItemname.DragMode gvItemNameEnabled = pForm.grdItemname.Enabled gvItemNameFillstyle = pForm.grdItemname.FillStyle gvItemNameFixedCols = pForm.grdItemname.FixedCols gvItemNameFixedRows = pForm.grdItemname.FixedRows gvItemNameFontBold = pForm.grdItemname.FontBold gvItemNameFontItalic = pForm.grdItemname.FontItalic gvItemNameFontName = pForm.grdItemname.FontName gvItemNameFontSize = pForm.grdItemname.FontSize gvItemNameFontstrikethru = pForm.grdItemname.FontStrikethru gvItemNameFontUnderline = pForm.grdItemname.FontUnderline gvItemNameForecolor = pForm.grdItemname.ForeColor gvItemNameGridLines = pForm.grdItemname.GridLines gvItemNameHeight = pForm.grdItemname.Height gvItemNameHelpContextID = pForm.grdItemname.HelpContextID gvItemNameHighlight = pForm.grdItemname.HighLight gvItemNameLeft = pForm.grdItemname.Left gvItemNameRows = pForm.grdItemname.Rows gvItemNameScrollbars = pForm.grdItemname.ScrollBars gvItemNameTop = pForm.grdItemname.Top gvItemNameVisible = pForm.grdItemname.Visible gvItemNameWidth = pForm.grdItemname.Width gvItemNameColWidth(0) = pForm.grdItemname.ColWidth(0) gvItemNameColWidth(1) = pForm.grdItemname.ColWidth(1) gvItemNameRowHeight(0) = pForm.grdItemname.RowHeight(0) gvItemNameRowHeight(1) = pForm.grdItemname.RowHeight(1) pForm.grdItemname.Row = 1: pForm.grdItemname.Col = 1 SavePicture pForm.grdItemname.Picture, tPath$ & "smarta01.tmp" gvItemNameText = pForm.grdItemname.Text 'Filename: File grid on Panel gvFilenameBackColor = pForm.grdFilename.BackColor gvFilenameBorderStyle = pForm.grdFilename.BorderStyle gvFilenameCols = pForm.grdFilename.Cols gvFilenameDragmode = pForm.grdFilename.DragMode gvFilenameEnabled = pForm.grdFilename.Enabled gvFilenameFillstyle = pForm.grdFilename.FillStyle gvFilenameFixedCols = pForm.grdFilename.FixedCols gvFilenameFixedRows = pForm.grdFilename.FixedRows gvFilenameFontBold = pForm.grdFilename.FontBold gvFilenameFontItalic = pForm.grdFilename.FontItalic gvFilenameFontName = pForm.grdFilename.FontName gvFilenameFontSize = pForm.grdFilename.FontSize gvFilenameFontstrikethru = pForm.grdFilename.FontStrikethru gvFilenameFontUnderline = pForm.grdFilename.FontUnderline gvFilenameForecolor = pForm.grdFilename.ForeColor gvFilenameGridLines = pForm.grdFilename.GridLines gvFilenameHeight = pForm.grdFilename.Height gvFilenameHelpContextID = pForm.grdFilename.HelpContextID gvFilenameHighlight = pForm.grdFilename.HighLight gvFilenameLeft = pForm.grdFilename.Left gvFilenameRows = pForm.grdFilename.Rows gvFilenameScrollbars = pForm.grdFilename.ScrollBars gvFilenameTop = pForm.grdFilename.Top gvFilenameVisible = pForm.grdFilename.Visible gvFilenameWidth = pForm.grdFilename.Width gvFilenameColWidth(0) = pForm.grdFilename.ColWidth(0) gvFilenameColWidth(1) = pForm.grdFilename.ColWidth(1) gvFilenameRowHeight(0) = pForm.grdFilename.RowHeight(0) gvFilenameRowHeight(1) = pForm.grdFilename.RowHeight(1) pForm.grdFilename.Row = 1: pForm.grdFilename.Col = 1 SavePicture pForm.grdFilename.Picture, tPath$ & "smarta02.tmp" gvFileNameText = pForm.grdFilename.Text End Sub
Sub gpTopPanelItem (pForm As Form, pText As Variant, pImage As Control) pForm.grdItemname.Col = 1: pForm.grdItemname.Row = 1 'choose cell for image AND text 'update image pForm.grdItemname.Picture = pImage.Picture If IsNull(pText) Then 'display default text pForm.grdItemname.BackColor = &HFFFFFF 'white pForm.grdItemname.ForeColor = &HFF& 'red pForm.grdItemname.FontName = "Arial" pForm.grdItemname.FontBold = False pForm.grdItemname.FontSize = 9.75 pForm.grdItemname.Text = "<click> to select an ITEM" ElseIf pText = "" Then 'no text, image only pForm.grdItemname.BackColor = &HFFFFFF 'white pForm.grdItemname.ForeColor = &HFF& 'red pForm.grdItemname.FontName = goListFontName pForm.grdItemname.FontBold = goListFontBold pForm.grdItemname.FontSize = goListFontSize pForm.grdItemname.FontItalic = goListFontItalic pForm.grdItemname.Text = "" Else 'text and image, using character settings from user options ' (go..=gENERAL oPTIONS) pForm.grdItemname.Col = 1: pForm.grdItemname.Row = 1 pForm.grdItemname.Text = pText pForm.grdItemname.BackColor = &H80FFFF pForm.grdItemname.ForeColor = 0 'black pForm.grdItemname.FontName = goListFontName pForm.grdItemname.FontBold = goListFontBold pForm.grdItemname.FontSize = goListFontSize pForm.grdItemname.FontItalic = goListFontItalic End If End Sub