Project


The administrator of the Library system will want to see statistics and information at a glance, or run various reports that provide meaningful summary or detail views of system data. As a programmer, I could try to add every conceivable type of report that the user may need, but I have learned from experience that this is not possible. Users always want the moon, usually in the form of some weird esoteric report that I know they will use once and never look at again (although they will call once a year asking for the same report to be written again). I don't like recompiling and re-releasing the entire application every time a user needs a new report. Instead, I keep the reports outside the application, stored as separate programs. Then, from one form in the main application, I make all of those external reports available in a nice convenient list.

To implement this generic feature, I use a report configuration file, a simple XML file that contains information on the available reports, and how to run them. I want my selection list to have indented items, so that I can visibly group reports for convenience. To do this, I will make my XML file into an unlimited depth hierarchy, with each level representing a further level of displayed indent. For instance, let's say I wanted the following outline of reports (with report group titles in bold).


Detail Reports
   Daily Report
   Monthly Reports
     Monthly Value
     Monthly Inventory
Summary Reports
  Inventory Summary

The XML configuration would follow this structure.

<Group name="Detail Reports">    <Item name="Daily Report"/>    <Group name="Monthly Reports">       <Item name="Monthly Value"/>       <Item name="Monthly Inventory"/>    </Group> </Group> <Group name="SummaryReports">    <Item name="Inventory Summary"/> </Group> 


Of course, this is greatly simplified (not to mention non-compliant) XML. In addition to the hierarchy, I also want to include support for a variety of reporting methods. To keep things simple, the Library project will include three methods of initiating reports.

  1. Built-in Reports. The application includes a limited number of reports that are permanently built in to the main application (assembly). The reports are numbered, starting from 1, and at this time I have five reports in mind. The designer of the XML configuration file can choose to include these in the display of reports or not by simply including or not including them in the file. In the absence of a configuration file, these reports will appear in the list by default. In addition to the report number (1 to 5), each entry has a display text and a long description.

  2. Application Reports. These reports are separate and distinct EXE files, and are started via standard application initiation methods. Each entry includes a display text, the full path to the application, optional arguments, a flag to pass the identity of the user initiating the report, and a long description.

  3. URL Reports. These reports are simply calls to web pages, or any other valid URL. For instance, you could include a report entry that does a "mailto:" to the local organization's help desk. Each entry includes the display text, the URL itself, and a long description.

The project activities in this chapter involve both coding and documentation of the new external resource (the XML file format).

Project Access

Load the "Chapter 13 (Before) Code" project, either through the New Project templates, or by accessing the project directly from the installation directory. To see the code in its final form, load "Chapter 13 (After) Code" instead.


Update Technical Documentation

First, let's add clear documentation on the structure of the XML configuration file. There is no easy way to communicate the structure of an XML file to an ordinary user. Although such documentation is a requirement, hopefully the application will also include a tool to let an administrator build the configuration file. Such a program, sadly, is not included in this book's project. It is left as an exercise for the reader. (I always wanted to say that.)

Report Configuration File

The library application can be configured to run any number of reports through the Reports form. The list of available reports is managed through an XML report configuration file, a file containing "groups" and "items." All items are reports, and appear within a group. You can nest groups within groups to any depth, and the list of reports displayed in the Library program will indent each subordinate group to help the user see the organization of the reports. There is no limit to the nesting of groups.

The root element of the XML file must be named <reportList>, and it may contain any number of <reportGroup> and <reportItem> data elements.

  • <reportItem> Represents a single report entry. This entry has one required attribute, and up to five subordinate data elements depending on the setting of the attribute.

  • type(attribute) Set to one of the following values:

  • built-in Run one of the built-in programs. This type of report uses the <displayText>, <reportPath>, and <description> data elements.

  • program Runs a separate EXE program. This type of report uses the <displayText>, <reportPath>, <reportArgs>, <reportFlags>, and <description> data elements.

  • url Starts a URL, such as a web page or a "mailto" email to a recipient address. This type of report uses the <displayText>, <reportPath>, and <description> data elements.

  • <displayText> A short name or description for this report, as it will appear in the list of report choices. This element is required for all types of reports.

  • <reportPath> The full path, URL, or number of the report, depending on the type of report. For program (EXE) reports, this is the full UNC or driver letter-based path to the report, without additional arguments. For built-in reports, this is a report number, from 1 to 5 (values and their meanings are listed later in this section). For URL reports, this is the actual URL, as in "http://mysite.com/myreport.aspx" or "mailto:helpdesk@mysite.com." This element is required for all types of reports.

  • <reportArgs> For program (EXE) reports, this entry includes any command-line arguments to be included when running the program. This element is only valid for program (EXE) reports, and is always optional.

  • <reportFlags> For program (EXE) reports, this entry indicates the optional flags that should be appended to the application command as arguments. At this time, the only flag is the U flag. When this element is set to U, the argument "-u userid" is appended to the command string (where userid is the user's login ID, from the database field UserName.LoginID). This element is only valid for program (EXE) reports, and is always optional.

  • <description> This is a longer, verbose description of the report, up to about 200 characters, which will appear on the Report form when the user selects the report from the list. This description should assist the user in selecting the right report. This element is valid for all types of reports, but is always optional.

  • <reportGroup> Represents a category group, used to visibly group and indent reports in the display list. This element must contain exactly one <displayText> element, but may contain any number of <reportItem> or <reportGroup> elements.

  • <displayText> A short name or description for this group, as it will appear in the list of report choices. This element is required.

When using the "built-in" report type, the <reportPath> element is set to one of the following integer values.

  • 1Items Checked Out Report

  • 2Items Overdue Report

  • 3Items Missing Report

  • 4Fines Owed by Patrons Report

  • 5Library Database Statistics Report


This technical description appears in the Technical Resource Kit document, originally developed in Chapter 4, "Designing the Database."

Create Report Entry Class

With .NET's ability to store whole objects as ListBox items, we can create a custom class that contains all the information needed to select and run a report from the list of reports. This class is fairly simple, with nothing but basic public fields, plus an overridden ToString function, used by the ListBox control to properly display each list item.

In the Library Project, add a new class file named ReportItem.vb through the Project Add Class menu command. Add the following enumeration to the file, but add it outside the Class . . . End Class boundaries. This enumeration indicates what type of entry each list item represents.

Insert Snippet

Insert Chapter 13, Snippet Item 1.


Public Enum ReportItemEnum    ' ----- The type of item in the report select list.    GroupLabel = 0    BuiltInCheckedOut = 1    BuiltInOverdue = 2    BuiltInMissing = 3    BuiltInFinesOwed = 4    BuiltInStatistics = 5    ExeProgram = 6    UrlProgram = 7 End Enum 


To this same file, add the members of the ReportItem class. This class contains all the information we need to run reports loaded from the configuration file.

Insert Snippet

Insert Chapter 13, Snippet Item 2.


' ----- Instance of report selection items used '       in the ReportSelect form. Public ItemType As ReportItemEnum Public Indent As Integer     ' Indent level. Starts with 0. Public DisplayText As String Public ReportPath As String  ' ExeProgram / UrlProgram only Public ReportArgs As String  ' ExeProgram only Public Description As String Public Overrides Function ToString() As String    ' ----- Display an indented string. Prepend with spaces.    Return StrDup(Indent * 5, " ") & DisplayText End Function 


Design the Report Form

Librarians and administrators use the Select Report form (see Figure 13-2) to view reports. The form includes a ListBox control that displays all reports and report groups, a Run button that starts a report, and a Close button that returns the user to the main form. A label displays the full description of a report, when available, just below the ListBox.

Figure 13-2. The Select Report form


Add a new form file named ReportSelect.vb through the Project Add Windows Form menu command. Add the controls and settings as listed here.

Control/Form

Type

Settings

 

LabelReports

Label

(Name):

LabelReports

  

Location:

8, 8

  

Text:

&Reports

AllReports

ListBox

(Name):

AllReports

  

Location:

8, 24

  

Size:

392, 160

LabelDescription

Label

(Name):

LabelDescription

  

Location:

8, 200

  

Text:

Report Description

FullDescription

Label

(Name):

FullDescription

  

AutoSize:

False

  

Location:

32, 224

  

Size:

368, 64

  

Text:

Report not selected.

  

UseMnemonic:

False

ActRun

Button

(Name):

ActRun

  

DialogResult:

None

  

Location:

232, 304

  

Size:

80, 24

  

Text:

Run

ActClose

Button

(Name):

ActClose

  

DialogResult:

Cancel

  

Location:

320, 304

  

Size:

80, 24

  

Text:

Close

ReportSelect

Form

(Name):

ReportSelect

  

AcceptButton:

ActRun

  

CancelButton:

ActClose

  

ControlBox:

False

  

FormBorderStyle:

FixedDialog

  

StartPosition:

CenterScreen

  

Text:

Library Reports


Adjust the tab order of the new controls by selecting the form, and then using the View Tab Order menu command.

Although the administrator has probably given useful names to each report, the terseness of each report name may still confuse the user. Each report includes an optional full description. As the user selects reports from the list, an event handler updates the FullDescription label just below the main list. Add this event handler member to the class.

Insert Snippet

Insert Chapter 13, Snippet Item 3.


Private Sub AllReports_SelectedIndexChanged( _       ByVal sender As Object, ByVal e As System.EventArgs) _       Handles AllReports.SelectedIndexChanged    ' ----- Display a description of the report, if available.    Dim reportEntry As Library.ReportItem    ' ----- Clear any previous description.    FullDescription.Text = "No report selected."    If (AllReports.SelectedIndex <> -1) Then       ' ----- Locate the content and display it.       reportEntry = CType(AllReports.SelectedItem, _          Library.ReportItem)       FullDescription.Text = reportEntry.Description    End If End Sub 


Populate Reports from Configuration File

The RefreshReportList method loads the data from the report configuration file, and processes the results. Eventually, the location of this file will be recorded in the application's configuration file, but we won't be adding that until a later chapter. For now, let's put in a hard-coded test file location, and mark it for later update.

Insert Snippet

Insert Chapter 13, Snippet Item 4.


Private Sub RefreshReportList()    ' ----- Load in the list of available reports.    Dim configFile As String    Dim configData As Xml.XmlDocument    Dim reportEntry As ReportItem    Dim counter As Integer    On Error GoTo ErrorHandler    ' ----- Clear the existing list.    AllReports.Items.Clear()    ' ----- Get the location of the configuration file.    ' TODO: Load this from the application's configuration.    '       For now, just hard-code the value.    configFile = "c:\ReportConfig.txt"    ' ----- Load the configuration file.    If (configFile <> "") Then       If (System.IO.File.Exists(configFile)) Then          ' ----- Load in the file.          configData = New Xml.XmlDocument          configData.Load(configFile)          ' ----- Process the configuration file.          LoadReportGroup(configData.DocumentElement, 0)       End If    End If    ' ----- If the configuration file resulted in no reports    '       appearing in the list, add the default reports.    If (AllReports.Items.Count = 0) Then       For counter = 1 To _             CInt(ReportItemEnum.BuiltInStatistics)          ' ----- Build the report entry.          reportEntry = New ReportItem          reportEntry.Indent = 0          reportEntry.ItemType = CType(counter, ReportItemEnum)          Select Case reportEntry.ItemType             Case ReportItemEnum.BuiltInCheckedOut                reportEntry.DisplayText = "Items Checked Out"             Case ReportItemEnum.BuiltInOverdue                reportEntry.DisplayText = "Items Overdue"             Case ReportItemEnum.BuiltInMissing                reportEntry.DisplayText = "Items Missing"             Case ReportItemEnum.BuiltInFinesOwed                reportEntry.DisplayText = "Patron Fines Owed"             Case ReportItemEnum.BuiltInStatistics                reportEntry.DisplayText = "Database Statistics"          End Select          ' ----- Add the report entry to the list.          AllReports.Items.Add(reportEntry)       Next counter    End If    Return ErrorHandler:    GeneralError("ReportSelect.RefreshReportList", _       Err.GetException())    Resume Next End Sub 


Because the report configuration file allows nested report groups to any level, we need to use a recursive routine to repeatedly descend to each successive level. The LoadReportGroup routine, called by RefreshReportList, adds all report items and report groups within a starting report group. It's initially called from the reference point of the root <reportList> element. Each time that it finds a child <reportGroup> element, it calls itself again, but this time starting from the reference point of the child <reportGroup> element.

Insert Snippet

Insert Chapter 13, Snippet Item 5.


Private Sub LoadReportGroup(ByVal groupNode As Xml.XmlNode, _       ByVal indentLevel As Integer)    ' ----- Add the groups and items at this level,    '       and recurse as needed.    Dim scanNode As Xml.XmlNode    Dim detailNode As Xml.XmlNode    Dim reportEntry As ReportItem    ' ----- Process each item or group.    For Each scanNode In groupNode.ChildNodes       ' ----- Build a content item for the list.       reportEntry = New ReportItem       reportEntry.Indent = indentLevel       ' ----- Get the display name.       detailNode = scanNode.SelectSingleNode("displayText")       If (detailNode Is Nothing) Then Continue For       reportEntry.DisplayText = Trim(detailNode.InnerText)       If (scanNode.Name = "reportGroup") Then          ' ----- Start a new display group.          reportEntry.ItemType = ReportItemEnum.GroupLabel          AllReports.Items.Add(reportEntry)          ' ----- Recurse to child items.          LoadReportGroup(scanNode, indentLevel + 1)       ElseIf (scanNode.Name = "reportItem") Then          ' ----- This is an item. Record its location.          detailNode = scanNode.SelectSingleNode("reportPath")          If Not (detailNode Is Nothing) Then _             reportEntry.ReportPath = _             Trim(detailNode.InnerText)          ' ----- Get any command-line arguments.          detailNode = scanNode.SelectSingleNode("reportArgs")          If Not (detailNode Is Nothing) Then _             reportEntry.ReportArgs = _             Trim(detailNode.InnerText)          ' ----- Get any item-specific flags.          detailNode = scanNode.SelectSingleNode("reportFlags")          If Not (detailNode Is Nothing) Then             ' ---- "U" adds "-u loginid" to the command.             If (InStr(UCase(detailNode.InnerText), "U") > 0) _                And (LoggedInUserName <> "") Then _                reportEntry.ReportArgs = _                Trim(reportEntry.ReportArgs & " -u " & _                LoggedInUserName)          End If          ' ----- Store the full description.          detailNode = scanNode.SelectSingleNode("description")          If Not (detailNode Is Nothing) Then _             reportEntry.Description = _             Trim(detailNode.InnerText)          ' ----- So, what type of entry is it?          If (scanNode.Attributes("type").Value = _                "built-in") Then             ' ----- Built-in program. Check for valid ID.             If (IsNumeric(reportEntry.ReportPath) = False) Or _                (Val(reportEntry.ReportPath) < 1) Or _                (Val(reportEntry.ReportPath) > _                CInt(ReportItemEnum.BuiltInStatistics)) Then _                Continue For             reportEntry.ItemType = CType(CInt( _                reportEntry.ReportPath), ReportItemEnum)             AllReports.Items.Add(reportEntry)          ElseIf (scanNode.Attributes("type").Value = _                "program") Then             ' ----- EXE program-based report.             If (reportEntry.ReportPath = "") Then Continue For             reportEntry.ItemType = ReportItemEnum.ExeProgram             AllReports.Items.Add(reportEntry)          ElseIf (scanNode.Attributes("type").Value = _                "url") Then             ' ----- URL-based report.             If (reportEntry.ReportPath = "") Then Continue For             reportEntry.ItemType = ReportItemEnum.UrlProgram             AllReports.Items.Add(reportEntry)          End If       End If    Next scanNode    Return ErrorHandler:    GeneralError("ReportSelect.LoadReportGroup", _       Err.GetException())    Resume Next End Sub 


Add the form's Load event, which loads in the content from the configuration file.

Insert Snippet

Insert Chapter 13, Snippet Item 6.


Private Sub ReportSelect_Load(ByVal sender As Object, _       ByVal e As System.EventArgs) Handles MyBase.Load    ' ----- Display the list of reports.    RefreshReportList() End Sub 


Running the Reports

Now that all of the groups and items appear in the list, we have to run the actual reports. The ActRun button's Click event handles this duty. For now, we will just add the framework to support the calling of each report. The built-in reports will be added in Chapter 20, "Reporting."

Insert Snippet

Insert Chapter 13, Snippet Item 7.


Private Sub ActRun_Click(ByVal sender As System.Object, _       ByVal e As System.EventArgs) Handles ActRun.Click    ' ----- Run the selected report.    Dim reportEntry As Library.ReportItem    On Error GoTo ErrorHandler    ' ----- Make sure a report is selected.    If (AllReports.SelectedIndex = -1) Then       MsgBox("Please select a report from the list.", _          MsgBoxStyle.OKOnly Or MsgBoxStyle.Exclamation, _          ProgramTitle)       Return    End If    ' ----- Different code for each type of entry.    reportEntry = CType(AllReports.SelectedItem, _       Library.ReportItem)    Me.Cursor = Windows.Forms.Cursors.WaitCursor    Select Case reportEntry.ItemType       Case ReportItemEnum.GroupLabel          ' ----- No report for group entries.          MsgBox("Please select a report from the list.", _             MsgBoxStyle.OKOnly Or MsgBoxStyle.Exclamation, _             ProgramTitle)       Case ReportItemEnum.BuiltInCheckedOut          ' ----- Items Checked Out          ' TODO: Write BasicReportCheckedOut()       Case ReportItemEnum.BuiltInOverdue          ' ----- Items Overdue          ' TODO: Write BasicReportOverdue()       Case ReportItemEnum.BuiltInMissing          ' ----- Items Missing          ' TODO: Write BasicReportMissing()       Case ReportItemEnum.BuiltInFinesOwed          ' ----- Fines Owed by Patrons          ' TODO: Write BasicReportFines()       Case ReportItemEnum.BuiltInStatistics          ' ----- Library Database Statistics          ' TODO: Write BasicReportStatistics()       Case ReportItemEnum.ExeProgram          ' ----- Start a program.          Process.Start("""" & reportEntry.ReportPath & _             """ " & reportEntry.ReportArgs)       Case ReportItemEnum.UrlProgram          ' ----- Start a URL.          Process.Start(reportEntry.ReportPath)    End Select    Me.Cursor = Windows.Forms.Cursors.Default    Return ErrorHandler:    GeneralError("ReportSelect.ActRun_Click", _       Err.GetException())    Resume Next End Sub 


For external reports, the event handler calls the Process.Start method. This amazing method accepts either a standard command-line expression, or any valid URL or web page address.

Connecting the Select Report Form

To make the reports available to the user, we must enable a link to the report form from the main form. We included a distinct panel on that form just for printing reports. The ActDoReports button on that panel triggers a call to the new report selection form. Create a new event handler for the ActDoReports button and add the following code.

Insert Snippet

Insert Chapter 13, Snippet Item 8.


' ----- Show the reports form. ReportSelect.ShowDialog() 


Now that we have a firm grasp on the world of XML, we'll let Visual Basic do all the hard work of manipulating it for application configuration purposes.




Start-to-Finish Visual Basic 2005. Learn Visual Basic 2005 as You Design and Develop a Complete Application
Start-to-Finish Visual Basic 2005: Learn Visual Basic 2005 as You Design and Develop a Complete Application
ISBN: 0321398009
EAN: 2147483647
Year: 2006
Pages: 247
Authors: Tim Patrick

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net