Вы находитесь на странице: 1из 68

TABLE OF CONTENTS

Features
8 Walking on Cloud 9.0 of Visual FoxPro
So just what was the Visual FoxPro team thinking when they planned Visual FoxPro 9.0? Here is what they wanted to accomplishstraight from the VFP team. Microsoft Visual FoxPro Development Team

44 Extending the Visual FoxPro 9.0 Reporting System


The new reporting engine splits responsibility for reporting between the report engine, which now deals only with data handling and object positioning, and a new object known as a report listener, which handles rendering and output. Doug Hennig

12 Whats New with Data in Visual FoxPro 9.0


With support for new data types and removing many limits from the SQL language, it's now easier to develop a single code base that works with the Visual FoxPro 9.0 native data engine and SQL Server. David T. Anderson

54 Visual FoxPro 9.0 IDE Enhancements


A little bit here and a little bit there make Visual FoxPro 9.0 even more productive. Rod provides an overview of the latest IDE enhancements to your favorite tool. Rod Paddock

22 The Visual FoxPro 9.0 Report Writer


Explore the new features of the Report Writer, including dealing with legacy data, report protection, user interface enhancements, layout objects, and data groups, plus the great new multiple detail band feature. Cathy Pountney

56 Interop: Making .NET and Visual FoxPro Talk to Each Other


Theres no doubt that many Visual FoxPro developers need to live in a world that supports older applications and new development architectures. But why rewrite business rules that already work? Our writers offer the highlights to make it all work together. Claudio Lassala, Markus Egger, Rod Paddock

36 Controls, Events, Commands, and More


The new productivity tools help you build interesting and useful applications with just a few carefully chosen bits of code. Claudio Lassala

Departments
6 CoDe Compilers 41 Advertisers Index

CoDe Focus Magazine is a supplement of Code Component Developer Magazine. Code Focus reports in-depth on newly released specific technologies. The supplement is available to CoDe Component Developer Magazine subscribers that have noted interest in the specific technologies covered in the Code Focus supplements. Subscribe to Code Component Developer Magazine US subscriptions are US $29.99 for one year; Canada, Mexico, and US territories are US $44.99; Europe is US $49.99; other countries, US $59.99. Payments should be made in US dollars drawn on a US bank. American Express, MasterCard, Visa, and Discover credit cards are accepted. Bill me option is $5 additional and available only for US subscriptions. Back issues are available. For subscription information, email subscriptions@code-magazine.com or contact customer service at 832-717-4445 ext 10. Subscribe online at www.code-magazine.com CoDe Component Developer Magazine (ISSN # 1547-5166) is published bimonthly by EPS Software Corporation, 6605 Cypresswood Drive., Suite 300, Spring, TX 77379. POSTMASTER: Send address changes to CoDe Component Developer Magazine, 6605 Cypresswood Drive., Suite 300, Spring, TX 77379.

Table of Contents

www.code-magazine.com

CODE COMPILERS

Volume 2 Issue 1 Group Publisher Markus Egger Associate Publisher Rick Strahl Editor-in-Chief Rod Paddock Managing Editor Ellen Whitney Content Editor Melanie Spiller Writers In This Issue David T. Anderson Markus Egger Doug Hennig Claudio Lassala Ken Levy Cathy Pountney Rod Paddock Technical Reviewers Markus Egger Rod Paddock Art & Layout King Laurin GmbH Friedl.raffeiner@tin.it Production

Franz Wimmer King Laurin GmbH 39057 St. Michael/ Eppan, Italy
Printing

Fry Communications, Inc. 800 West Church Rd. Mechanicsburg, PA 17055


Advertising Sales Vice President, Sales and Marketing

Michelle Yates 703-328-0333


myates@code-magazine.com Sales Managers

Erna Egger +43 (664) 151 0861


erna@code-magazine.com

Tammy Ferguson 832-717-4445 ext 26


tammy@code-magazine.com Circulation & Distribution

General Circulation: EPS Software Corp.


Subscriptions CoDe Focus Magazine is a supplement of Code Component Developer Magazine. Code Focus reports indepth on newly released specific technologies. The supplement is available to CoDe Component Developer Magazine subscribers that have noted interest in the specific technologies covered in the Code Focus supplements. Subscriptions Manager

Cleo Gaither 832-717-4445 ext 10


subscriptions@code-magazine.com Subscribe to CoDe Component Developer Magazine US subscriptions are US $29.99 for one year; Canada, Mexico & US territories are US $44.99; Europe is US $49.99; other countries are US $59.99. Payments should be made in US dollars drawn on a US bank. American Express, MasterCard, Visa, and Discover credit cards accepted. Bill me option is $5 additional and available only for US subscriptions. Back issues are available. For subscription information, email subscriptions@code-magazine.com or contact customer service at 832-717-4445 ext 10. Subscribe online at www.code-magazine.com CoDe Component Developer Magazine EPS Software Corporation / Publishing Division 6605 Cypresswood Drive, Ste 300, Spring, Texas 77379 Phone: 832-717-4445 Fax: 832-717-4460

QUICK ID 0404012

Walking on Cloud 9.0 of Visual FoxPro


The Visual FoxPro Team at Microsoft is enthused [sic] to announce Visual FoxPro 9.0 Wed like to include some details as an overview to the more detailed articles in this special CoDe Focus issue devoted to Visual FoxPro 9.0. Visual FoxPro 9.0 Overview
Visual FoxPro 9.0 is fully compatible with previous versions of Visual FoxPro. With its local cursor engine, tight coupling between language and data, and powerful features, Visual FoxPro 9.0 is a great tool for building database solutions of all sizes. Its data-centric, object-oriented language offers developers a robust set of tools for building database applications for the desktop, client-server environments, or the Web. Developers will have the necessary tools to manage datafrom organizing tables of information, running queries, and creating an integrated relational database management system (DBMS) to programming a fully-developed data management application for end users.

Visual FoxPro 9.0 Goals


Our goals for Visual FoxPro 9.0 were to: Maintain backward compatibility Enhance database language and types Add end user UI features Increase developer productivity Improve the Report Writer significantly Extend .NET and SQL Server interoperability Increase extensibility, including Xbase source code

with the flexibility to build all types of database solutions, move on to reporting system enhancements, and finish up with data handling and interoperability. But these only touch on some the great new features youll want to explore on your own.

Flexibility to Build All Types of Database Solutions


Using Visual FoxPro 9.0, you can create .NET-compatible solutions with hierarchical XML and XML Web services. You can also exchange data with SQL Server through enhanced SQL language capabilities and newly supported data types. You can build and deploy standalone and remote applications for Windows-based Tablet PCs. You can create and access COM components and XML Web services that are compatible with .NET technology. Visual FoxPro 9.0 allows you to build end-to-end solutions, from data entry forms to complex report outputs.

Visual FoxPro 9.0 Product Highlights


There are so many new features that its hard to limit the discussion. Well start

Reporting System Features


To Visual FoxPros already strong reporting system, weve added an extensible new output architecture that provides precision control of report data output and formatting. Theres multiple-detail band support for data with multiple one-to-many relationships. You can customize the Print Preview window with improved display quality and multiple page support. Our new output reports support XML, HTML, image formats, and customizable multi-page print preview windows. The enhanced report writer is backward compatible with existing Visual FoxPro reports. And flexible report chaining allows for more complex print jobs.

Figure 1: Pictured here are some of the Visual FoxPro team members (from left to right): Ken Levy, John Koziol, Yair Alan Griver, Calvin Hsia, Randy Brown, Richard Stanton, and Mike Stewart.

Walking on Cloud 9.0 of Visual FoxPro

www.code-magazine.com

The new ReportListener class provides access to report generation and rendering events at runtime. Powerful design-time hooks with customizable builders make your development experience smoother.

Common Questions
As for what will be added to Visual FoxPro after version 9.0 is released, it is too early to discuss this at the time of writing this article. The Visual FoxPro team welcomes feedback on how we can improve our developer tools in the future based on what is disclosed for Visual FoxPro 9.0 and Visual Studio 2005. Details of how we enhance Visual FoxPro, how it is packaged, what it is called, and when updates will be released probably will not be disclosed until the first half of 2005. A common question is how Visual FoxPro 9.0 relates to Visual Studio 2005 and SQL Server 2005. We improved the XML support in Visual FoxPro 9.0 which will also improve .NET interoperability. We also added some new data types that will also help for use with both SQL Server 2000 and SQL Server 2005. Visual FoxPro 9.0 is scheduled to be released in late 2004, and the schedule for Longhorn is not announced yet. We can only discuss how Visual FoxPro relates to Longhorn once Longhorn is nearly complete and close to being released. Although we will not be including native PDF support in Visual FoxPro 9.0, we are working to make it very easy for third parties and FoxPro developers to create a variety of solutions for add-on PDF output support. We do not have any plans to extend the 2GB database size limit in Visual FoxPro for many reasons, including the 32-bit architecture that already exists within the product. Besides using SQL Server 2000, an additional option is upcoming: SQL Server 2005 Express extends the 2GB limit of MSDE to 4GB total. Although Visual FoxPro will remain 32-bit and will not use 64-bit addressing natively, it will run in 32-bit compatibility mode. Visual Studio 2005 will support creating native 64bit applications. It is up to developers and installing organizations to decide which .NET language is best for them. C# is generally for developers who like to write and control all of their code. Its very source code-centric. Visual Basic is for RAD (rapid application development) and ease-of-use while providing access to the full power of the .NET Framework. For a highlevel overview of the language differences, refer to the Visual Studio Roadmap at http://msdn.microsoft.com/vstudio/productinfo/roa dmap.aspx.

download at http://msdn.com/vfoxpro. The beta can be installed side by side with Visual FoxPro 9.0 or previous versions of Visual FoxPro. There is a time bomb built into the beta that expires March 31st, 2005. Do not contact technical support for questions, as the public beta is unsupported and is to be used as is. Visual FoxPro 9.0 is scheduled to be released to manufacturing near the end of 2004 and will be included in all levels of MSDN Subscriptions (Universal, Enterprise, and Professional). For more information about MSDN Subscriptions, refer to http://msdn.com/subscriptions/prodinfo/levels.asp. MSDN Subscription members will be first to obtain the released version of Visual FoxPro 9.0 (within a few business days of release to manufacturing). The full product, upgrades, and academic editions will be available approximately six to eight weeks after they are released to manufacturing. Visual FoxPro 9.0 pricing will be the same as it was for Visual FoxPro 8.0: the full product is $649US, the upgrade is $349US, and the academic edition is $75US. For a limited time, a $50 rebate will be offered with Visual FoxPro 9.0 (for the upgrade version only, in U.S. and Canada only) for licensed users of Visual FoxPro 8.0. To obtain Visual FoxPro, MSDN Subscriptions, FoxPro gear, and more, go to http://FoxToolbox.com. Additional information about Visual FoxPro 9.0 including white papers, samples, and links to third-party resources can be found at http://msdn.com/vfoxpro.

Data-Handling and Interoperability


Three new data types, VarChar, VarBinary, and BLOB, allow for improved interoperability with SQL Server. There are extended SQL language enhancements as well, including more capabilities with SELECT, INSERT, UPDATE, and DELETE statements. A new function, CAST(), allows you to convert different data types. You can use the new binary index to improve performance using the deleted tag, and the XMLAdapter provides improved nested hierarchical XML and XSD schema support. There are many improvements to client UI features. You can dock forms, anchor form elements to control movement on a form during resizing, and control the position of images on a button using text alignment. Theres new word wrap support for checkbox captions and rotating text for label captions. Using shapes and lines, you can create polygons and Bezier curves. The property sheet provides support for new font and color display options, extended characters, and long expressions. You can bind images to non file-based pictures. You can use List and Combo collections as databinding row sources. Member data extensibility adds the ability to specify custom property editors and favorites. With the extended system capabilities, there are no limits beyond available memory for arrays, procedure size, and nesting levels. Single line background compiling allows you to see whether or not your syntax is valid as you type command lines. There are new and improved task panes such as the Data Explorer pane. There are new string functions and a new inline functionICASE()that is similar to DO CASE statements. For international solutions weve added greater support for using FontCharSets in applications.

Visual FoxPro 9.0 System Requirements


To install Microsoft Visual FoxPro 9.0 Professional, we recommend: A PC with a Pentium-class processor Windows 2000 with Service Pack 2 or later operating system 64MB of RAM; 128MB or higher recommended 165MB of available hard disk space for a typical installation, 165MB maximum; 20MB of additional hard-disk space for Microsoft Visual FoxPro 9.0 Prerequisites A CD-ROM or DVD-ROM drive A super VGA 800 X 600 resolution monitor, or higher, with 256 colors Microsoft Mouse or compatible pointing device

Visual FoxPro 9.0 Availability


Until March 31st, 2005, the free public beta of Visual FoxPro 9.0 will be available for

10

Walking on Cloud 9.0 of Visual FoxPro

www.code-magazine.com

QUICK ID 0404022

What's New with Data in Visual FoxPro 9.0?


David T. Anderson
David is currently serving as a contract tester with the Visual FoxPro team at Microsoft, testing the data enhancements made to Visual FoxPro 9.0 and implementing performance benchmarks such as the Transaction Processing Council's TPC-H. With 22 years of application development experience in the PC industry for government, military, educational institutions, and private industry, David uses his experience relating Enterprise Architecture and Organizational Maturity to assist organizations in determining the most appropriate software processes for their needs. You can reach David at
dta@davidtanderson.com

The Microsoft Visual FoxPro team has a great reputation for responding to community requests and the next version is no exception. Most evident in several changes to the data engine, Visual FoxPro 9.0 includes many enhancements for data access and database application development.

isual FoxPro 9.0 contains more functional enhancements to the data engine than any release since 3.0. From the new and extended functionality in SQL to new data types and a binary index, this release demonstrates the power of a mature development platform for data-centric solutions. Fast Changes to the data engine can be summarized in five major areas:

SQL Enhancements

Certainly the best word to describe changes to the SQL sub-language is MORE! There are no longer hard-coded limits to the number of elements in an SQL statement. A single SQL SELECT statement can contain Facts more tables, more JOINs, more Changes to the data engine in sub-queries, more nested subqueries and more UNIONs Visual FoxPro 9.0 provide than in previous versions. developers with enhanced power,

There are also no hard-coded SQL Enhancements: Removal From the greatly expanded SQL limits on the number of items of most hard-coded limits, sub-language to new data types and in an SQL IN list. In versions enhanced sub-query and Rushmore optimizations, this prior to Visual FoxPro 9.0, correlation support, support release demonstrates the FoxPro SQL IN was mapped to the for more complex expresINLIST() function; that depensions, and enhanced UNION teams commitment to producing dency has been removed. This support. a world-class data-centric change allows an increase in Performance: A new index development language. the number of arguments for type, enhanced performance IN and for better optimization. of filtered indexes and Visual FoxPro 9.0 stops evaluating expressions improved SQL performance with TOP n, from the list as soon as the match is found. This is MIN()/MAX(), and LIKE. helpful if the IN condition is not Rushmore-opti Commands and Functions: Greater ability to mized, as performance can be improved by placing fine-tune how data is accessed and committed, the most-likely-to-match values at the beginning of functions to supplement SQL showplan, and the list. The total number of items is still indirectly easier immediate expression nesting with limited by the SYS(3055) function, which controls ICASE(). buffer memory size, so the higher the setting, the New Data Types: Support for VarChar, more items are supported via IN. (See the VarBinary, and BLOB data types, a new CAST() Common Error Messages sidebar for more inforfunction, and enhancements to existing functions mation.) to control and convert data types. Remote Data: Enhanced control over transactions, better visibility regarding fetched records, No Limits? rowsets returned from the provider, and No hard-coded limits does not mean limitless. CursorAdapter enhancements that bring behavior Issues such as available memory and expression in line with remote views. complexity can still have an impact on whether or not a very long and complex statement can be run, Many of these changes improve the Visual FoxPro but you'll have to work hard to find many real9.0 client/server story by providing stronger interworld limitations. operability with SQL Server. With support for new data types and removing many limits from the SQL language, it's now easier to develop a single code Enhanced Sub-Query Support base that works with the Visual FoxPro 9.0 native Sub-queries have always been powerful in the SQL data engine and SQL Server. language. They can be used as filters by placing them on the right side of a comparison in the Enough overview, let's dig in!

flexibility, and performance.

12

What's New with Data in Visual FoxPro 9.0

www.code-magazine.com

WHERE clause. In Visual FoxPro 9, sub-queries can also be used as part of the SELECT list (called a projection) and in the FROM clause (often called a derived table). When used as a projection, if the sub-query doesn't return any records, a NULL value is returned. Correlation is supported with projection (more on this in a minute). A SQL statement that uses projection looks like this:
SELECT ; C.CustomerID, ; C.CompanyName, ; (SELECT YTD_Sales FROM Sales_02 WHERE C.CustomerID = Sales_02.CustomerID) (SELECT YTD_Sales FROM Sales_03 WHERE C.CustomerID = Sales_03.CustomerID) (SELECT YTD_Sales FROM Sales_04 WHERE C.CustomerID = Sales_04.CustomerID) FROM Customers C

A single SQL SELECT statement can contain more tables, more JOINs, more sub-queries, more nested sub-queries, and more UNIONs than ever before.
Consider the following example:
SELECT ; C.customerid, ; P.product_count AS p_count; FROM Customers C, ; (SELECT c2.customerid, ; COUNT(DISTINCT D.productID) AS p_count ; FROM Customers C2 ; INNER JOIN Orders O ; ON C2.customerid = O.customerid ; INNER JOIN OrderDetails D ; ON O.orderid = D.orderid ; GROUP BY c2.customerid) AS P ; WHERE C.customerID = p.customerID ; AND P.p_count >= ; (SELECT (COUNT(*)*.50) FROM Products) ; ORDER BY p.product_count DESC

; AS Y02,; ; AS Y03,; ; AS Y04 ;

Common Error Messages


As your SQL statements get longer and more complex, you will become familiar with the following errors: Error # 18: Line is too long Error # 1812: SQL: Statement is too long Error# 1814:Queries of this type are not supported. Error# 1845: Too Complex Error# 2189: SQL: Too many fields in final or intermediate result have the same name. If you dynamically build SQL, you may want to consider adding traps for these errors so that you can isolate them for analysis. SYS(3055) If you are not bound by a low memory environment, consider using SYS(3055) to set the complexity level of the FOR and WHERE clauses to its maximum value of 2040 when you set up the environment for your applications. This allows more complex SQL statements and more values with IN. See the Help files for a list of other commands and functions that are affected by this setting.

This SELECT statement returns the customer ID and company name along with year-to-date sales from warehoused tables for the last three fiscal years. A restriction on a projection is that the sub-query should return only one column and no more than one record for each record in the containing SELECT. Another valuable use of a projection is when its used as part of an expression.
SELECT ; C.customerID, ; C.companyname, ; SUM(D.quantity*D.unitprice) AS CustTotal ,; (SUM(D.quantity*D.unitprice) / ; (SELECT SUM((quantity*unitprice)-discount) ; FROM OrderDetails D2) ; )*100 AS PctTotal ; FROM Customers C ; INNER JOIN Orders O ; ON C.customerID = O.customerID ; INNER JOIN OrderDetails D ; ON O.orderid = D.orderid ; GROUP BY C.customerID, C.companyname, O.orderID ; ORDER BY pctTotal DESC

This SELECT statement returns customer ID and product count for all customers who have purchased at least 50% of the product line. Notice that the derived table has an alias of "P" that is designated the same way you would alias a column, using the AS clause (required). It's also important to note that the sub-query can be complex (in this case, joining to two other tables) and that the results from the derived table can be used as a condition of the WHERE clause and in the ORDER BY of the top-most SELECT. Unlike a projection, the derived sub-query can return more than one column and more than one record. It cannot be correlated. All sub-selects are executed before the top-most SELECT is evaluated. Sub-queries are also supported in the SET list of a SQL UPDATE statement. Only one sub-query is allowed in a SET clause and if there is a sub-query in the SET clause, a sub-query in the WHERE clause is not allowed.

This SELECT statement returns customer ID, company name, total sales, and a percent of total sales against all customer sales. Note that the sub-query in the SELECT list is part of a complex expression that includes an aggregate function. Now that's flexibility! A derived table as sub-query allows you to treat the results of a sub-query as though it were its own table.

Better Correlation Support


The SQL UPDATE and SQL DELETE commands now support correlation. A correlated statement includes a FROM clause to relate the records being affected to another table.

www.code-magazine.com

What's New with Data in Visual FoxPro 9.0

13

Many of these changes improve FoxPro's client/server capabilities by providing stronger interoperability with SQL Server.
For example:
DELETE FROM WHERE AND products ; mfg ; mfg.productID = products.productID; mfg.discontinued = .t.

Also, with the hard-coded limits of SQL IN removed, the Designers no longer convert IN to INLIST(). The INLIST() function still has a limit of 24 elements.

Enhanced UNION Support


In addition to having no hard-coded limits for the number of UNIONs, you can now use a UNION inside the result set that is used by an INSERT INTO. You can now also ORDER BY <fieldname> when using UNION. The referenced field must be present in the SELECT list for the last SELECT in the UNION.

John Koziol
Test Engineer, Visual FoxPro Team Microsoft An overall area of enhancements for Visual FoxPro 9.0 includes more event hooks and fewer capacity limitations. You can now hook into Windows API events with the BINDEVENTS() function. Now only limited to available memory, you can create arrays larger than 64K and nest DOs deeper than 128 levels. Also, many of the limits on clauses in SQL statements have been extended. You can write your own report handlers and enhance the Visual FoxPro interface via MemberData. Visual FoxPro 9.0 is a great upgrade for Visual FoxPro developers who want to have fewer limits in building applications!

Performance
This DELETE statement deletes all products marked as discontinued from a table provided by the manufacturer. A correlated UPDATE looks similar:
UPDATE SET FROM WHERE products ; unitprice = mfg.msrp *.90 ; mfg ; mfg.productID = products.productID

Regardless of whether you are doing remote data access or relying on the powerful native data engine, performance has always been a priority for Visual FoxPro. Visual FoxPro 9.0 enhances the data engine even further.

Binary Indexes
This new index type is a specialized index, designed for one thing:
INDEX ON DELETED() TAG DELETED BINARY

This UPDATE statement sets the unit price for a product at 90% of the manufacturers suggested retail price. You may be tempted to use a correlated sub-query as this is also supported. Just be aware that when using a sub-query it's like doing an outer join. For every record that is not found in the sub-query, the value returned is NULL. This may not give the desired results.
UPDATE products ; SET unitprice = ; (SELECT ( msrp *.90 ) ; FROM mfg ; WHERE mfg.productID = products.productID)

The new index type can be used with any NOT NULL logical expression. Other restrictions preclude the use of a FOR expression and ASCENDING, DESCENDING, UNIQUE, and CANDIDATE keywords. SET ORDER TO is not supported and the INDEX ON command sets the current order to 0. Also, you cannot use a Binary index with any Seek operation. The big advantage of a Binary index is its size. A Binary index for a table with 8,000,000 records is approximately 30 times smaller (1.1MB versus 31.5MB). Smaller means faster I/O and faster APPEND and REPLACE, all with the same Rushmore optimization as a non-binary index on the same expression. There is a trade-off to consider. Rushmore optimization is faster if the amount of records returned is more than 3% of the total records (about 92% faster when all records match the condition). However, Rushmore optimization is slower if the amount of records returned is less than 3% (about two times slower when 0 records match the condition). It is likely that the 3% threshold will become smaller as the number of records in the table increases. Turning your DELETED indexes into Binary indexes is an easy way to start taking immediate advantage of Visual FoxPro 9.0 performance enhancements. Just be sure that all clients accessing

This UPDATE statement sets the unit price for a product at 90% of the manufacturers suggested retail price for every product found in the Manufacturers table. The price for products not found in the Manufacturers table is set to NULL. Note that this statement operates on every record in the Products table; in the previous statement, only updated records that matched in the Manufacturers table were involved.

View and Query Designers


Unfortunately, due to the complexity of the SQL statements you can write with these enhancements, the Query and View Designers do not support many of the sub-query changes to SQL.

14

What's New with Data in Visual FoxPro 9.0

www.code-magazine.com

your data are upgraded, as this new index cannot be read by prior versions.

currently deploy. If you are unable to use a binary index, you may find that with these optimizations, you can at least drop a few existing indexes. If only indexes filtered FOR NOT DELETED() were used for Rushmore optimization and SET DELETED is ON, additional NOT DELETED() optimization is unnecessary.

Rushmore Optimizations
There are a few new Rushmore optimizations that do not require changes to data and index structures. Top N [PERCENT], an optimization made to SQL, provides improved performance. This operation returns only the top number or percent of records in a result set as controlled in the ORDER BY clause. This change in Visual FoxPro 9.0 eliminates records from the sort process that don't belong in TOP N as early as possible, reducing comparison operations and decreasing file I/O in low memory situations. This also has the side-effect of only returning exactly N [PERCENT] records. In previous versions, if there was a tie for Nth place, all records that matched the tie were included, resulting in getting back more than N records. If this change in behavior is not desired, consider bracketing the SQL call with SET ENGINEBEHAVIOR 80. The only limitation to this optimization is that TOP N PERCENT cannot be used unless the entire result set can be read into memory at once. When appropriate, Visual FoxPro 9.0 uses filtered indexes to optimize MIN() and MAX() aggregate functions in FOR DELETED() and FOR NOT DELETED() only. This improves MIN()/MAX() performance, if such an index exists. The Like sometext% operator is now fully optimizable when the string ends in a wildcard. (Note that this is not the case when the comparison value begins with a wildcard or when the wildcard is embedded within the string.) This optimization scenario works like WHERE field = sometext.

Commands and Functions


A few commands and functions have been extended to provide greater control over how and when Visual FoxPro reads and writes data to disk.

Fine-Tune How Data is Accessed and Committed


It is now possible to specify fractions of a second for the second parameter of the SET REFRESH command. The second parameter is used to specify the number of seconds between refreshing local memory buffers with current data from disk. You can also specify a value of 1, which forces Visual FoxPro to always read data from the hard drive. The lowest setting for the second parameter is .001. Setting this value to a low number causes some performance degradation as the number of requests increase, especially across a network, so use it sparingly.

A derived table, such as a sub-query, allows you to treat the results of a sub-query as though it were its own table.

More INDEX Smarts


Visual FoxPro 9.0 is even smarter in how it utilizes existing indexes to achieve Rushmore optimization. For example:
INDEX ON DELETED() TAG DELETED

This index is used to optimize both NOT DELETED() and DELETED() conditions without the presence of a tag created by INDEX ON NOT DELETED(). Just like the MIN()/MAX() optimization, Visual FoxPro 9.0 uses a FOR NOT DELETED() filter on an index to optimize a DELETED() or NOT DELETED() query. Whenever it is possible to determine that a condition should filter on DELETED() or NOT DELETED(), a filtered index FOR DELETED() or FOR NOT DELETED() is used in the event that no non-filtered indexes exist. Take this upgrade opportunity to review the indexes you

The SYS(1104) function purges memory cached by programs and data, and it clears and refreshes buffers for open tables. In Visual FoxPro 9.0, a second parameter scopes the operation to a specific work area or alias. This is valuable because using SYS(1104) when a large number of buffered tables are open can result in slow performance while each buffered table refreshes. The FLUSH command is used to ensure that changes made to tables, indexes, and files are saved to the disk. In Visual FoxPro 9.0, the FLUSH command has been enhanced in two ways: specifying FLUSH areas, and calling the FlushFileBuffers function. You can now be specific about the filename, work area, or alias to be FLUSHed. Although this extra granularity is handy, it's the FORCE keyword that is very useful in scenarios where Visual FoxPro 9.0

16

What's New with Data in Visual FoxPro 9.0

www.code-magazine.com

Regardless of whether you are doing remote data access or relying on the powerful native data engine, performance has always been a priority.
writes data to the disk but the operating system keeps the writes cached. When you use the FORCE keyword, Visual FoxPro 9.0 includes a call to the Windows API FlushFileBuffers function. This ensures that even operating system buffers are written to disk. Some examples of using the enhanced FLUSH command include:
FLUSH FLUSH FLUSH FLUSH FLUSH FLUSH "c:\data\customers.dbf" "c:\data\customers.dbf" FORCE IN 1 FORCE IN customer FORCE "c:\test.txt" FORCE FORCE

causes the current record to be committed before the statement is executed. The SET SQLBUFFERING command is scoped to the current data session. When the WITH BUFFERING clause is used, it overrides the SET statement.

CAST()
The new CAST() function is modeled after the SQL Server function by the same name. It is useful both in and out of SQL. Used inside a SQL statement, you can write SQL code that is more TSQL compliant. As you might expect, this function converts a data expression to another data type. Used within a SQL statement, the CAST() function looks like this:
SELECT CustomerID, ; CAST(nAmount*nRate AS N(8,2)) ; FROM SALES SELECT CustomerID, ; CAST(nAmount*nRate AS B NOT NULL) ; FROM SALES SELECT CustomerID, ; CAST(nAmount*nRate AS C(10)) ; FROM SALES SELECT foo.*, ; CAST(NULL as I) AS IntegerField ; FROM foo

Calvin Hsia
Lead Developer, Visual FoxPro Team Microsoft One of the many cool things VFP 9.0 allows you to create is better photo database applications without using the general field using the new BLOB data type and enhancements to image properties. There are many new features in the Property Sheet including custom font and color control as well as a custom Favorites tab for categorizing properties and methods. There are new functions and events in VFP 9.0 specifically designed for building even more advanced Tablet PC based applications.

In versions prior to Visual FoxPro 9.0, using a SQL SELECT statement meant that the results were always pulled from disk. This meant that if you wanted to query uncommitted changes from a buffered table, you were forced to use procedural commands. Now it's possible to specify for each table in a SELECT statement whether to read from the disk or from the local buffer using SET SQLBUFFERING and SELECT WITH (Buffering = <lexpr>). Some examples of how to use WITH (BUFFERING ) include:
SELECT * FROM Customer WITH (BUFFERING = .t.) SELECT * FROM Orders WITH (BUFFERING = lUseBuffer) SELECT FROM JOIN ON DISTINCT c.city, o.shipcity ; customers C WITH (BUFFERING=.T.) ; orders O WITH (BUFFERING=.T.) ; c.customerID = o.customerID

Notice that there is the ability to specify whether NULLs are acceptable. If not specified, CAST() inherits NULL behavior from the expression, if possible.

ICASE()
Another function, ICASE() emulates TSQLs CASE branching construct. This function is similar to IIF(), the immediate IF function. The value of ICASE() is that it doesn't require the kind of ugly and verbose nesting syntax of IIF(). ICASE() works by requiring condition/result parameter pairings. The first parameter is the condition expression to evaluate, and the second parameter is the result if the condition provided in the first parameter evaluates to True. If the condition evaluates to False, the second parameter is skipped and the next condition/result parameter pair is evaluated. This continues for each parameter pairing. If the parameters are not passed in pairs, Error # 11 is thrown. Just like the CASE/ENDCASE syntax, there is an Otherwise option that can be passed as the last parameter to the function. If this parameter is not passed and all other condition parameters evaluate to False, ICASE() returns a NULL.

Notice that each table referenced has its own WITH BUFFERING clause. If no BUFFERING clause is used, Visual FoxPro 9.0 respects the SET SQLBUFFERING value (whose default is .f.). Support for BUFFERING is only available on local Visual FoxPro 9.0 data. It is not supported on data from back-end databases, so it should not be used with SQL Pass Through. When working with a table that is involved in ROW buffering, using the WITH BUFFERING command

www.code-magazine.com

What's New with Data in Visual FoxPro 9.0

17

CLOSE DATA ALL MODIFY FILE Showplan.txt NOWAIT

If you don't include a variable name as the third parameter for SYS(3054), results are echoed to the current window.

New Datatypes
In an effort to provide better compatibility with back-ends such as SQL Server, Visual FoxPro 9.0 has added three new data types, VarChar, VarBinary, and BLOB. These data types can be used as part of a local table, which is created by using the CAST() function or mapped to when retrieving results from remote data.

VarChar
The VarChar is a character data type that is not padded with spaces to the length of the field. This provides similar functionality as SET ANSI_PADDING ON in SQL Server. In addition, if spaces are included with the original value, they are not trimmed. It's important to note that when using expressions combining VarChar with Character data types, the result is always of the type VarChar. Because a VarChar is a fixed length field in Visual FoxPro 9.0, the maximum field length is 255 characters. In SQL Server, it is possible for a single VarChar field to have a length <= 8060 bytes.

Figure 1: Comparing Character and VarBinary data types looks like this.

Here is an example of ICASE() used outside of a SQL statement:


nHour = HOUR(DATETIME()) ? ICASE( nHour = 8, "breakfast" nHour = 10, "caffeine" nHour = 12, "lunch" nHour = 15, "caffeine" nHour = 18, "dinner" "snack" ; )

, , , , ,

; ; ; ; ;

VarBinary
The VarBinary data type is similar to VarChar in that values assigned to VarBinary fields are not padded. The only real difference between the two is that FoxPro does not perform any code page translation for VarBinary types. (See Figure 1 for a comparison of VarChar and VarBinary datatypes.)

Up to 100 condition/result parameter pairs can be passed to the ICASE(). SYS(3092) Output to a File This new SYS() function works in conjunction with SYS(3054), the SQL Showplan function. With SYS(3092), you can specify a filename to which the results of SYS(3054) output are sent. An example of how to use these functions together is listed here:
SYS(3054,12,"dummyVar") SYS(3092,"ShowPlan.txt") OPEN DATABASE HOME(2)+"Northwind\Northwind" SELECT * ; FROM Customers INTO CURSOR temp1 SELECT * ; FROM summary_of_sales_by_year ; INTO CURSOR temp2 SYS(3092,"")

BLOB
The BLOB (Binary Large OBject) data type is not a fixed length type, like a Memo. Its information is stored in an .FPT file that is external to but referenced by the .DBF file. BLOBs have the same limitations and issues as Memo fields and do not support indexes.

When using expressions that combine VarChar with Character data types, the result is always of the type VarChar.

18

What's New with Data in Visual FoxPro 9.0

www.code-magazine.com

Many of the changes made to the CursorAdapter were done to bring behavior in line with remote views.
Like the VarBinary type, Visual FoxPro 9.0 does not perform any code page translation and its content is treated as binary content. The BLOB datatype is an ideal candidate to replace the legacy General field. Pictures and other media can be stored in a BLOB and then rendered using the Image control PictureVal property. MODIFY MEMO of a BLOB displays a HEX dump of the binary data.

9.0. Tables that include these new types cannot be read by prior versions.

Remote Data
Visual FoxPro has always had a strong remote data story, and Visual FoxPro 9.0 adds even more control over how remote data is manipulated and retrieved.

Transactions and Connections


A new property, DisconnectRollBack has been added to control whether pending transactions should be rolled back when the connection is disconnected. This property is available through the SQLSetProp, SQLGetProp, DBSetProp, and DBGetProp functions. SQLIdleDisconnect() is a new function that can be used to temporarily disconnect a connection. In most cases, this is controlled through the IdleTimeOut property of a connection. But in multithreaded run-time (MTDLL), idle tasks are disabled. Calling this function releases the connection until the next command requiring a connection is issued.

SET EXACT & Binary Comparison


With Binary data types comes a difference in behavior with SET EXACT and SET COLLATE. SET EXACT ON specifies that expressions must match exactly to be equal. However, with VarBinary types whose values are padded with CHR(0), the trailing bytes are ignored for the comparison. The shorter of the two expressions is padded on the right with CHR(0) bytes to match the length of the longer expression. SET EXACT OFF specifies that expressions must match up exactly to the length of the expression on the right side of the comparison. The == operator requires that both sides of the expression contain exactly the same number of bytes, including CHR(0) bytes. Further, because Binary data is case-sensitive, comparisons are always case-sensitive regardless of SET COLLATE. This is different than a comparison with a Character type, which is case-insensitive if SET COLLATE is set to GENERAL.

Fetching and Processing Information


CursorGetProp() receives two new properties, RecordsFetched and FetchIsComplete. These properties allow you to determine the number of records fetched and when a fetch is completed during the execution of SQL Pass Through statements. The RecordsFetched value might not reflect the number of records in the cursor in the event that some records in the cursor were locally appended or deleted. The number of records reported does not respect filters that may be on the cursor. The number of records affected by the SQL Pass Through can be determined using the additional parameter aCountInfo available with the

Data Type
Character Date DateTime Numeric Floating Integer Double Currency Logical Memo General Picture VarChar VarBinary BLOB

Long Name
Char, Character Date Datetime Num, Number Float Int, Integer Double Currency Logical Memo General Picture Varchar Varbinary Blob

Initial
C D T N F I B Y L M G P V Q W

Learn to Type
Prior to version 9.0, Visual FoxPro allowed a SQL CREATE TABLE statement to include a long typename, although only the first letter of the typename was respected. This resulted in issues with data types such as Character and Currency. There is now full support for long typenames with both CREATE and ALTER TABLE/CURSOR as well as the new CAST() function. Table 1 provides a list of FoxPro data types with their long name, single letter, and, in some cases, abbreviations, supported. Just like with the new Binary index datatype, using these new datatypes requires that all clients accessing the data be upgraded to Visual FoxPro

Table 1: Here are some FoxPro data types with their supported long names and initials.

20

What's New with Data in Visual FoxPro 9.0

www.code-magazine.com

SQLEXEC() and SQLMORERESULTS() functions. ACountInfo is a two column array containing the alias and count.

In addition, properties have been added to support DEFAULT and CHECK constraints and for mapping remote data to the new VarChar and VarBinary data types. XMLAdapter changes include support for: Hierarchical XML XPath expressions XML encoding and decoding enhancements

Rowsets from the Provider


Three functions have been added to support returning result sets from stored procedures when accessed via the provider. SetResultSet() is used to specify which work area in the current DataSession is to be used as the result set. Only one cursor in a DataSession can be marked. GetResultSet() returns the work area for the cursor marked by SetResultSet. ClearResultSet() sets the marked result set to 0 and returns the work area for the previously marked cursor. Within a stored procedure, you create a cursor with the results you want returned and use SetResultSet() to identify the work area for the cursor. When executed by the provider, return values are not respected, but a rowset is created based on the cursor you provided.

Upgrading
Visual FoxPro 8.0 included changes to FoxPro's SQL commands, and brought it into greater compliance with ANSI SQL standards. These changes may have discouraged you from upgrading to version 8 because of the impact on existing code. If you've been putting off fixing some of those ambiguous queries or at least bracketing them with SET ENGINEBEHAVIOR, Visual FoxPro 9.0 provides many compelling reasons to make the investment to upgrade. Fortunately, the kinds of changes made in Visual FoxPro 9.0 won't require the kind of recoding you may have found necessary for 8.0. As in prior versions, using the SET ENGINEBEHAVIOR command allows you to isolate legacy code that may be problematic. SET ENGINEBEHAVIOR 90 impacts the behavior of TOP N and the behavior of aggregate functions without a GROUP BY clause. In versions prior to 9, if such a statement resulted in no matching criteria, 0 records were returned. To be more ANSI compliant in version 9.0, FoxPro returns a single record resultset with NULL value(s) for aggregate columns.

Ken Levy
Product Manager, Visual Studio Data Team Microsoft With all the great new features included in Visual FoxPro 9.0, my favorite feature remains the world-wide FoxPro community. There is a list of great Visual FoxPro community resources at http://msdn.com/vfoxpro community. Posting a question on a community Web site is free and you usually get a quick response from somebody who knows the answer. Interacting within the FoxPro community is not only educational, but can be fun and it can result in finding employees or employers. Information from the Visual FoxPro team is regularly included in the monthly letters at
http://msdn.com/vfoxpro/letters.

As in prior versions, using the SET ENGINEBEHAVIOR command allows you to isolate legacy code that may be problematic.
CursorAdapter and XMLAdapter Enhancements
Changes to the CursorAdapter and XMLAdapter classes are worthy of another multi-page article. But for the purpose of this overview, it's important to briefly note some of the more significant enhancements. Many of the changes made to the CursorAdapter bring behavior in line with remote views. These enhancements include: Support for Timestamp fields. This allows UPDATE/DELETE commands to use Timestamp fields as part of the WhereType method. Auto-Refresh support. Several new properties have been added so that remote data managed by the CursorAdapter is automatically refreshed after an INSERT or UPDATE operation. This is valuable for retrieving auto-incremented or default field values and timestamps. On Demand Record Refresh support. More properties and events have been added to CursorAdapter to support the same kind of functionality provided by the REFRESH() function for local and remote views.

Final Thoughts
In this article, youve seen that the changes to the data engine in this release are substantial. A commitment to backward compatibility and an easy upgrade path has made these changes nearly transparent when moving from 8 to 9. Once you've made the move, you can start taking advantage of these great enhancementssome without changing a single line of code. Other enhancements ensure that code based on the new features is more compatible, powerful and maintainable than ever before.

For even more up-to-date news and information, refer to my blog at http://blogs.msdn.com/klevy, Calvin Hsia's blog at
http://blogs.msdn.com/calvin_h sia, and the Visual Studio Data

Team blog at
http://blogs.msdn.com/vsdata.

David T. Anderson

All of this information Visual FoxPro 9.0 whitepapers and samples can be found on the Visual FoxPro home page at http://msdn.com/vfoxpro.

www.code-magazine.com

What's New with Data in Visual FoxPro 9.0

21

QUICK ID 0404042

The Visual FoxPro 9.0 Report Writer


Cathy Pountney
Cathy Pountney is a Microsoft Visual FoxPro MVP and has been developing software for over 22 years, thirteen of which were as an independent consultant specializing in FoxPro. In 2001 she had the privilege of spending six months as a contractor onsite in Redmond with the Microsoft Fox Team. Currently she works for Optimal Solutions developing Visual FoxPro applications for schools. Cathy has spoken at many FoxPro conferences and user groups across the U.S., written articles for various magazines, and her book, The Visual FoxPro Report Writer: Pushing it to the Limit and Beyond, is available from Hentzenwerke Publishing (www.hentzenwerke.com).
cathy@frontier2000.com www.frontier2000.com www.optimalinternet.com

Microsoft has significantly improved the Report Writer in Visual FoxPro 9.0. They also recognized the significant investment in existing FRX-based reports and designed the new Visual FoxPro 9.0 Report Writer to be compatible with previous versions of Visual FoxPro Reports. This makes it a great blend of the old and the new. In this article, you'll learn about some of the new features, including the new reusable data environments, report protection, and several user interface enhancements. Youll also learn about enhancements to layout objects and data groups. Finally, youll learn about one of the best improvements to the Visual FoxPro 9.0 Report Writer: multiple detail bands.

efore I explain the new features, you need to understand how to use the new Report Designer and how to run reports using the new output engine.

*-- Use the old output engine SET REPORTBEHAVIOR 80

The rest of this article assumes that the new Report The Report Designer: By default, the new Visual Designer and the new output engine are both in use. FoxPro 9.0 Report Writer uses the new Xbase Report Designer. It provides newer dialog boxes and is easier Fast Facts Data Environments to use than the old version. It The new also provides access to many of Visual FoxPro 9.0 Report Writer the new features that are not The Visual FoxPro 9.0 Report available through the old is compatible with previous Writer can now share Data Report Designer. You can easily versions of Visual FoxPro, Environments with other control which Report Designer making it a great blend of the old reports. Data Environments is used by changing the value of and the new. can also be saved as a class and the new _REPORTBUILDER then loaded into reports as system variable. needed. This offers a great reuse scenario for defining common reporting needs. *-- Use the new Report Builder
_REPORTBUILDER = HOME() + 'ReportBuilder.app'

Save As Class
*-- Use the old Report Builder _REPORTBUILDER = ''

The Output Engine: Just as with the Report Designer, you can control whether the new or old output engine is used. Unlike the Report Designer, which defaults to the new style out of the box, Visual FoxPro 9.0 defaults to the older output engine. The reason for this is that GDI+ is used in the new engine, and renders slightly differently than the GDI used by the old engine. Therefore, some of your existing reports could render differently in Visual FoxPro 9.0, which means you'd have to tweak them to make them appear correctly. You can switch between the output engines with the following command:
*-- Use the new output engine SET REPORTBEHAVIOR 90

To save a Data Environment as a class, start by defining the Data Environment in a report as usual. While the Data Environment window is still active, select the new Save As Class option from the File menu. After selecting the Save As Class option, the Save As Class dialog box appears, as shown in Figure 1. The DataEnvironment button of the Save option

Figure 1: Use the Save As Class dialog box to declare the name of the new class and the class library for saving the Data Environment of a report.

22

The Visual FoxPro 9.0 Report Writer

www.code-magazine.com

must click Yes to continue. This helps remind you that anything you have defined in the Data Environment of the current report is about to be overwritten. If you click No, changes are not made and the process is aborted. When you click Yes, the Data Environment is copied and you are notified by another dialog box. You are now finished copying the Data Environment. You may manipulate the Data Environment as needed. However, remember that any changes made to the original report's Data Environment after this point are not propagated to this new report. Figure 2: Use the Data Environment tab of the Report Properties dialog box to choose which report you want to copy a Data Environment from.

From a DE Class
When loading the Data Environment from a class, code is added to the Data Environment of the new report to bind to the original DataEnvironment class and instantiate it at runtime. This means that future changes made to the DataEnvironment class will propagate into any reports using the DataEnvironment class. Using the Report Properties dialog box, shown in Figure 2, click the Link to a visual DE class button. Next, click the Select button to invoke the Open dialog box that you can use to choose which class library and which class to use. After confirming your intentions, the Data Environment is updated and you are notified of its completion. At this point, code has been added to five Data Environment methods: Init(), BeforeOpenTables(), AfterCloseTables(), Destroy(), and Error(). Some of the methods have very simple code with nothing more than a DODEFAULT() command. The reason for this is that BindEvents() does not function unless the method contains at least one line of code. Look at the code in these methods to see what it does, but I do not recommend that you change it.

group is the only option button-enabled when saving a Data Environment of a report. Enter a name for the class in the Name textbox. Next, enter the name of the class library you want the new class saved in. If you enter the name of a class library that does not exist, the new class library is created for you. You can also use the ellipse button to brows for the location of an existing class library. Finally, you may optionally enter a description of the new class.

Loading a DE
In addition to manually defining the Data Environment for a new report, Visual FoxPro 9.0 also gives you the option to load the Data Environment from an existing report or from a saved DataEnvironment class. The Load Data Environment option on the Report menu allows you to select which Data Environment to load.

From a Report
When loading the Data Environment from another report, all the code and members of the original Data Environment are copied into the new report. This means any changes made to the original report's Data Environment after the fact are not propagated into reports created from the original report. The Report Properties dialog box, as shown in Figure 2, appears after selecting Load Data Environment from the Report menu. Use this menu to select the report from which you wish to copy the Data Environment. Select the Copy from another report file option button and then click the Select button. This invokes the Open dialog box so you can choose which report to copy from. Once you chose a report, a confirmation dialog box appears. Visual FoxPro 9.0 is about to copy the Data Environment from another report to the current report. Visual FoxPro 9.0 notifies you that it's about to overwrite the current Data Environment, and you

Just like the Report Designer, you can control whether the new or old output engine is used.

Protection
In Visual FoxPro 9.0, you can create protection for one or more layout objects when using Report Designer or Label Designer. This lets your user modify a report, yet keeps them from making certain changes. Layout objects have five protection modes you can set, and Field objects have an additional protection option. Bands have two protection modes you can set. The report itself has a variety of different protection modes you can set.

www.code-magazine.com

The Visual FoxPro 9.0 Report Writer

23

option on the Report menu, from the right-click menu of the band, or by double-clicking the gray bar of the band. Figure 4 shows the Protection tab of the Properties dialog box for a band. You can set the following two protection modes for bands: Band cannot be edited prevents the Band Properties dialog box from being accessible to the user. Band cannot be resized prevents the user from resizing the band.

Protecting a Report
To set overall report protection, select the Report Properties dialog box. You can invoke this dialog box by selecting Properties from the Report menu or from the right-click menu of the report. Figure 5 shows the Protection tab of the Report Properties dialog box. The top portion of this dialog box allows you to define which tabs of the Report Properties dialog box are unavailable to the user. For each of the selections made in this area, the applicable tab of the Report Properties dialog box is disabled. The Protection option is always checked and disabled. The Ruler/Grid option is disabled because the tab cannot be protected, although it appears on the dialog box so that the selections are consistent with the tabs on the Report dialog box. The bottom portion of this dialog box allows you to define which menu options are unavailable to the user. For each of the selections in this area, the applicable menu option is disabled.

Figure 4: Use the Protection tab of the Band Properties dialog box to set protection modes of the band. Figure 3: Use the Protection tab of the Properties dialog box to set protection modes of a layout object.

Protecting an Object

SUMMARY Issues
Using the SUMMARY clause of the REPORT FORM command prevents the Detail band from printing, yet all the Page Headers and Footers, Column Headers and Footers, and Group Headers and Footers are printed. Any On Entry or On Exit expressions in a Detail band are not processed when the SUMMARY clause is used. When issuing SUMMARY on a Multiple-Detail band report, the Detail Headers, Detail Footers, and Detail bands are not processed.

To protect a layout object in the Report Designer, select the Properties dialog box for the object. The Properties dialog box can be invoked from the Report menu after selecting the object from the right-click menu of the object, or by double-clicking the object. Figure 3 shows the Protection tab of the Properties dialog box for a field object. You can set the following five protection modes for layout objects: Object cannot be moved or resized prevents users from moving this layout object to a different position on the design surface and prevents users from resizing this object. Object cannot be edited prevents the user from making changes to the properties of this layout object. Object cannot be deleted prevents the user from deleting this object. Object cannot be selected prevents users from selecting this object. When this option is selected, the protection behaviors of Object cannot be moved or sized. Object cannot be edited and Object cannot be deleted are also imposed. Object is not visible in Designer prevents this object from appearing in the Report Designer in protected mode. When this option is selected, the protection behavior of the other four options is also imposed. The Design-time caption portion of this dialog box only applies to Field objects. The literal string entered into this textbox is displayed in the Report Designer, instead of the Expression. This gives you the opportunity to display something that is userfriendly instead of a complicated expression.

Honoring Protection Flags


To invoke protection during a Report Designer or Label Designer session, use the PROTECTED keyword, as shown in the following examples.
CREATE MODIFY CREATE MODIFY REPORT MyReport PROTECTED REPORT MyReport PROTECTED LABEL MyLabel PROTECTED LABEL MyLabel PROTECTED

If the PROTECTED keyword is not used, the

Protecting a Band
To protect a band in the Report Designer, select the Properties dialog box for the band. The Properties dialog box can be invoked from the Edit Bands Figure 5: Use the Protection tab of the Report Properties dialog box to set overall protection modes of a report.

24

The Visual FoxPro 9.0 Report Writer

www.code-magazine.com

Report Designer functions as if no protection were applied to the layout objects.

the Data Environment are listed in the Fields list box. Tables opened outside of the Data Environment are not available in the list box. When the _REPORTBUILDER system variable is set to ReportBuilder.app, the behavior of the Expression Builder dialog box is quite different. First of all, the Expression Builder defined in _GETEXPR is invoked instead of the native Expression Builder. This Expression Builder dialog box has a combo box for choosing which table should be used when listing fields in the Fields list box. Only tables that are currently in use are listed in the combo box. This is an important point to remember because tables defined in the Data Environment are not automatically opened by the Report Designer, and do not automatically appear in the combo box. This gives you the ability to control which tables are available to end-users when you allow them to modify reports within your application. You may have defined some tables in the Data Environment that you need, but to which you don't want user access. Because you have to specifically open the tables you want users to access, you can omit any tables you want to keep from them.

UI Enhancements
Many changes have been made to the user interface to make designing reports easier and more intuitive. Menus have been overhauled, context menus have been changed, and new options have been added to the Report Designer toolbar. The Expression Builder dialog box and Expression Builder Options dialog box have new behaviors, and a few other miscellaneous user interface enhancements have been added to the Visual FoxPro 9.0 Report Writer.

Yair Alan Griver


Group Manager, Visual Studio Data Team Microsoft The new data enhancements particularly those that target SQL Serverare among my favorite new capabilities in Visual FoxPro 9.0. I really like being able to use CAST(), optimized LIKE, and some of the new features that give me better control over SQL handles. I especially love the enhanced SQL syntax. Being able to more closely model Visual FoxPro and SQL Server statements is an awesome capability!

Menus
The report menu system has been overhauled in Visual FoxPro 9.0 to accommodate new options. In addition, some options have been relabeled for clarity and some options have been repeated on several menus to allow easier access. The new Save As Class option appears on the File menu. A new option has been added for the Report Designer Toolbar and horizontal lines have been added to separate the Grid Lines and Show Position options from the other options. Many changes appear on the Report menu, including relabeled options, new options, and the addition of the Print Preview option.

Expression Builder Options Dialog Box


The Field aliases option group is now enabled in the Expression Builder Options dialog box. This option group allows you to indicate whether or not you want the table alias added to the report expression when picking fields from the Expression Builder dialog box. The Always add alias and Never add alias option buttons cause Visual FoxPro 9.0 to automatically add the table alias, or to not add the table alias, for all fields. The behavior of the Add non-selected alias only option button depends on the value of the _REPORTBUILDER system variable. If the _REPORTBUILDER system variable is empty, any field chosen from a table that is not the InitialSelectedAlias is prefixed with the table alias. Fields from the InitialSelectedAlias table are not prefixed with the table alias. If the _REPORTBUILDER system variable is set to ReportBuilder.app, the Add non-selected alias only option uses slightly different logic. The currently selected alias is used instead of the InitialSelectedAlias for determining whether or not to prefix the field with the table alias. In addition to selecting a field from the Expression Builder dialog box, dragging a field from the Data Environment to the Report Designer surface honors the setting of the Field aliases option group. Also, a new option exists on the Report tab of the Options dialog box to determine what the default Field aliases setting is for all newly created reports.

Context Menus
Existing context menus have been improved with additional items and are now more consistent with the dialog boxes they invoke. Items in the Report Designer that previously didn't have context menus now do. The Global Context menu has new options and one relabeled option. Invoke the new Band Context menu by rightclicking on the gray bar of any band. Invoke the new Layout Object context menu by right-clicking on any layout object.

Toolbar
The improved Report Designer toolbar, shown in Figure 6, has two new buttons: The Page Setup button and the Font Properties button. Figure 6: New options for Page Setup and Font Properties have been added to the Report Designer toolbar.

Expression Builder Dialog Box


A few changes have been made to the Report Expression dialog box, including a taller Expression for Field editing box, which allows more room for entering a report expression. When the _REPORTBUILDER system variable is empty, the native behavior of the Expression Builder dialog box is specific. Only tables defined in

26

The Visual FoxPro 9.0 Report Writer

www.code-magazine.com

Mouse Cursor
The mouse cursor now changes to provide a visual cue when an object can be resized (see Figure 7).

example, when the Format expression is "999-999", and the data contains "123456", the report shows "123-56". Notice that the "4" is replaced by the dash in the format expression. When the Interleave option is used, the special character is inserted in between existing characters in the data. For example, when the Format expression is "999-999", and the data contains "123456", the report shows "123-456". Notice that the dash is inserted between the "3" and the "4".

Multiple Selection Dialog Box


Visual FoxPro 9.0 has a Multiple Selection dialog box, which allows you to set the Protection and Print when properties for more than one layout object at a time. It also allows you to change any of the other properties of any individual layout object. To use this new feature, select more than one layout object, and then double-click any one of the objects to invoke the Multiple Selection dialog box, as shown in Figure 8. The layout objects that were selected when this dialog box was invoked are listed in the first tab of this dialog box. To work with all layout objects defined in a report, use CTRL + A to select all layout objects before invoking this dialog box. The Sort by option allows you to sort the list of layout objects by Type or Location within the report. The Remove from list button removes the selected layout objects from the list. Double-click on any item in the list and the applicable Properties dialog box for the individual object is invoked. The Properties tab of the Multiple Selection dialog box, shown in Figure 9, is used to change the properties of all the items listed in the Selection tab. Select the Apply these protection settings to the selected objects checkbox to enable the Protection options. Select the Apply this condition to the selected objects upon saving checkbox to enable the Print when option. Change the Protection and Print when settings as needed, and then select OK to close the dialog box and apply the changes to all the layout objects listed on the first tab.

Trim Mode for Character Expressions


Prior to Visual FoxPro 9.0, field objects were always trimmed to the nearest word when the text was too long. In Visual FoxPro 9.0, a new option on the Field Properties dialog box allows you to determine how the text is trimmed. The six trim options are: Default trimming uses the default behavior, which is the same as the Trim to nearest word, append ellipsis option. This behavior is similar to prior versions of Visual FoxPro, with the exception of appending the ellipsis. Trim to nearest character trims text to the last full character that fits in the defined area. Trim to nearest word trims text to the last full word that fits in the defined area. Trim to nearest character, append ellipsis trims text to the last full character that fits in the defined area, after an ellipsis is added to the text that prints. Trim to nearest word, append ellipsis trims text to the last full word that fits in the defined area, after an ellipsis is added to the text that prints. Filespec: Show inner path as ellipsis replaces the inner directories of a long path and filename with an ellipsis when the full text does not fit in the defined area.

Figure 7: Use the new mouse cursor to know when an object can be resized.

Figure 8: Use the Selection tab of the Multiple Selection dialog box to choose which layout objects you want to work on.

More Zoom Levels


The Preview window now has more Zoom levels, ranging from 10% up to 500%.

Layout Object Enhancements


A few improvements have been added for layout objects, including an option to control template characters, a trim mode for character expressions, and relative and absolute positioning.

Size and Position


Another new feature available on Layout objects is better control of the size and position of the object. When an object is added to the report, the values for From page top, From left, Height, and Width are automatically set. It's important to note that the From page top property is relative to the top of the page in the Report Designer, which means it takes into account the height of any gray bars above the object. Changing the From page top property may inadvertently move the object to another band.

Template Characters
The Field Properties dialog box has a new section for Template characters. The two available options are Overlay and Interleave. These determine how special characters are used in the format. When the Overlay option is used, special characters are treated as part of the data and overlay any other specific character in a specified position. For

Figure 9: Use the Properties tab of the Multiple Selection dialog box to change the Protection properties and the Print When logic of the selected layout objects all at once.

www.code-magazine.com

The Visual FoxPro 9.0 Report Writer

27

Figure 10: Prior versions of Visual FoxPro wasted a lot of space when using horizontal columns with Data Groups.

Figure 11: Prior versions of Visual FoxPro reserved space for Data Group Headers, even when none were defined.

Relative Positioning: The From page top property and the Height property work together to determine whether absolute positioning or relative positioning is used. When the From page top property is set to a value that falls within the Report Designer surface, and the value of the Height property is less than or equal to the height of the band in which the object is located, relative positioning is used. Relative positioning is needed for objects in bands other than the Page Header and Page Footer. Absolute Positioning: When the From page top property is set to a value that falls outside the Report Designer surface, or the value of the Height property is greater than the height of the band in which the object is located, absolute positioning is used. Absolute positioning means the object is printed in exactly the same location on each and every page. Absolute Positioning can be used to create a watermark on a report. Place a graphic image in the Page Header band, and set it to Scale contents, retain shape. Change the From page top property and the From left property to indicate the upperleft corner of where you want the watermark to begin. Change the Height and Width properties to indicate the overall size of the watermark, making sure not to extend beyond the printable margins of the printer.

Data Group Enhancements


A few enhancements have been made to Data Groups in the Visual FoxPro 9.0 Report Writer, including maximum Data Groups and horizontal columns.

Maximum Data Groups


The maximum number of Data Groups has been increased from 20 to 74. In actuality, the maximum of 74 was always true, but the user interface only allowed 20 Data Groups to be entered.

Horizontal Columns
Previously, reports with more than one column defined as horizontal with a Data Group wasted a lot of space. The first position (row 1, column 1) was left blank and data began in column 2 of row 1. Also, a blank band was wasted in between each set of Data Groups, as shown in Figure 10. Even if the height of the Data Group Header band is zero, Visual FoxPro still reserved the space, as shown in Figure 11. In Visual FoxPro 9.0, the behavior of Data Groups and horizontal columns has been changed. When a new Data Group is encountered, it is printed in column 1 of the next full row. The remainder of this row is left blank and not used for printing details. The details belonging to the Data Group begin in column 1 of the row immediately after the row the Data Group is printed in, as shown in Figure 12. Also, no extra space is reserved if the height of the Data Group Header band is zero, as shown in Figure 13. The new behavior, although avoiding the previously described situation, may break some existing reports.

Figure 12: Visual FoxPro 9.0 does not waste as much space as prior versions when horizontal columns and Data Groups are used.

28

The Visual FoxPro 9.0 Report Writer

www.code-magazine.com

Figure 13: Visual FoxPro 9.0 does not reserve extra space when the Data Group Header band is 0" tall.

Figure 14: In Visual FoxPro 9.0, you can expand the Data Group Header band across multiple columns.

However, an added benefit of the new behavior is that the Data Group band can be stretched across all the columns, as shown in Figure 14.

make sure the Customer table is the current work area at the time the report runs.

The Target Alias

Multiple-Detail Bands
The new multiple-detail band feature is one of the biggest, and most often requested, improvements. It allows you to process multiple child tables for each record in a parent table. An example of this type of report is shown in Figure 15.

The target alias is the term used to describe which table is the driving table for a particular detail band. In this example, the Members table is the target alias for Detail 1 band, the Vehicles table is the target alias for Detail 2 band, and the Homes table is the target alias for Detail 3 band. If no target alias is defined for a Detail band, the behavior it takes on is that of prior versions of Visual FoxPro. In other words, one detail band is processed per parent record. However, if you enter the name of the parent table as the target alias, you'll get very different results. For each record in the parent table, Visual FoxPro processes through all records in the entire parent table, printing each parent record in the Detail band. If you have a table with 10 parent records, and you set the target alias of a detail band to the parent table, the final report prints 10 sets of 10 records, or a total of 100 records.

Tables and Relationships


Understanding how the parent and child tables work together are key to understanding how to use this new feature. As an example of a Multiple-Detail scenario, assume you are writing the report shown in Figure 15. The database for this scenario is: The Customer table is the parent table and contains one record for each insurance customer. The Members table holds one record for each family member of the customer. The Members table is a child table of the Customer table. The Vehicles table holds one record for each vehicle insured by the customer. The Vehicles table is a child table of the Customer table. The Homes table holds one record for each home insured by the customer. The Homes table is a child table of the Customer table.

Relationships
Relationships play a big part in how multiple-detail bands operate. Visual FoxPro uses the relationships between the parent table and the child tables to navigate through the records. You may use SET RELATION or SET SKIP to define these relationships. If you're opening the tables in the Data Environment, and relationships are already defined in the database, these relationships are honored. If you're opening the tables in code, Listing 1 shows how to set up the tables for the Insurance Customer Listing shown in Figure 15.

Driving the Report


One table is necessary to drive the report. In this example, the Customer table is the driving table. If you use the report's Data Environment to define the tables, set the InitialSelectedAlias property to this table. If you are using code to define the tables,

30

The Visual FoxPro 9.0 Report Writer

www.code-magazine.com

When creating Multiple-Detail band reports, it's important to prefix field names with the applicable alias name. This prevents any confusion as to which table a field comes from.

Headers and Footers


Another enhancement of the Multiple-Detail band is the ability to add Headers and Footers to each Detail band. These are similar to Group Headers and Footers in some ways, yet different in others. For each parent record that is processed, the following occurs: The Detail 1 Header band is processed. The Detail 1 band is processed once for each child record in the associated target alias. The Detail 1 Footer band is processed. The Detail 2 Header band is processed. The Detail 2 band is processed once for each child record in the associated target alias. The Detail 2 Footer band is processed. The Detail 3 Header band is processed. The Detail 3 band is processed once for each child record in the associated target alias. The Detail 3 Footer band is processed. And so on To turn on Detail Headers and Footers, place a check mark in the selection box for Detail Header/Footer in the Detail dialog box for each of the Detail bands. To help sort out the Detail bands from other bands, the triangle preceding the band name is solid and the other triangles are clear. It's important to note that even if no detail records exist for a particular Detail band, the associated Detail Header and Detail Footer bands still print. If you do not want the Detail Header and Detail Footer bands under this condition, you may use the following Print when logic in each layout object defined in the bands to suppress their printing.
NOT EOF(<target alias>)

Figure 15: This sample Multiple-Detail band report has three separate detail bands for each customer.

Defining Multiple-Detail Bands


By default, new reports are created with one Detail band. Additional Detail bands are added through the Optional Bands dialog box. To invoke this dialog box, select Optional Bands from the Report menu. This is the same dialog box that was formerly named Title/Summary. Click the Add button to add another Detail band to the report. You may define up to 20 Detail bands for each report.

Defining the Target Alias


The target alias is assigned to a Detail band through the Detail dialog box. This dialog box can be invoked by selecting Edit Bands from the Report menu and selecting the applicable Detail band. You can also invoke this dialog box by double-clicking the gray bar of the applicable Detail band. The Target alias is an expression and you must wrap the table name in quotes. Once you have each target alias defined, the gray bars representing the Detail bands show the target alias.

Listing 1: Setting the relationship between the parent and child tables.
*-USE USE USE Open the child tables Members IN 0 ORDER CustomerFK Vehicles IN 0 ORDER CustomerFK Homes IN 0 ORDER CustomerFK

In addition to manually defining the Data Environment for a new report, you have the option to load the Data Environment from an existing report or from a saved DataEnvironment class.

*-- Open the parent table SELECT 0 USE customer ORDER CustomerPK *-SET SET SET Set the relations between the parent and children RELATION TO CustomerPK INTO Members RELATION TO CustomerPK INTO Vehicles ADDITIVE RELATION TO CustomerPK INTO Homes ADDITIVE

*-- Run the report REPORT FORM Insurance PREVIEW

www.code-magazine.com

The Visual FoxPro 9.0 Report Writer

31

Be sure to place a check mark in the selection box for Remove line if blank for these layout objects as well. Similar to Group Headers and Footers, Detail Headers and Footers have some of the same options. Start on a new column: Causes the Detail set to start on a new column of the report. Note that this option allows you to assign a Detail set to a specific column. If one Detail set has enough information to overflow the column, it is continued in the next column. Start on a new page: Makes the Detail set start on a new page. Reset page number to 1 for each detail set: With the Start on a new page option, resets the page number to 1 for each new Detail set. Start detail set on new page when less than: Helps prevent orphans. The detail set begins on a new page if the indicated amount of space is not available. Detail Header/Footer: Adds a Detail Header and Detail Footer band around this Detail band. Reprint detail header on each page: With the Detail Header/Footer option, makes the Detail Header band reprint whenever the Detail set overflows to a new page.

Report Variables and Calculations


With the introduction of Multiple-Detail bands, report variables and calculations have some new twists to them. You need to fully understand how they are processed in order to use them effectively. Otherwise, you may not get the results you're expecting. The Reset at prompt on the Report Variables dialog box has been renamed Reset based on. This more clearly defines that the variable is reset based on the change in value of the selected option. In addition to renaming the control, if more than one Detail band is defined in the report, each Detail band is added to the drop-down list. Selecting Detail n as the Reset based on value tells Visual FoxPro to only process this calculation when processing the detail records in the target alias of this Detail band. The report variable is not altered while records in other target aliases are being processed. This allows you to tie a report variable to one particular Detail band. The value of the report variable is not cleared until the Detail Header band of this same Detail set is processed for the next parent record. If you chose a Reset based on value other than one of the Detail bands, the calculation is processed in several places. First, for each parent record, the calcu-

32

The Visual FoxPro 9.0 Report Writer

www.code-magazine.com

Figure 16: Use a Multiple-Detail report to print group subtotals at the beginning of the group. Figure 17: Use a Multiple-Detail report to print percentages along with each detail line. lation is applied. Next, the calculation is applied for each record in the target alias of the first Detail band. Then the calculation is applied for each record in the target alias of the second Detail band, and so on.

Randy Brown
Lead Program Manager, Visual FoxPro Team Microsoft With Visual FoxPro 9.0, the Fox team brings you the best desktop database application software on the market. As one who has always appreciated all the great extensibility hooks that Fox has to offer, I am very excited about some of the new capabilities in VFP 9.0 such as the ability to hook in your own custom Menu Designer. You can now extend your classes with MemberData for class members that offer capabilities such as custom property editors, display in a Favorites tab and even XML docs support. And the new open architecture for Visual FoxPro 9.0 Reporting blows the doors off of anything available in the market today.

Tricks with Multiple-Detail Bands


The previous section showed how to print data from three different child tables on the same report. But a multiple detail band report doesn't necessarily have to have multiple child tables. The same child table can be used in more than one detail band.

and set the Reset based on to Detail1. 6. Define the Detail #2 band: Double-click the gray bar of the Detail 2 band to invoke the Properties dialog box. Set the target alias to "Vehicles", remembering to use the quotes. Check the Associated header and footer bands checkbox. Add any other objects needed in Detail 2 band. The above report definition tells the Visual FoxPro 9.0 Report Writer to process the Vehicles table twice for each customer in the Customer table. The first time, it calculates the total records and dollar amount for the customer and then prints them. The second pass of the Vehicles table prints the details. This process repeats for each customer in the Customer table.

Group Totals
Prior to Visual FoxPro 9.0, printing subtotals in the Data Group Header band (shown in Figure 16) was very difficult. The data had to be preprocessed to calculate the totals prior to running the report. With Visual FoxPro 9.0, no preprocessing is required. Follow these steps to create this report: 1. Create the Data Environment: Add the Customer and Vehicles tables. Set the InitialSelectedAlias property to the Customer table. 2. Create a Data Group: Set the Data Group expression to the Customer PK. Do not place any objects in the Data Group Header or the Data Group Footer bands. 3. Create the multiple detail bands: Select Optional Bands from the Report menu. Click the Add button to add one more detail band to the report. Click OK to exit the dialog box. 4. Define the Detail #1 band: Double-click the gray bar of the Detail #1 band to invoke the Properties dialog box. Set the target alias to "Vehicles", remembering to use the quotes. Check the Associated header and footer bands checkbox. Do not place any objects in the Detail 1 band. 5. Define the Detail #1 Footer band: Add the Customer Name and Address objects to the band. Add the total vehicles and total premiums Label objects to the band. Add a Field object for the total vehicles; set the expression to vehicles.premium, set the Calculation type to Count, and set the Reset based on to Detail1. Add a Field object for the total premiums; set the expression to vehicles.premium, set the Calculation type to Sum,

Percentages
Another reporting concept is to show percentages of totals of each detail line, as the detail line prints. This can also be handled with Multiple-Detail bands, as shown in Figure 17. Follow these steps to create this report: 1. Define the Data Environment: Add the Customer and Vehicles tables. Set the InitialSelectedAlias property to the Customer. 2. Create a Data Group: Set the Data Group expression to the Customer PK. Add the Customer Name and Address objects to the Data Group Header band. 3. Create the multiple detail bands: Select Optional Bands from the Report menu. Click the Add button to add one more detail band to the report.

The Visual FoxPro 9.0 Report Writer includes one of the most often requested features: Multiple Detail Bands.

www.code-magazine.com

The Visual FoxPro 9.0 Report Writer

33

4. Define the Detail #1 band: Double-click the gray bar of the Detail #1 band to invoke the Properties dialog box. Set the target alias to "Vehicles", remembering to use the quotes. Check the Associated header and footer bands checkbox. Do not place any objects in the Detail 1 band. 5. Create some report variables: Create a variable named rnTotalPremium, set the Value to store to Vehicles.premium, set Calculation type to sum, and set the Reset based on to Detail 1. Create a variable named rnPercent, set the Value to store to ROUND(100 * vehicles.premium / rnTotalPremium, 2), and set the Calculation type to None. 6. Define the Detail #2 band: Double-click the gray bar of the Detail 2 band to invoke the Properties dialog box. Set the target alias to "Vehicles", remembering to use the quotes. Check the

Associated header and footer bands checkbox. Add any objects needed in the Detail 2 band. Add the Percent object to the Detail 2 band with an expression of rnPercent. 7. Define the Detail #2 Footer band: Add the total premium object, set the expression to vehicles.premium, set the Calculation type to Sum, and set the Reset based on to Detail 2. Add the total percent object, set the expression to rnPercent, set the Calculation type to Sum, and set the Reset based on to Detail 2. The above report definition tells the Visual FoxPro 9.0 Report Writer to process the Vehicles table twice for each customer in the Customer table. The first time totals the premium so it can be used in the second pass. The second pass of the Vehicles table prints the data for the customer, using the report variable that was calculated during the first detail band. This process repeats for each customer in the Customer table.

A Lot to Learn
The Visual FoxPro 9.0 Report Writer has many new features to help you create better reports. The new reusable data environments allow you to share data environments among reports. Various aspects of reports, such as objects and bands, can be protected. A better development experience has been created through the new user interface. The new object layout enhancements and data group enhancements offer more options than previously available. And finally, the new multiple-detail band feature opens up many more reporting options. Together, all these improvements and enhancements give you the ability to create more complex reports than ever before.

Cathy Pountney

34

The Visual FoxPro 9.0 Report Writer

www.code-magazine.com

QUICK ID 0404052

Controls, Events, Commands, and More


Claudio Lassala
claudio@eps-software.com

Microsoft Visual FoxPro 9.0 is here and it brings lots of new features. This entire issue of CoDe is dedicated to Visual FoxPro 9.0, providing details and scenarios on how you can use the new features and enhancements. In this article, I will discuss some of the new controls, events, and commands, and a little more.

Claudio Lassala is a Senior Developer at EPS Software Corp. and a part-time Universal Thread Consultant. He has presented several lectures at Microsoft events as well as at several conferences and user groups in North America and Brazil. He is a Microsoft C# MVP, and MCSD for .NET, and also a columnist at MSDN Brazil. He is the author of several training videos that can be found at Universal Thread, and has had several articles published in various magazines, such as MSDN Brazil Magazine and FoxPro Advisor. A full-biography can be found at www.lassala.net/bio.

m sure you are as anxious as I am to explore new properties, events, and methods, new or improved commands, and FoxPro Foundation Classes (FFCs). Lets dive right in!

the IDE or on the application delivered to the users.

Anchoring Controls

Before Visual FoxPro 8.0, many lines of code had to be written in order to properly resize onscreen controls whenever a form was resized. In Visual This article highlights FoxPro 8.0, that task got slightly some of these new features to make Collection Object easier with the introduction of the introductions, but youll the BindEvent function. In Support in ComboBox want to look at the Visual FoxPro 9.0, it gets way and ListBox Controls easier with the introduction of Whats New section on the Help The RowSourceType property anchors. Anchors allow a file, because theres a lot on ComboBox and ListBox control to maintain its propormore in there. objects has a new option added tional relationship with other to the list of possible sources: objects and controls within a value 10, for the Collection object. form, no matter what size the form becomes. The Anchor property is obeyed when the control sits on containers such as Forms, CommandGroups, and Page objects. Whenever those containers get resized, the controls within it that have the Anchor property set are resized and/or repositioned depending on the settings used. Make sure to check out the Anchor Property topic on the Help file for available settings.

In order to support this feature, new members were added to the Form class, such as the Dockable and Docked properties, the Dock and GetDockState methods, Fast Facts and the AfterDock, BeforeAs is typical of every new Dock, and UnDock events. Listing 1 shows a simple version of Visual FoxPro, example that produces the new commands, properties, result showed on Figure 1. methods, and events are introduced.

A Collection could be populated with Business objects or objects holding some sort of data, and that Collection can be used to populate ComboBoxes and ListBoxes. The following code snippet shows an example of that, using a collection named colEmployees contained in the form, and the ComboBoxs RowSourceType property is set to 10.
Select EmployeeId, FirstName; from employees into cursor curTemp Scan Local loEmployee as Object Scatter NAME loEmployee Blank Scatter NAME loEmployee additive This.colEmployees.Add(loEmployee,; Transform(loEmployee.EmployeeId)) EndScan This.cboEmployees.RowSource =; "Thisform.colEmployees, FirstName, EmployeeId"

Docking Forms
Docking allows toolbars and other controls to be attached to any user-chosen edge of a Form and maintain that positioning despite resizing or scrolling. Visual FoxPro 8.0 introduced the ability to dock objects, such as the Command, Document View, and Properties windows, as well commands and functions that handled docking programmatically. Visual FoxPro 9.0 goes a step further and introduces the docking capability to user-defined forms as well. That enables creating dockable Forms on

36

Controls, Events, Commands, and More

www.code-magazine.com

Figure 1: Docking user-defined windows is simple.

Figure 2: The ListBoxs AutoHideScrollbar property provides lots of options. more items on the ListBox than what can be viewed in a single pane, up and down arrows appear on the control. Figure 2 shows a snapshot of a form with ListBoxes using the three available settings.

Rotating Label, Line, and Shape Controls


For long time now, Visual FoxPro developers have been waiting for the ability to rotate labels. The option is now available through a new Rotation property, which receives a number indicating the degrees the control will rotate. The Rotation property is also available for Line and Shape controls.

Converting Data Types with the Cast() Function


The new Cast() function converts expressions from one data type to another. Get used to writing SQL statements that are compatible with SQL Server.

NewObject() Creates Objects without Raising Initialization Code


The NewObject function now accepts 0 as the third parameter in order to create the object without raising code in the Init method. Thats useful when the interface of an object must be analyzed (using the AMember function, for example), where neither the code on the Init or Destroy methods should run.

ListBoxControls Can Hide ScrollBars


A new AutoHideScrollbar property was added to the ListBox class. This property determines whether or not the ScrollBar is shown on the ListBox, and whether it is shown if the control only has a few entries. If the default value 0 is used, the scrollbar is always visible. Value 1 determines that the scrollbar is only visible when the ListBox is full. This makes a lot of sense, as a scrollbar is of no use when all the items on the ListBox fit in a single pane. Value 2 determines that the scrollbar is never visible. If there are

The AppStates property indicates whether the application has focus (value 1), or not (value 0).

Listing 1: Docking User-Defined Windows


Local Local Local Local loMainForm as Form loCustomerForm1 as Form loCustomerForm2 as Form loCustomerForm3 as Form Read Events Define CLASS CustomerForm as Form *-- The value of one indicates this form *-- "Supports dock and is dockable" Dockable = 1 EndDefine Define CLASS MainForm as Form Dockable = 1 Procedure Destroy() Clear Events EndProc EndDefine

loMainForm = CreateObject("MainForm") loCustomerForm1 = CreateObject("CustomerForm") loCustomerForm2 = CreateObject("CustomerForm") loCustomerForm3 = CreateObject("CustomerForm") loCustomerForm1.Caption = "Customer 1" loCustomerForm2.Caption = "Customer 2" loCustomerForm3.Caption = "Customer 3" loCustomerForm1.Dock(3, loMainForm) loCustomerForm2.Dock(2, loCustomerForm1) loCustomerForm3.Dock(2, loCustomerForm2) loMainForm.Show()

www.code-magazine.com

Controls, Events, Commands, and More

37

When the following code snippet runs, youll notice that the Customer table doesnt open by the Init method:
loCustomer = NewObject("CustomerBizObj",; "NewObjectSample.prg", 0) AMembers(laMembers, loCustomer, 1) Display Memory like laMembers Define Class CustomerBizObj As Session CustomerID = "" CustomerName = ""

The new Report Engine in Visual FoxPro can leverage the power of GDI+, so if youre planning on doing powerful work with reports, take a serious look at the GDI+ FFC classes.
expressions, the harder it is to read the code, as multiple IIF calls are needed. The ICASE (immediate case) function solves that problem. The following code snippet shows an example written using both the IIF and the ICASE functions:
Local lcAnswer lcAnswer = "YES" ? Iif(lcAnswer = "YES", "answered 'yes'",; Iif(lcAnswer = "NO", "answered 'no'",; 'unanswered')) ? ICASE(lcAnswer = "YES", "answered 'yes'",; lcAnswer = "NO", "answered 'no'",; "unanswered")

Aleksey Tsingauz
Developer, Visual FoxPro Team Microsoft Visual FoxPro 9.0 significantly extends SQL capabilities. Some hard-coded limits on the amount of tables, the amount of JOIN operators, the amount of sub-queries, and the amount of UNION operators used in a single SELECT command are removed. Also, now you can enjoy multiple sub-query nesting, sub-queries in the SET and SELECT list, and subqueries in the FROM clause (derived tables). In addition, Visual FoxPro 9.0 supports the FROM clause for UPDATE and DELETE commands, which allows you to easily modify a table based on data stored in other tables. The new SET SQLBUFFERING command and WITH (BUFFERING = ...) clause allows you to query table buffered data along with those already committed. All these, and other SQL enhancements, allow you to efficiently perform very complex queries or modifications using a single SQL command.

Procedure Init Use Home(1)+"\samples\northwind\Customers" EndProc Procedure GetCustomersByPk(lcPK As String) EndProc EndDefine

Set Path Command Enhancements


An Additive clause was added to the Set Path command. When that clause is used, the path being specified is added to the path setting, if it isnt there already. Besides that, the Set Path commands maximum character limit has been increased to 4095 characters.

AppState Property Detects an Application Losing or Receiving Focus


The _Screen object has a new property called AppState. This property is only available read-only at runtime, and it indicates whether the Visual FoxPro application has focus (value 1), or not (value 0). The value changes automatically whenever the application loses or receives focus, such as when the user uses Alt+Tab. A Timer object, or event handler (using the BindEvent function) can be used to query this property and execute code whenever its value changes.

TTOC() Converts DateTime Expressions to XML DateTime Format


Visual FoxPro 9.0 joins the ranks of great software XML adopters. The TTOC function has been improved and receives a value 3 as the second parameter, indicating that the resulting string must be formatted as an XML DataTime format.
? Ttoc(Datetime(),3)

The settings Set Century, Set Hours, or Set Seconds dont affect the result of the TTOC function when the parameter 3 is passed.

Specify Where Focus is Assigned in the Valid Event


Its always been a pain when something happens inside a Valid event and you want the focus to go to another control, as calling SetFocus from within a Valid event is not allowed. Thats been solved in Visual FoxPro 9.0 by allowing you to return the execution to a specified object from within the Valid event:
Return Thisform.txtCity

Log Execution Plans Output from SYS(3054) Using SYS(3092)


The SYS(3054) function is very helpful for optimizing SQL queries, as it produces an execution plan of which indexes, if any, are in use, and enabling optimization of queries by creating new indexes or fine-tuning the way the queries are written. The new SYS(3092) function provides the ability to write the output of SYS(3054) directly to a file on disk.
*-- Turn on execution plan. Sys(3054, 12) *-- Set up output file. Sys(3092, "c:\ExecutionPlan.txt", .T.)

ICASE() Function
The IIF() (immediate if) function has always been a very useful function, but the more complex the

38

Controls, Events, Commands, and More

www.code-magazine.com

*-- Run Query. Select * ; from Home(1)+"\samples\northwind\Customers" ; into cursor curCustomers *-- Turn off output and close file. Sys(3092, "") *-- Let's see the log. Modify File "c:\ExecutionPlan.txt"

Populate an Array with Aliases Used by a Specified Table


The AUsed function improved to allow passing a third parameter, one that indicates that the array created by this function should only look for aliases of a specific table. The following code snippet shows a simple example:
Select * ; from Home(1)+"\samples\northwind\Customers" ; into cursor curCustomers Select * ; from Home(1)+"\samples\northwind\employees" ; into cursor curEmployees ? AUSED(gaInuse, Set("Datasession"), "Employees") DISPLAY MEMORY LIKE gaInuse

Extended SQL Capabilities


There are many improvements to the SQL subset in Visual FoxPro 9.0. With each new release, the Visual FoxPro team moves Visual FoxPros SQL closer to T-SQL (SQL Servers SQL implementation). Some of the most notable improvements are: The limit of nine join clauses and sub-queries was removed from the SQL Select statement, as were the nine Union clauses limit, the 30 tables limit, and the referenced aliases limit. The level of nesting for sub-queries in a SQL Select statement, formerly limited to only one level, is now unlimited. The list of fields and the FROM clause can now have a sub-query. This works for the ON clause on joins as well. The Like clause is now optimized. Make sure to read the Data Features Enhancements section under the Whats New in Visual FoxPro on the Help file for more specific information about all the improvements on these subjects.

In the sample above, an array shows the aliases and work area numbers for the Employees table available at the current data session.

SQLIdleDisconnect() Temporarily Disconnects SQL Pass-Through Connections


The new SQLIdleDisconnect function is a great new addition to the SQL Pass-Through set of functions. This function temporarily disconnects a connection handle passed to it (or it disconnects all connections, if a 0 is passed). Thats a great feature for disconnected scenarios, where an application doesnt have to hold a connection to the database even when the user is reading retrieved data, rather than sending data back and forth regularly. Plus, when the connection must be enabled again in order to process something, it happens automatically, using the original connection string.

Set Coverage Command Available at Runtime


The Set Coverage command is now available at run time. Anywhere inside an application, the Set Coverage command can be called like this:
SET COVERAGE TO C:\MyApp.log

The Set Coverage command creates a log file for the code being executed. The Coverage Profiler tool (available on the Visual FoxPro IDE under the Tools menu) can then be used to analyze this log file. The Coverage Profiler tool shows the codes coverage, indicating which lines are being executed, and which ones never get executed; it also shows statistics, such as how many times a line is executed, and how long that takes, helping you find code bottlenecks. This new ability of enabling coverage during runtime is also useful for debugging errors that occur during runtime, but that never happen in interactive mode (through the Visual FoxPro IDE).

Retrieving Active SQL Connection Statement Handles


The new ASQLHandles function creates an array with the handles for all active SQL connections. Those handles can then be used with other functions such as SQLExec and SQLDisconnect.
lnConn1 = SQLStringConnect(; "driver=sql server;server=(local); "+; "database=northwind;") lnConn2 = SQLStringConnect(; "driver=sql server;server=(local); "+; "database=pubs;")

www.code-magazine.com

Controls, Events, Commands, and More

39

ASQLHandles(laHandles) Display Memory like laHandles

Get Cursor and Count Records Affected by SQL Pass-Through Execution


The SQLExec function has been improved in order to make it possible to immediately get cursor and count records affected by the SQL Pass-Through command. A parameter is passed determining the name of the created array with a list of aliases and a record count produced by the SQL commands. This code snippet demonstrates this enhancement, producing multiple result-sets:
lnConn = SQLStringConnect("driver=sql server;"+; "server=(local);database=northwind;") SQLExec(lnConn, ; "Select * from Customers; "+; "Select * from Employees", "", aCount) Display Memory like aCount

The SQLExec function makes it possible to get cursor and count records affected by the SQL PassThrough command
By using GDI+, you can manipulate graphics and images, so this is a really powerful feature. Entire articles could be devoted to this topic, exploring the different classes, how to use them, and when to use them, but its beyond the scope of this article. Ill just mention these classes here so that you dont overlook this new feature when you are all excited by the other new features. One simple example of image manipulation is a small program that takes an existent bitmap file on disk, and creates a new JPEG out of the original one, as shown in the following code snippet implementation:
*-- Create an Image object. Local oLogoImage As GpImage ; Of Home(1)+"ffc/_gdiplus.vcx" oLogoImage = ; Newobject('GpImage',Home(1)+'ffc/_gdiplus.vcx')

View Classes Inside a Program File (.prg) with the Class Browser
The Class Browser now has the ability to manage program-based (PRG) classes. In previous versions, only Visual Class Library-based (VCX) classes could be viewed. For developers who write several PRGbased classes (like me!), this is definitely a verywelcome feature.

GDI+ FFC classes


Visual FoxPro 9.0 has a new FFC (FoxPro Foundation Class) library for the Graphics Device Interface (GDI) called _GDIPlus.vcx. This library has many classes working as wrappers around the GDI+ API. Figure 3: The image can be drawn on a form using GDI+.

Listing 2: An image can be drawn on a form using GDI+.


Public oForm As Form oForm = Createobject("MyFoxForm") oForm.Show() Define Class MyFoxForm As Form DoCreate = .T. Name = "Form1" Procedure Paint *-- Create Graphics object. Local oGr As GpGraphics Of Home(1)+"ffc/_gdiplus.vcx" oGr = Newobject('GpGraphics',Home(1)+'ffc/_gdiplus.vcx') *-- Get the Form's (Window's) handle. oGr.CreateFromHWND(Thisform.HWnd) *-- Create an Image object. Local oLogoImage As GpImage Of Home(1)+"ffc/_gdiplus.vcx" oLogoImage = Newobject('GpImage',Home(1)+'ffc/_gdiplus.vcx') *-- Load a bitmap file in memory. oLogoImage.CreateFromFile("c:\Fox.bmp") *-- Draw Image object on Graphic's surface (here, the Form). oGr.DrawImageAt(oLogoImage,0,0) EndProc EndDefine

40

Controls, Events, Commands, and More

www.code-magazine.com

*-- Load an image file in memory. oLogoImage.CreateFromFile("c:\Fox.bmp") *-- Save the image to a JPG file on disk. oLogoImage.SaveToFile("c:\Fox.jpg",; oLogoImage.GetEncoderCLSID("image/jpeg"))

Another example is the ability to manipulate images and draw them on a device such as a form. Listing 2 shows off an implementation of it, and Figure 3 show the result. Notice that the image shown on the form was really drawn right into it, instead of having been added using the Visual FoxPro Image object. The important thing to notice is that before being drawn, the image can be manipulated, perhaps adding a logo or watermark to it, or text can be drawn over it. Its also worth mentioning that the new Report engine in Visual FoxPro can leverage the power of GDI+, so if youre planning on doing powerful stuff with reports, take a serious look at the GDI+ FFC classes. These classes (shown in Figure 4) map to the classes available on the .NET Framework. I strongly suggest that you read some articles about GDI+ with .NET Framework published in previous editions of CoDe Magazine, so that you can understand what sorts of things can be done with this technology, and then you should be able to translate most of the .NET code into Visual FoxPro using the FFC classes. Figure 4: New FFC classes for GDI+ are similar to the ones found in the .NET Framework.

Conclusion
There are many improvements and new features in Visual FoxPro 9.0 that are both productivity tools and exciting new ways of building interesting and useful applications. Ive only briefly covered some of the developments youll find when you start playing with this new version. Make sure you take a close look into the Whats New in Visual FoxPro section on the Help file, and have fun!

Claudio Lassala

CoDe Magazine www.code-magazine.com dFPUG c/o ISYS GmbH , Germany www.visualextend.com EPS Software Corp. www.eps-software.com F1 Technologies www.f1tech.com Micromega Systems www.micromegasystems.com Microsoft msdn.microsoft.com/visual/think ProLib Software www.AFPages.com Southwest Fox Conference www.southwestfox.com Stonefield Systems Group www.stonefieldquery.com Take Note Technologies www.takenote.com

6, 35, 60

Vision Data Solutions www.visionds.com West Wind Technologies www.west-wind.com

68

25

15, 34

19, 29, 42-43, 62-63


This listing is provided as a courtesy to our readers and advertisers. The publisher assumes no responsibility for errors or omissions.

67

2-3

Advertising Sales: Vice President, Sales and Marketing Michelle Yates 703-328-0333 myates@code-magazine.com

11 Sales Managers Erna Egger +43 (664) 151 0861 erna@code-magazine.com Tammy Ferguson 832-717-4445 ext 26 tammy@code-magazine.com

32

www.code-magazine.com

Controls, Events, Commands, and More

ADVERTISING INDEX

41

Extending the Visual FoxPro 9.0 Reporting System


Doug Hennig
dhennig@stonefield.com

QUICK ID 0404032

Doug Hennig is a partner with Stonefield Software Inc. He is the author of the awardwinning Stonefield Database Toolkit (SDT), the awardwinning Stonefield Query, and the MemberData Editor, Anchor Editor, and CursorAdapter and DataEnvironment builders that come with Visual FoxPro. Doug is co-author of the Whats New in Visual FoxPro series and The Hackers Guide to Visual FoxPro 7.0. He was the technical editor of The Hackers Guide to Visual FoxPro 6.0 and The Fundamentals. All of these books are from Hentzenwerke Publishing. Doug has spoken at every Microsoft FoxPro Developers Conference (DevCon) since 1997 and at user groups and developer conferences all over North America. He is a Microsoft Most Valuable Professional (MVP). For more information about Stonefield and its products, visit www.stonefield.com and
www.stonefieldquery.com

Among the new and improved features in Microsoft Visual FoxPro 9.0, youll find the ability to extend the behavior of the reporting system when running reports. In this article, youll learn about Visual FoxPro 9.0s report listener concept, how it receives events as a report runs, and how you can create your own listeners to provide different types of output in addition to print and preview.

here are incredible improvements in the Visual FoxPro 9.0 reporting system. Of several aspects, Ill discuss just one in this article: the ability to extend the behavior of the runtime reporting engine. The Visual FoxPro development team had several goals in mind when they worked on the runtime improvements, including: Handling more types of report output than just printing and previewing Using GDI+ for report output, providing many significant improvements, such as more accurate rendering, smooth scaling up and down of images and fonts, and additional capabilities, such as text rotation. Providing a more flexible and extendible reporting system

listener, which handles rendering and output. Because report listeners are classes, we can now interact with the reporting process in ways we could only dream of before.

also giving us the ability to change how a report is rendered.

Report listeners produce output in two ways. Pageat-a-time mode renders a page and outputs it, then renders and outputs the next page, and so on. This mode is Fast Facts used when a report is printed. The Visual FoxPro 9.0 In all-pages-at-once mode, the report listener renders all the reporting system raises events in a pages and caches them in new base class, called memory. It then outputs these ReportListener. ReportListener is rendered pages on demand. the key to the different This mode is used when a types of provided report output, report is previewed.

New Reporting Syntax

You have access to both the old report engine and the new one, so you can run reports under either engine as you see fit. But once you see the benefits of the new report engine, you wont want to go back to old-style reporting.

Reporting System Architecture


Before Visual FoxPro 9.0, the report engine was monolithic; it handled everything and with a few exceptions (user-defined function, expressions for OnEntry and OnExit of bands, and so forth), you couldnt interact with it during a report run. The new reporting engine splits responsibility for reporting between the report engine, which now deals only with data handling and object positioning, and a new object known as a report

Visual FoxPro 9.0 supports running reports using the old report engine; you use the REPORT command just as you did before (although, as youll see in a moment, you can use a new command to override the behavior of REPORT). To get new-style reporting behavior, use the new OBJECT clause of the REPORT command. The OBJECT clause supports two ways of using it: by specifying a report listener and by specifying a report type. Microsoft refers to this as object-assisted reporting. A report listener is an object that provides new-style reporting behavior. Report listeners are based on a new base class in Visual FoxPro 9.0 called ReportListener. To tell Visual FoxPro 9.0 to use a specific listener for a report, instantiate the listener class and then specify the objects name in the OBJECT clause of the REPORT command. Heres an example:
loListener = createobject('MyReportListener') report form MyReport object loListener

44

Extending the Visual FoxPro 9.0 Reporting System

www.code-magazine.com

If youd rather not instantiate a listener manually, you can have Visual FoxPro do it for you automatically by specifying a report type:
report form MyReport object type 1

For example, suppose ListenerA and ListenerB are both subclasses of _ReportListener that each perform some task, and you want to use both listeners for a certain report. Heres how these listeners can be chained together:
loListener = createobject('ListenerA') loListener.Successor = createobject('ListenerB') report form MyReport object loListener

The defined types are 0 for outputting to a printer, 1 for previewing, 2 for page-at-a-time mode but not to send the output to a printer, 3 for all-pages-at-once mode but not invoke the preview window, 4 for XML output, and 5 for HTML output. Other, userdefined, types can also be used. When you run a report this way, the application specified in the new _REPORTOUTPUT system variable (ReportOutput.APP in the Visual FoxPro home directory by default) is called to determine which listener class to instantiate for the specified type. It does this by looking for the listener type in a listener registry table built into the APP (although you can also tell it to use an external table). If it finds the desired class, it instantiates the class and gives a reference to the listener object to the reporting engine. Thus, using OBJECT TYPE SomeType in the REPORT command is essentially the same as:
loListener = .NULL. do (_ReportOutput) with SomeType, loListener report form MyReport object loListener

The report engine only communicates with the listener specified in the REPORT or LABEL command, called the lead listener. As the report engine raises report events, the lead listener calls the appropriate methods of its successor, the successor calls the appropriate methods of its successor, and so on down the chain. This type of architecture is known as a chain of responsibility, as any listener in the chain can decide to take some action or pass the message on to the next item in the chain. Another interesting capability of _ReportListener is chaining reports. The AddReport method adds a report to the custom ReportFileNames collection. You pass this method the name of a report and optional report clauses to use (such as the RANGE clause) and a reference to another listener object. The RemoveReports method removes all reports from the collection. RunReports runs the reports; pass it .T. for the first parameter to remove reports from the collection after

SET REPORTBEHAVIOR
Instead of modifying the REPORT commands in your application to use the OBJECT clause, you can use SET REPORTBEHAVIOR 90 to turn on object-assisted reporting by default. This means the REPORT command behaves as if youve specified OBJECT TYPE 0 when you use the TO PRINT clause or OBJECT TYPE 1 when you use the PREVIEW clause. SET REPORTBEHAVIOR 80 reverts to behavior from Visual FoxPro 8.0 and earlier.

ReportListener
During the run of a report, Visual FoxPro exposes reporting events to objects based on the ReportListener base class as they happen. The Visual FoxPro Help file has complete documentation on the properties, events, and methods (PEMs) of ReportListener, so Ill only discuss the most useful ones in this article. Table 1 lists the most commonly used properties of ReportListener. Table 2 lists the most commonly used events and methods of ReportListener.

Property
CurrentDataSession FRXDataSession GDIPlusGraphics ListenerType

Description
The data session ID for the reports data The data session ID for the FRX cursor The handle for the GDI+ graphics object used for rendering The type of report output the listener produces. The default is -1, which specifies no output, so youll need to change this to a more reasonable value. The values are the same as those specified in the OBJECT TYPE clause of the REPORT command. The number of pages rendered .T. (the default is .F.) to suppress progress information

OutputPageCount QuietMode

_ReportListener
The FFC (FoxPro Foundation Classes) subdirectory of the Visual FoxPro home directory includes _ReportListener.VCX, which contains some subclasses of ReportListener that have more functionality than the base class. The most useful of these is _ReportListener. One of the most important features of _ReportListener is support for successors. Its possible you may want to use more than one report listener when running a report. For example, if you want to both preview a report and output it to HTML at the same time, more than one report listener must be involved. _ReportListener allows chaining of listeners by providing a Successor property that can contain an object reference to another listener.

Table 1: Some useful properties of the ReportListener class.

Event/Method
LoadReport UnloadReport BeforeReport AfterReport BeforeBand AfterBand EvaluateContents Render OutputPage

Description
Fires before the FRX is loaded and the printer spool is opened Fires after the report has been run Fires after the FRX has been loaded but before the report has been run Fires after the report has been run Fires before a band is processed Fires after a band is processed Fires before a field is rendered Fires as an object is being rendered Outputs the specified rendered page to the specified device

Table 2: Some useful events and methods of the ReportListener class.

www.code-magazine.com

Extending the Visual FoxPro 9.0 Reporting System

45

theyre run and .T. for the second parameter to ignore any listeners specified in AddReport. Heres an example that runs two reports as if they were a single one:
loListener = newobject('_ReportListener', ; home() + 'ffc\_ReportListener.vcx') loListener.ListenerType = 1 loListener.AddReport('MyReport1.frx', ; 'nopageeject') loListener.AddReport('MyReport2.frx') loListener.RunReports()

Both of these listener classes have additional properties you can use to further control the output. I recommend a look at the Visual FoxPro documentation for details. Also, as they are subclasses of _ReportListener, listener classes support the capabilities of the _ReportListener class, including chaining listeners and running multiple reports. Heres an example that outputs to both XML and HTML at the same time:
use _samples + 'Northwind\Orders' loListener1 = .NULL. do (_reportoutput) with 4, loListener1 loListener1.TargetFileName = 'MyReport.xml' loListener1.QuietMode = .T. loListener1.XMLMode = 0 && 0 = data only, 1 = layout only, 2 = both loListener2 = .NULL. do (_reportoutput) with 5, loListener2 loListener2.TargetFileName = 'MyReport.html' loListener2.QuietMode = .T. loListener1.Successor = loListener2 report form MyReport object loListener1

Richard Stanton
Developer, Visual FoxPro Team Microsoft The enhanced report engine emphasizes backward compatibility, one of Visual FoxPro's strongest aspects. Many of the new reporting features are designed so that they are easy to integrate into existing applications. For example, you can use the new SET REPORTBEHAVIOR command to quickly run and preview reports using the new reporting engine or the old engine. Visual FoxPro 9.0 also includes features such as object-assisted runtime report processing using the new custom ReportListener class, multiple-detail bands, report chaining, and many other design time enhancements. You can add all these features incrementally to existing reports, making it easy to take advantage of the functionality you want without major design changes.

HTML and XML Output


Because one of the design goals of the development team was to provide additional types of report output, Visual FoxPro 9.0 includes two subclasses of _ReportListener, called the HTMLListener, and the XMLListener, providing HTML and XML output, respectively. These listeners are built into ReportOutput.APP but are also available in _ReportListener.VCX. Listener type 5 specifies HTML output and type 4 is for XML output, so you could just use the following command to output to HTML:
report form MyReport object type 5

Creating Your Own Listeners


Because report listeners are a class, you can create subclasses that alter the behavior of the reporting system when a report runs. For example, one thing Ive always wanted is a way to dynamically format a field at runtime. Under some conditions, I may want a field to print with red text, and under others, I want it in black. Perhaps a field should sometimes be bold and the rest of the time not. The key to changing the way a field appears in a report is the EvaluateContents method. This method fires for each field object just before its rendered, and gives the listener the opportunity to change the appearance of the field. The first parameter is the FRX record number for the field object being processed and the second is an object containing properties with information about the field object. (See the Visual FoxPro Help file for a list of the properties this object contains.) You can change any of these properties to change the appearance of the field in the report. If you do so, set the Reload property of the object to .T. to notify the report engine that youve changed one or more of the other properties. Listing 1 shows some code (TestDynamicFormatting.PRG) defining a subclass of _ReportListener called EffectsListener, that handles different types of effects that may be applied to fields in a report. These effects are applied by effect handler objects, which are stored in a collection in the oEffectsHandlers property of EffectsListener. Each effect handler object handles a single effect As the report is processed, the listener needs to determine which fields will have effects applied to them. It

However, this doesnt give you any control over the name of the file to create or other settings. Instead, call ReportOutput.APP to give you a reference to the desired listener, set some properties, and then tell the REPORT command to use that listener. The following code creates an HTML file called MyReport.HTML from the MyReport report. When you specify type 5, ReportOutput.APP uses its builtin HTMLListener class to provide output.
loListener = .NULL. do (_reportoutput) with 5, loListener loListener.TargetFileName = 'MyReport.html' loListener.QuietMode = .T. report form MyReport object loListener

The following code creates an XML file, called MyReport.XML from the MyReport report, containing only the data. In this case, the XMLListener class (type 4) is used.
loListener = .NULL. do (_reportoutput) with 4, loListener loListener.TargetFileName = 'MyReport.xml' loListener.QuietMode = .T. loListener.XMLMode = 0 && 0 = data only, 1 = layout only, 2 = both report form MyReport object loListener

HTML output actually uses the XML listener to produce XML and then uses XSLT to produce the HTML end-result.

46

Extending the Visual FoxPro 9.0 Reporting System

www.code-magazine.com

Listing 1: TestDynamicFormatting.PRG shows that the EvaluateContents method of ReportListener can change the way a field appears in a report.
use _samples + 'Northwind\Orders' loListener = createobject('EffectsListener') loListener.OutputType = 1 report form TestDynamicFormatting.FRX preview object loListener * Define a class that knows how to apply effects to objects in a * report. define class EffectsListener as _ReportListener of ; home() + 'ffc\_ReportListener.vcx' oEffectHandlers = .NULL. && a collection of effect handlers dimension aRecords[1] && an array of information for each record in the FRX * Create a collection of effect handler objects and fill it with * the handlers we know about. A subclass or instance could be * filled with additional ones. function Init dodefault() with This .oEffectHandlers = createobject('Collection') .oEffectHandlers.Add(createobject('DynamicForeColorEffect')) .oEffectHandlers.Add(createobject('DynamicStyleEffect')) endwith endfunc * * * * * Dimension aRecords to as many records as there are in the FRX so we don't have to redimension it as the report runs. The first column indicates if we've processed that record in the FRX yet and the second column contains a collection of effect handlers used to process the record. function BeforeReport dodefault() with This .SetFRXDataSession() dimension .aRecords[reccount(), 2] .ResetDataSession() endwith endfunc * Apply any effects that were requested to the field about to be * rendered. function EvaluateContents(tnFRXRecno, toObjProperties) local loEffectObject, ; loEffectHandler, ; lcExpression with This * If we haven't already checked if this field needs any effects, do * so and flag that we have checked it so we don't do it again. if not .aRecords[tnFRXRecno, 1] .aRecords[tnFRXRecno, 1] = .T. .aRecords[tnFRXRecno, 2] = ; .SetupEffectsForObject(tnFRXRecno) endif not .aRecords[tnFRXRecno, 1] * Go through the collection of effect handlers for the field (the * collection may be empty if the field doesn't need any effects), * letting each one do its thing. for each loEffectObject in .aRecords[tnFRXRecno, 2] loEffectHandler = loEffectObject.oEffectHandler lcExpression = loEffectObject.cExpression loEffectHandler.Execute(toObjProperties, lcExpression) next loEffect endwith * Do the normal behavior.

Handling dodefault(tnFRXRecno, toObjProperties) Datasessions endfunc


* Go through each effect handler to see There are three datasessions if it'll handle the current * report object. If so, add it to a collection of handlers forrun. involved during a report the * object, and return that collection. The first is the datasession in

which the ReportListener is


function SetupEffectsForObject(tnFRXRecno) instantiated. This is often the local loFRX, ; default datasession. The loHandlers, ; second is the datasession in loObject which Visual FoxPro creates a with This copy of the FRX file into a .SetFRXDataSession() cursor called FRX (this copy go tnFRXRecno can be used by a listener). The scatter memo name loFRX FRXDataSession property .ResetDataSession() contains the datasession ID for loHandlers = createobject('Collection') this cursor, so use SET for each loEffectHandler in .oEffectHandlers DATASESSION TO loObject = loEffectHandler.GetEffect(loFRX) This.FRXDataSession if you if vartype(loObject) = 'O' need access to the FRX. The loHandlers.Add(loObject) third datasession is the one in endif vartype(loObject) = 'O' which the reports data resides. next loEffectHandler If the report has a private endwith datasession, it is a unique return loHandlers datasession; otherwise, the endfunc report resides in the default enddefine datasession. The Current-

DataSession property tells you


* Create a class that holds a reference which datasession to use. to an effect handler and * the expression the effect handler is supposed toto save the a Remember act on for * particular record in the FRX. ReportListeners datasession define class EffectObject as Custom oEffectHandler = .NULL. cExpression = '' enddefine

and switch back to it after selecting either the FRX or report data datasession.

* Define an abstract class for effect handler objects. define class EffectHandler as Custom * Execute is called by the EvaluateContents method of * EffectsListener to perform an effect. function Execute(toObjProperties, toFRX) endfunc * GetEffects is called to return an object containing a reference * to the handler and the expression it's supposed to work on if the

www.code-magazine.com

Extending the Visual FoxPro 9.0 Reporting System

47

does that in the EvaluateContents method by looking at each field as its about to be rendered. EvaluateContents calls SetupEffectsForObject, which calls the GetEffect method of each effect handler to let it decide whether to apply an effect to the field. GetEffect looks in the USER memo of the fields record in the FRX for a directive indicating what type of effect to apply. If a particular handler is needed for the field, a reference to the handler is added to a collection of handlers that processes the field (as a field may have more than one effect applied to it).

However, EvaluateContents fires for every field in every record, and there really isnt a need to check for the effects more than once for a particular field (doing so would slow down the performance of the report). So, BeforeReport creates an array with as many rows as there are records in the FRX. If the first column of the array is the default .F., the listener hasnt checked for the effects of the field being rendered yet, so EvaluateContents does that

Listing 1: Continued
* specified report object needs this effect, or return null if not. function GetEffect(toFRX) local loObject loObject = .NULL. return loObject endfunc * EvaluateExprssion may be called by Execute to evaluate the * specified expression. function EvaluateExpression(tcExpression) return evaluate(tcExpression) endfunc enddefine * Define an abstract class for effect handlers that look for * "*:EFFECTS <effectname> = <effectexpression>" in the USER memo. define class UserEffectHandler as EffectHandler cEffectsDirective = '*:EFFECTS' && the directive that indicates an effect is needed cEffectName = '' && the effect name to look for (filled in in a subclass) function GetEffect(toFRX) local lcEffect, ; laLines[1], ; lnRow, ; lcLine, ; lnPos, ; loObject lcEffect = This.cEffectsDirective + ' ' + This.cEffectName if atc(lcEffect, toFRX.User) > 0 alines(laLines, toFRX.User) lnRow = ascan(laLines, lcEffect, -1, -1, 1, 13) lcLine = laLines[lnRow] lnPos = at('=', lcLine) loObject = createobject('EffectObject') loObject.oEffectHandler = This loObject.cExpression = alltrim(substr(lcLine, lnPos + 1)) else loObject = .NULL. endif atc(lcEffect, toFRX.User) > 0 return loObject endfunc enddefine * Define a class to provide dynamic forecolor effects. define class DynamicForeColorEffect as UserEffectHandler cEffectName = 'FORECOLOR' * Evaluate the expression. If the result is a numeric value and * doesn't match the existing color of the object, change the * object's color and set the Reload flag to .T. function Execute(toObjProperties, tcExpression, toFRX) local lnColor, ; lnPenRed, ; lnPenGreen, ; lnPenBlue lnColor = This.EvaluateExpression(tcExpression) if vartype(lnColor) = 'N' lnPenRed = bitand(lnColor, 0x0000FF) lnPenGreen = bitrshift(bitand(lnColor, 0x00FF00), 8) lnPenBlue = bitrshift(bitand(lnColor, 0xFF0000), 16) if toObjProperties.PenRed <> lnPenRed or ; toObjProperties.PenGreen <> lnPenGreen or ; toObjProperties.PenBlue <> lnPenBlue with toObjProperties .PenRed = lnPenRed .PenGreen = lnPenGreen .PenBlue = lnPenBlue .Reload = .T. endwith endif toObjProperties.PenRed <> lnPenRed ... endif vartype(lnColor) = 'N' endfunc enddefine * Define a class to provide dynamic style effects. define class DynamicStyleEffect as UserEffectHandler cEffectName = 'STYLE' * Evaluate the expression. If the result is a numeric value and * doesn't match the existing style of the object, change the * object's style and set the Reload flag to .T. function Execute(toObjProperties, tcExpression, toFRX) local lnStyle lnStyle = This.EvaluateExpression(tcExpression) if vartype(lnStyle) = 'N' and ; toObjProperties.FontStyle <> lnStyle toObjProperties.FontStyle = lnStyle toObjProperties.Reload = .T. endif vartype(lnStyle) = 'N' ... endfunc enddefine

48

Extending the Visual FoxPro 9.0 Reporting System

www.code-magazine.com

Figure 2: This is the TestColumnChart.FRX as it looks at design time.

Figure 1: The code in Listing 1 produced this report, which shows dynamic formatting for the Shipped Date and Ship Via columns.

and then sets the first column of the array to .T. so the FRX record isnt examined again. After determining whether there are any effects to be applied to the field, EvaluateContents then goes through the collection of effect handlers for the field, calling the Execute method of each one to have it do whatever is necessary. DynamicForeColorEffect is one of the effect handlers. It looks for a directive in the USER memo of a field in the report with the following format:
*:EFFECTS FORECOLOR = expression

Figure 3: The code in Listing 2 produced this report, which creates a column chart rather than traditional output.

(You can access the USER memo of an object in a report from the Other page of the properties dialog box for that object.) The ORDERDATE field of the TestDynamicFormatting report used in Listing 1 has the directive in the following snippet in its USER memo; it tells EffectsListener that the DynamicForeColorEffect object should adjust the color of the field so it displays in red if the date shipped is more than 10 days after it was ordered or black if not:
*:EFFECTS FORECOLOR = iif(SHIPPEDDATE > ORDERDATE + 10, rgb(255, 0, 0), rgb(0, 0, 0))

The Execute method of DynamicForeColorEffect changes the color of the field by setting the PenRed, PenGreen, and PenBlue properties of the field properties object that was passed to EvaluateContents to the appropriate colors and sets Reload to .T., which tells the report engine that changes were made. DynamicStyleEffect uses a similar directive to change the font style. The style to use must be a numeric value: 0 is normal, 1 is bold, 2 is italics, and 3 is bold and italics. The SHIPVIA field in the TestDynamicFormatting report has the following directive in USER, which causes the field to display in bold if SHIPVIA is 3 (which, because of the expression for the field, actually displays as Mail) or normal if not:
*:EFFECTS STYLE = iif(SHIPVIA = 3, 1, 0)

The reporting engine in Visual FoxPro 9.0 splits responsibility for reporting between the report engine, which now just deals with data handling and object positioning, and a new object known as a report listener, which handles rendering and output.

DynamicStyleEffect works the same as DynamicForeColorEffect, but changes the Style property of the field properties object. Running TestDynamicFormatting.PRG results in the output shown in Figure 1.

Custom Rendering
You arent limited to changing the appearance of a fieldyou can do just about anything you like in a report listener. The Render method of ReportListener

www.code-magazine.com

Extending the Visual FoxPro 9.0 Reporting System

49

Listing 2: TestColumnChart.PRG shows how you can do custom rendering in a report to produce a column chart.
* Define some constants we'll use. #define #define #define #define FRX_OBJCOD_TITLE FRX_OBJCOD_PAGEHEADER GDIPLUS_FontStyle_Regular GDIPLUS_Unit_Point 0 1 0 3 && && && && OBJCODE for title band OBJCODE for page header band GDI+ font style: regular GDI+ units: points .aColumnColors[5] && Yellow .aColumnColors[6] && Cyan .aColumnColors[7] && Orange .aColumnColors[8] && Purple endwith dodefault() endfunc = .CreateColor(rgb(255, 255, 0))

= .CreateColor(rgb( 0, 255, 255)) = .CreateColor(rgb(255, 128, = .CreateColor(rgb(128, 0))

0, 255))

* Open the data for the report. close databases all open database _samples + 'Northwind\Northwind' use Category_Sales_For_1997 * Create the listener and run the report. loListener = createobject('ColumnChartListener') loListener.OutputType = 1 report form TestColumnChart object loListener * The ColumnChartListener class. define class ColumnChartListener as _ReportListener of ; home() + 'ffc\_ReportListener.vcx' oGDIGraphics = .NULL. && reference to GPGraphics _GDIPlus object dimension aRecords[1] && array of flags for each FRX record dimension aValues[1] && array of labels and values to chart nCurrentRow = 0 && current row being processed in aValues dimension aColumnColors[1] && array of column colors nSpacing = 100 && space between columns nLegendSpacing = 300 && spacing between the chart and its legend nLegendBoxSize = 200 && the size of a legend box nLegendBoxSpacing = 100 && the spacing between items in the legend nLegendTextSpacing = 50 && the spacing between boxes and text in the legend cLegendFontName = 'Arial' && font name for legend text nLegendFontSize = 10 && font size for legend text * Set the colors for the various columns. function Init with This dimension .aColumnColors[8] .aColumnColors[1] = .CreateColor(rgb( 0, 0, 255)) && Blue .aColumnColors[2] = .CreateColor(rgb( 0, 255, 0)) && Green .aColumnColors[3] = .CreateColor(rgb(255, 0, 0)) && Red .aColumnColors[4] = .CreateColor(rgb(255, 0, 255)) && Magenta * * * * * * *

* Do some setup tasks before the report starts. function BeforeReport dodefault() with This * Create a GPGraphics object so we can do GDI+ drawing. .oGDIGraphics = newobject('GPGraphics', ; home() + 'ffc\_GDIPlus.vcx') Dimension aRecords to as many records as there are in the FRX so we don't have to redimension it as the report runs. The first column indicates if we've processed that record in the FRX yet, the second column is .T. for a Column chart object, the third column is .T. for a field containing values for chart labels, and the fourth column is .T. for a field containing values for the chart data. .SetFRXDataSession() dimension .aRecords[reccount(), 4] .ResetDataSession() endwith endfunc * Because the GDI+ plus handle changes on every page, we need to * set our SharedGDIPlusGraphics property appropriately and set the * handle for our GPGraphics object. function BeforeBand(tnBandObjCode, tnFRXRecNo) with This if inlist(tnBandObjCode, FRX_OBJCOD_PAGEHEADER, ; FRX_OBJCOD_TITLE) if not .IsSuccessor .SharedGDIPlusGraphics = .GDIPlusGraphics endif not .IsSuccessor .oGDIGraphics.SetHandle(.SharedGDIPlusGraphics) endif inlist(tnBandObjCode ... dodefault(tnBandObjCode, tnFRXRecNo) endwith endfunc * Return a SCATTER NAME object for the specified record in the FRX. procedure GetReportObject(tnFRXRecno) local loObject This.SetFRXDataSession() go tnFRXRecno scatter memo name loObject This.ResetDataSession() return loObject

50

Extending the Visual FoxPro 9.0 Reporting System

www.code-magazine.com

Listing 2: Continued
endproc * Handle a shape to see if it's a column chart. * If this is a data value, add it to the current total. procedure AdjustObjectSize(tnFRXRecno, toObjProperties) local loObject with This * * * * If we haven't already checked if this object is a column chart, find its record in the FRX and see if its USER memo contains "COLUMNCHART". Then flag that we have checked it so we don't do it again. if not .aRecords[tnFRXRecno, 1] loObject = .GetReportObject(tnFRXRecno) .aRecords[tnFRXRecno, 1] = .T. .aRecords[tnFRXRecno, 2] = atc('COLUMNCHART', ; loObject.User) > 0 endif not .aRecords[tnFRXRecno, 1] * If this is supposed to be a column chart, make its width the same * as its height. if .aRecords[tnFRXRecno, 2] toObjProperties.Height = toObjProperties.Width toObjProperties.Reload = .T. endif .aRecords[tnFRXRecno, 2] endwith endproc * Handle a field to see if it's involved in the column chart. procedure EvaluateContents(tnFRXRecno, toObjProperties) local loObject, lcText, lnRow with This * * * * If we haven't already checked if this object is involved in the column chart, find its record in the FRX and see if its USER memo contains "LABEL" or "VALUE". Then flag that we have checked it so we don't do it again. if not .aRecords[tnFRXRecno, 1] loObject = .GetReportObject(tnFRXRecno) .aRecords[tnFRXRecno, 1] = .T. .aRecords[tnFRXRecno, 3] = atc('LABEL', loObject.User) > 0 .aRecords[tnFRXRecno, 4] = atc('DATA', loObject.User) > 0 endif not .aRecords[tnFRXRecno, 1] * Get the value for the field, then decide what to do with it. lcText = toObjProperties.Text do case * If this is a label, ensure it's in our array. case .aRecords[tnFRXRecno, 3] lnRow = ascan(.aValues, lcText, -1, -1, 1) if lnRow = 0 lnRow = iif(empty(.aValues[1]), 1, ; alen(.aValues, 1) + 1) dimension .aValues[lnRow, 2] .aValues[lnRow, 1] = lcText .aValues[lnRow, 2] = 0 case .aRecords[tnFRXRecno, 4] .aValues[.nCurrentRow, 2] = .aValues[.nCurrentRow, 2] + ; val(lcText) endcase endwith endproc * If we're supposed to draw a column chart, do so. Otherwise do the * normal rendering. procedure Render(tnFRXRecno, tnLeft, tnTop, tnWidth, tnHeight, ; tnObjectContinuationType, tcContentsToBeRendered, ; tiGDIPlusImage) with This if .aRecords[tnFRXRecno, 2] .DrawColumnChart(tnLeft, tnTop, tnWidth, tnHeight) nodefault endif .aRecords[tnFRXRecno, 2] endwith endproc procedure DrawColumnChart(tnLeft, tnTop, tnWidth, tnHeight) local lnMax, lnColumns, lnI, lnColumnWidth, loColumnBrush, ; loPen, loFont, loStringFormat, loPoint, loTextBrush, ; lnColors, lnColor, lnLeft, lnHeight, lnTop with This * Figure out the highest value and the width of each column. lnMax = 0 lnColumns = alen(.aValues, 1) for lnI = 1 to lnColumns lnMax = max(lnMax, .aValues[lnI, 2]) next lnI lnColumnWidth = (tnWidth - (lnColumns * .nSpacing))/lnColumns * Create _GDIPlus objects we'll need for drawing. loColumnBrush = newobject('GPSolidBrush', ; home() + 'ffc\_GDIPlus.vcx') loPen = newobject('GPPen', ; home() + 'ffc\_GDIPlus.vcx') loFont = newobject('GPFont', ; home() + 'ffc\_GDIPlus.vcx') loStringFormat = newobject('GPStringFormat', ; home() + 'ffc\_GDIPlus.vcx') loPoint = newobject('GPPoint', ; home() + 'ffc\_GDIPlus.vcx') loTextBrush = newobject('GPSolidBrush', ; home() + 'ffc\_GDIPlus.vcx') loPen.Create(.CreateColor(0)) && Black loFont.Create(.cLegendFontName, .nLegendFontSize, ; GDIPLUS_FontStyle_Regular, GDIPLUS_Unit_Point) * Draw the border for the column chart. .oGDIGraphics.DrawLine(loPen, tnLeft, tnTop, tnLeft, ; tnTop + tnHeight) endif lnRow = 0 .nCurrentRow = lnRow

www.code-magazine.com

Extending the Visual FoxPro 9.0 Reporting System

51

Listing 2: Continued
.oGDIGraphics.DrawLine(loPen, tnLeft, tnTop + tnHeight, ; tnLeft + tnWidth, tnTop + tnHeight) * Draw the column. lnColors = alen(.aColumnColors) for lnI = 1 to lnColumns lnColor = (lnI - 1) % lnColors + 1 loColumnBrush.Create(.aColumnColors[lnColor]) lnLeft = tnLeft + lnI * .nSpacing + ; (lnI - 1) * lnColumnWidth lnHeight = tnHeight/lnMax * .aValues[lnI, 2] lnTop = tnTop + tnHeight - lnHeight .oGDIGraphics.DrawRectangle(loPen, lnLeft, lnTop, ; lnColumnWidth, lnHeight) .oGDIGraphics.FillRectangle(loColumnBrush, lnLeft, lnTop, ; lnColumnWidth, lnHeight) * Draw the legend for the column. lnLeft = tnLeft + tnWidth + .nLegendSpacing lnTop = tnTop + (lnI - 1) * (.nLegendBoxSize + ; .nLegendBoxSpacing) .oGDIGraphics.DrawRectangle(loPen, lnLeft, lnTop, ; .nLegendBoxSize, .nLegendBoxSize) .oGDIGraphics.FillRectangle(loColumnBrush, lnLeft, lnTop, ; .nLegendBoxSize, .nLegendBoxSize) lnLeft = lnLeft + .nLegendBoxSize + .nLegendTextSpacing loPoint.Create(lnLeft, lnTop) loTextBrush.Create(.CreateColor(0)) && Black .oGDIGraphics.DrawStringA(.aValues[lnI, 1], loFont, ; loPoint, loStringFormat, loTextBrush) next lnI endwith endproc * * * * * GDI+ colors are represented by a number as 0xAARRGGBB, where AA is the alpha, RR is the red, GG is the green, and BB is the blue. Unfortunately, the VFP RGB() function gives us 0xBBGGRR, so we need to add the alpha component and reverse the red and blue component positions.

procedure CreateColor(tnRGB, tnAlpha) local lnAlpha lnAlpha = iif(pcount() = 1, 255, tnAlpha) return bitlshift(lnAlpha, 24) + bitand(tnRGB, 0x00FF00) + ; bitlshift(tnRGB, 16) + bitrshift(tnRGB, 16) endproc enddefine

is responsible for drawing each object on the report page. You can override this method to perform various types of output. A listener that performs custom rendering will almost certainly have to use GDI+ functions. GDI+ is a set of hundreds of Windows API functions that perform graphical manipulations and output.

_ReportListener allows chaining listeners by providing a Successor property that may contain an object reference to another listener.

The code shown in Listing 2, taken from TestColumnChart.PRG, runs the TestColumnChart.FRX report, shown in Figure 2, and creates the output shown in Figure 3. Notice how different the output looks than the report layout suggests; the fields and shape dont appear but a column chart graphing the contents of the Category_Sales_For_1997 view in the sample Northwind database does. Thats partly due to Print When clauses on the fields that prevent them from printing, but the biggest reason is that the listener class used for this report, ColumnChartListener, replaces the shape object in the Summary band with a column chart. Lets see how this listener does that. The Init method of ColumnChartListener initializes the aColumnColors array to the color to use for each column in the chart. Note that GDI+ colors are a little different than the values returned by RGB(), so the CreateColor method is used to do the necessary conversion. If you want to use a different set of colors, you can subclass ColumnChartListener or store a different set of colors in the array after you instantiate ColumnChartListener. Note that only eight colors are defined; if there are more than eight columns in the chart, the listener uses the same colors for more than one bar. The BeforeReport method instantiates a GPGraphics object into the custom oGDIGraphics property. GPGraphics is one of the classes in _GDIPlus.VCX. It and other _GDIPlus classes are used in the DrawColumnChart method to draw the components of the column chart.

To make it easier to work with GDI+ functions, Visual FoxPro includes _GDIPlus.VCX in the FFC directory. _GDIPlus, which was written by Walter Nicholls of Cornerstone Software in New Zealand, consists of wrapper classes for GDI+ functions, making them both easier to use and object-oriented. The GDI Plus API Wrapper Foundation Classes topic in the Visual FoxPro Help file lists these classes and provides a little background about them. This library is a great help in doing GDI+ rendering because you dont have to know much about GDI+ to use them; I certainly dont and yet was able to create the listener class discussed next in just a couple of hours.

52

Extending the Visual FoxPro 9.0 Reporting System

www.code-magazine.com

The key to changing the way a field appears in a report is the EvaluateContents method.
GPGraphics needs a handle to the GDI+ surface being rendered to. Fortunately, the listener already has such a handle in its GDIPlusGraphics property. The only complication is that the handle changes on every page, so the BeforeBand method, which fires just before a band is processed, calls the SetHandle method of the GPGraphics object to give it the handle when the title or page header bands are processed. As the report is processed, the listener needs to determine where the labels and values for the chart come from. It does that in the EvaluateContents method by looking at each field as its about to be rendered. If the USER memo for the fields record in the FRX contains LABEL (as it does for the CategoryName field), that indicates that this field should be used for the labels for the column chart. DATA in the USER memo (as is the case for the CategorySales field) indicates that the field is used as the values for the chart. As with the EffectListener class I discussed earlier, there isnt a need to check the USER memo more than once, so the same type of mechanismstoring a flag in an array property to indicate whether the field was processedis used in this class. If the listener hasnt checked the USER memo for the field being rendered yet, EvaluateContents does that, setting flags in the array indicating whether the field is used for labels or values, and setting the first column of the array to .T. so the FRX record isnt examined again. If the field is used for either labels or fields, EvaluateContents updates the aValues array accordingly. AdjustObjectSize is similar to EvaluateContents except it fires for shapes rather than fields. AdjustObjectSize checks the USER memo of the FRX record of the current shape for the presence of COLUMNCHART, indicating that this shape is to be replaced by a column chart. As with EvaluateContents, the listener only needs to check once, so it uses similar logic. The Render method is responsible for drawing an object on the report. If the object about to be drawn is a shape to be replaced with a column chart, it calls the custom DrawColumnChart method followed by NODEFAULT to prevent the shape from being drawn. Otherwise, the object is drawn normally. (Notice that theres no DEDEFAULT(); the native behavior is to draw the object, so it isnt required). DrawColumnChart figures out the maximum of the values being charted so it knows how big to draw

the columns, and then it creates some objects from _GDIPlus classes needed to perform the drawing. It calls the DrawLine method to draw the vertical and horizontal borders for the chart, and then goes through the aValues array, drawing a column for each value using DrawRectangle and filling it with the appropriate color via FillRectangle. DrawColumnChart adds a box and label to the legend for the chart using the same DrawRectangle and FillRectangle methods for the box and DrawStringA for the label. Some drawing attributes come from values in custom properties, making charting more flexible. For example, cLegendFontName and nLegendFontSize specify the font and size to use for the legend label, and nLegendBoxSize specifies the size of box to draw. See the comments for these properties near the start of the Listing 2.

A listener that performs custom rendering will almost certainly have to use GDI+ functions to do so.
Summary
Microsoft has blown the lid off the Visual FoxPro reporting system! By passing report events to ReportListener objects, theyve allowed us to react to these events to do just about anything we wish, from providing different types of output to dynamically changing the way objects are rendered. I can hardly wait to see the type of things the Visual FoxPro community does with these new features. Doug Hennig

www.code-magazine.com

Extending the Visual FoxPro 9.0 Reporting System

53

QUICK ID 0404062

Visual FoxPro 9.0 IDE Enhancements


Every version of FoxPro has always included a number of enhancements to the IDE, and Visual FoxPro 9.0 is no exception. Visual FoxPro 9.0 includes a number of IDE enhancements that make its already rich development environment considerably richer.

Rod Paddock
rpaddock@dashpoint.com

Fonts and Colors Everywhere

Rod Paddock is the editor of CoDe Magazine. He has been a software developer for more than 12 years and has worked with numerous tools including Visual Studio .NET, SQL Server, Visual Basic, Visual FoxPro, Delphi, and many others. Rod is president of Dash Point Software, Inc. Dash Point is an award-winning software development firm that specializes in developing applications for small to large businesses. Dash Point has delivered applications for several corporations including: Six Flags, First Premier Bank, Intel, Microsoft, and the US Coast Guard. Rod is also VP of Development for SQL Server tools maker, Red Matrix Technologies. (www.redmatrix.com)

Microsoft didnt stop with just the Project Manager, of course. You can also specify the display font for One major enhancement to the UI is the ability to the Properties sheet. In previous versions of Visual configure fonts that are used in many designers and FoxPro, you were limited to changing the font from dialog boxes. Visual FoxPro 9.0 allows you to small to medium or large. You now have full configure the font used to control over the font in the display items in the Project Properties sheet. You can rightFast Facts Manager. To change the font click the Properties sheet and Visual FoxPro 9.0 for your projects, right-click on select Font from the shortcut your open project and select menu to activate the Windows has a number of IDE Changes Font from the shortcut menu. Font dialog box just like you that continue This activates the Windows did with the Project Manager. Visual FoxPros trend of making Font dialog box, from which developers more productive. you can specify the font, style, Another enhancement to the and size you wish to use. Properties sheet is the ability to Figure 1 shows the Visual FoxPro Project Manager specify the colors for different categories of properties. with the Comic Sans font, 14pt, and Bold selected. You can specify colors for ActiveX properties, nondefault values, custom properties, and instance properties. To specify the colors for custom properties: 1. Right click on the Properties sheet. 2. Select Custom Properties Color from the shortcut menu. 3. Select the color from the Windows Color dialog box. Figure 2 shows the Properties sheet with a custom property displayed in red.

Class Upgrades
Visual FoxPro 9.0 added one much-desired feature to its Class designer. You can now specify default values for custom properties added to your classes. To specify a default values for your custom property: Figure 1: The Project Manager with the font set to Comic Sans. 1. Open your custom class from the Project Manager or type MODIFY CLASS in the Command window.

Figure 2: The Properties sheet with the Custom Property color set to red.

Figure 3: The new Property dialog box with the default value set to ADD,EDIT.

54

Visual FoxPro 9.0 IDE Enhancements

www.code-magazine.com

Figure 4: This Properties sheet shows the default value for the ceditmode property. Figure 7: The Run Query dialog box shows the results of a query. Figure 5: Heres the More Panes dropdown list from the Task Pane Manager dialog box. feature called the Data Explorer. The Data Explorer is a new tool that is included in the Visual FoxPro Task Pane Manager. To activate the Data Explorer, open the Task Pane Manager from the Visual FoxPro Tools menu and select Data Explorer from the Task Pane Managers More Panes dropdown list (Figure 5). This activates the Data Explorer in the Task Pane Manager window. You can now add connections to external databases or use Explorer for databases on your local computer. Figure 6 shows the Data Explorer perusing the Master database. From the Data Explorer, you can query tables by right-clicking on a table and selecting Run Query from the shortcut menu. Selecting Run Query opens the Run Query dialog box (Figure 7), which allows you to execute SQL code in your SQL Server database. The Data Explorer tool is something that has been sorely missing from Visual FoxPro for some time. Thanks to Microsoft for adding this very necessary feature.

Mike Stewart
Test Engineer, Visual FoxPro Team Microsoft The enhanced BINDEVENTS() functionality brings new ways for Visual FoxPro developers to interact with the Windows operating system. Prior to Visual FoxPro 9.0, when events such as a system-level color or font changes occurred, it was difficult or impossible for a Visual FoxPro developer to execute code in response. In Visual FoxPro 9.0, developers can execute their own code when users insert a USB drive, connect to a network drive, power down their computer, or nearly any other event notification. In addition, Visual FoxPro developers can now hook into Visual FoxPro's IDE windows more easily than before. This brings with it possibilities for custom context menus and IDE window hooks with just a small amount of native Visual FoxPro code.

Figure 6: Data Explorer shows the contents of the Master database. 2. Select Class then select New Property from the Visual FoxPro menu. 3. Specify the name of your property. 4. In the Default/Initial Value field, enter the new default value (as shown in Figure 3). 5. Click Add. You should now see the new default value for your property in the Properties sheet (see Figure 4).

In The Background
Visual FoxPro 9.0 also includes a major enhancement to the Program Editor. Visual FoxPro 9.0 has background compilation of code. When FoxPro comes across a syntax error, it underlines it. This gives you the ability to fix code immediately instead of waiting for a compile to run.

Data Exploration
As you know, Visual FoxPro works very well with data. Visual FoxPro 9.0 is especially well-suited to working in client/server development environments. Visual FoxPro 9.0 includes tools to help facilitate development in a client/server world with a new

Visual FoxPro 9.0 IDE = More Productivity


Even though this list may be small, the IDE changes in Visual FoxPro 9.0 pack a real punch. Background compilation prevents bugs at the source, font changes make it easier to see what you You can specify colors are looking for, and the Data Explorer makes dealing with for ActiveX properties, client/server data that much non-default values, simpler. Rod Paddock

Visual FoxPro 9.0 facilitates development in a client/server world with a new feature called the Data Explorer.

custom properties, and instance properties.

www.code-magazine.com

Visual FoxPro 9.0 IDE Enhancements

55

QUICK ID 0404072

Interop: Making .NET and VFP Talk to Each Other


Many companies have relied on COM components in the last couple of years. That includes Microsoft. Using COM components made it possible for different programming languages to reuse logic between them, by agreeing to a standard defined by the COM specification.

Claudio Lassala
claudio@eps-software.com

Cladio Lassala is a Senior Developer at EPS Software Corp. and a part time Universal Thread Consultant. He has presented several lectures at Microsoft events as well as several conferences and user groups in North America and Brazil. He is a Microsoft C# MVP, MCSD for .NET, and also a columnist at MSDN Brazil. He is the author of several training videos that can be found at the Universal Thread, and he has several articles published in various magazines including MSDN Brazil Magazine and FoxPro Advisor. A full-bio can be found at www.lassala.net/bio.

Calling VFP Components any developers wrote VFP applications using COM components, usually for data from .NET access logic and business logic. As a VFP In order for .NET to see COM components, you developer youll be relieved to know that you can must create a proxy (or wrapper). This proxy, reuse those components in .NET, allowing you to called Runtime Called easily create a .NET User Wrapper (or just RCW), is an Interface (a Web application, Fast Facts object that sits between COM for instance) that uses those .NET is growing and .NET, and translates calls VFP components, instead of from .NET to COM. To the throwing them away and more and more important, .NET client, the proxy looks rewriting everything from making interoperability between like any other .NET composcratch. On the other hand, .NET and VFP a hot topic. nent, and the proxy takes care the .NET Framework comes There are a few of interpreting the calls to with many classes that VFP different approaches to COM. Creating the RCW is developer might want to use combining the two tools, not a daunting task, as you will in their applications, and that such as COM Interop, see in a minute. is also possible.
Web services,
You first create a COM Whether you use a COM and interoperability on component in VFP. The component from .NET, or a the database level. following code creates a sort .NET component from a COMof business object class that enabled environment (such as .NET will use. (We say sort of business object VFP), the mechanism that allows for that is called because the data access code is there too, but COM Interop. separating layers is not the point were trying to make here):

Why COM Interop?

COM-enabled languages can use COM components created in any language because those components conform to the standards defined by COM. Most languages have different types, or treat common types in a different way, and therefore, in order to make components created in different languages talk to each other, they have to be compatible, and it is COM that determines the common rules. .NET goes a step further trying to address issues with the COM standards (such as DLL hell), and it uses different approaches that lead to a very different set of standards. COM components and .NET components are not compatible by default. However, keeping in mind that many companies have put a lot of work into COM components, Microsoft added a mechanism to .NET so that .NET components can see COM components as if they were .NET components, and COM components can see .NET components as if they were COM components.

Define Class CustomerBizObj As Session OlePublic DataSession = 2 && Private Session Procedure Init Use Home(2) + "\Northwind\Customers.dbf" EndProc Procedure GetCustomerList() As String Local lcOut As String lcOut = "" Cursortoxml("Customers","lcOut",1,0,0,"1") Return lcOut EndProc EndDefine

The GetCustomerList method retrieves a list of customers, returning the results in an XML string. Note that you must declare the return type, otherwise VFP will define the return type to be of type

56

COM Interop: Making .NET and VFP Talk to Each Other

www.code-magazine.com

Figure 1: Adding a reference to a .NET project.

variant in the type library (a file that defines the methods and other things that are in a COM component). A variant is really bad because .NET doesnt support a variant type. On the .NET side, the developer must know in advance what data type is actually getting returned in order to be able to use it. You declare the class using the OlePublic keyword, marking it to be exposed as a COM component. For this demo we created a project called COMInteropSample and added the CustomerBizObj.prg to the project. We need to build the project as a Multithreaded COM server (.dll). You can use the following code to test the COM component in VFP:
*-- Instantiate the object. oCustomerBizObj=; CreateObject("COMInteropSample.CustomerBizObj") *-- Call the method a save XML returned to file. StrToFile(oCustomerBizObj.GetCustomerList(),; "c:\CustomerList.xml") *-- Release the object. Release oCustomerBizObj *-- Show XML. Modify File c:\CustomerList.xml

Markus Egger
Figure 2: A .NET Web application that uses VFP components and data. megger@eps-software.com

The CustomerBizObj class created in VFP will be contained within a namespace called cominteropsample, so we added the line using cominteropsample; at the top of the code-behind the Web form. Inside that namespace youll find the class named CustomerBizObjClass. This Web form displays the list of Customers returned by the GetCustomerList method on the business object. The following code snippet shows the Page_Load method on the Web form, which runs every time the page loads:
private void Page_Load(object sender, System.EventArgs e) { CustomerBizObjClass customer = new CustomerBizObjClass(); DataSet dsCustomers = new DataSet(); dsCustomers.ReadXml( new StringReader( customer.GetCustomerList())); this.dgCustomerList.DataSource = dsCustomers; this.dgCustomerList.DataBind(); }

Markus is an international speaker who presents sessions at numerous conferences including a number of conferences in North America and Europe. Markus has written numerous articles for publications including CoDe Magazine, Visual Studio Magazine, asp.netPro Magazine, FoxPro Advisor, Fuchs, FoxTalk and Microsoft Office & Database Journal. Markus is the publisher of CoDe Magazine. Markus is also the President and Chief Software Architect of EPS Software Corp., a custom software development and consulting firm located in Houston, Texas. He specializes in consulting for objectoriented development, Internet development, B2B and Web services. EPS does most of its development using Microsoft Visual Studio (.NET). EPS has worked on numerous software projects for Fortune 500 companies including Philip Morris, Qualcomm, Shell, and Microsoft. Markus has also worked as a contractor for the Microsoft Visual Studio team where he is mostly responsible for object modeling and other object and component related technologies. Markus also received the Microsoft MVP Award (96, 97, 98, 99, 2000, 2001, 2002, 2003, and 2004) for his contributions to the developer community. Visual LandPro 98, one of the applications Markus was in charge of, was nominated for the Microsoft Excellence Awards three times.

Next you can create any sort of .NET application. For this example weve created an ASP.NET Web application, and we chose to use C#, but the language really doesnt matter. After we created the project, we added a reference to the COM component in the .NET project. You can do this by going to the Add Reference option on the Project menu, or by right-clicking the References node on the project through the Solution Explorer window (Figure 1 shows that). From the dialog box, click the Browse button, and navigate to the COMInteropSample.dll that was created when the VFP project was compiled. Next we created a CustomerList.aspx Web Form, and added a DataGrid control (named dgCustomers) to it.

As you can see, the code just instantiates the CustomerBizObjClass as well as a DataSet. The DataSet is then filled with data based on the XML returned from GetCustomerList. The DataSets ReadXml() method takes care of the transformation from XML to ADO.NET data. Finally, the DataSet is bound to the DataGrid. Other than the specifics of using DataSets and StringReaders, using the VFP component is just a matter of instantiating objects and calling methods, as the VFP developer is very used to doing in VFP. Figure 2 shows the results of running that page. Remember what seemed to be a daunting task of creating the RCW (that proxy that intermediates .NET calls to COM components)? Thats been

www.code-magazine.com

COM Interop: Making .NET and VFP Talk to Each Other

57

"C:\Program Files\Microsoft Visual Studio .NET 2003\SDK\v1.1\Bin\. You can run the tool at a DOS prompt like this: (We broke the lines for better readability, but this is typed in one line.)
tlbimp.exe "C:\YourVFPProject\BizObjects.dll" /out: "C:\YourDotNetProject\bin\Proxy.BizObjects.dll" /namespace:BizObjects

Rod Paddock
rpaddock@dashpoint.com

Figure 3: A .NET project set up for COM Interop.

Rod Paddock is the editor of CoDe Magazine. He has been a software developer for more than 12 years and has worked with numerous tools including Visual Studio .NET, SQL Server, Visual Basic, Visual FoxPro, Delphi, and many others. Rod is president of Dash Point Software, Inc. Dash Point is an award-winning software development firm that specializes in developing applications for small to large businesses. Dash Point has delivered applications for several corporations including: Six Flags, First Premier Bank, Intel, Microsoft, and the US Coast Guard. Rod is also VP of Development for SQL Server tools maker, Red Matrix Technologies. (www.redmatrix.com)

created automatically by the Visual Studio .NET IDE as soon as a reference to the COM component was added to the .NET project. If you select the cominteropsample reference on the Solution Explorer window and look at its Path property, you should see something like the following:
C:\YourProject\obj\Interop.cominteropsample.dll

Notice that you specify the COM DLL, and then use the switch out in order to specify where you want to locate the proxy and what you want to name it. Use the namespace switch to specify the name of the namespace the proxy class will be contained on. At this point, you can remove the reference created previously in the .NET project for the COM component. You can add a new reference pointing to the Proxy.BizObjects.dll you just created. (The RCW is already a .NET class so Visual Studio .NET wont try to create another proxy). You can rewrite the using statement at the top of the Web Form as using BizObjects.

YourProject should be whatever path you have to the .NET project youve created. The important detail to notice here is that the path doesnt point directly to the cominteropsample.dll (created by Visual FoxPro). Instead, it points to an Interop.cominteropsample.dll. This DLL is the RCW created by .NET. This proxy will help .NET to communicate with the COM component. It has a class with the same name as the one exposed by the COM component, but with the class word added to it (thus, the CustomerBizObjClass thats instantiated in the .NET sample). In other words, whenever your application instantiates that class in .NET, the proxy will know how to instantiate the COM component, and whenever a method is called in that class, the proxy will know how to translate the .NET call into a COM call.

Calling .NET Components from Visual FoxPro


There are many .NET classes that can be useful for the Visual FoxPro developer, and you can use those classes through COM interop. For instance, you might want to use the classes that provide GDI+ features. Listing 1 shows a class created in .NET that wraps up some GDI+ functionality. We compiled the class into a class library project in .NET. The most important thing to note here is that the project has been marked as Register for COM Interop. To do that, right-click on the project, select Properties, select Configuration Properties Build, and then set the Register for COM Interop option to True to expose a .NET class as a COM component (Figure 3). After you compile the project you can immediately use the class through COM from VFP. However, in order to provide a better experience for the user of such class, you might want to apply some attributes to the class. For instance, the class showed in Listing 1 has the following attributes applied:
[ClassInterface(ClassInterfaceType.AutoDual)] [ProgId("VFPAndDotNet.ImageHelper")]

The Type Library Importer Tool


When a reference to a COM component is added to a .NET project by using the Visual Studio .NET IDE, Visual Studio uses a tool called the Type Library Importer, accepting default values for it. Some of those defaults determine that the proxy will be named after the COM DLL, but preceded by the word interop (such as in Interop.cominteropsample.dll), and the proxy class will be placed inside a namespace also named after the .dll (such as cominteropsample). Many developers want to have more control over the process of creating the RCW. This means they want to have more control over the namespace where the proxy is going to be placed, and where the proxy DLL is going to be created. Developers can use the Type Library Importer tool (Tlbimp.exe) for that. This command-line tool that comes with the .NET SDK is in the folder

The ClassInterface attribute, set to ClassInterfaceType.AutoDual, enables the IntelliSense support in VFP. The ProgId attribute specifies the ProgId that VFP uses when instantiating the .NET component as a COM component. For example, you can use the ImageHelper class in VFP like so:

58

COM Interop: Making .NET and VFP Talk to Each Other

www.code-magazine.com

*-- Instantiate the .NET class as a COM component. oHelper = CreateObject("VFPAndDotNet.ImageHelper") *-- Set some properties. oHelper.Copyright = "Claudio Lassala" oHelper.ImageFile = "C:\MyImage.BMP" oHelper.SaveFileAs = "C:\CopyOfMyImage.jpg" *-- Call a method on the class. oHelper.ProcessImage()

From the VFP side, there is no indication that the object being used is a .NET object.

Calling .NET Web Services from VFP


The one .NET feature that was (and still is) mentioned more than any others is the ability to use .NET to create XML Web services. Web services are methods of functions that are exposed to the Web through a standardized protocol called SOAP. SOAP enables you access to components in a plat-

Listing 1: An ImageHelper class that wraps up GDI+ funcionalities


using using using using using using System; System.Drawing; System.Drawing.Imaging; System.Drawing.Drawing2D; System.Drawing.Text; System.Runtime.InteropServices; bmpGraphics.InterpolationMode = InterpolationMode.High; bmpGraphics.TextRenderingHint = TextRenderingHint.AntiAlias; bmpGraphics.SmoothingMode = SmoothingMode.HighQuality; // Creates the rectangle where we'll draw the image. Rectangle compressionRectangle = new Rectangle(0, 0, SourceBitmap.Width, SourceBitmap.Height); // Draw the original image on the graphic object. bmpGraphics.DrawImage(SourceBitmap, compressionRectangle); // Add logo. Image imgPhoto = Image.FromFile(@"c:\MyCompanylogo.jpg"); Bitmap bmPhoto = new Bitmap(imgPhoto.Width, imgPhoto.Height, PixelFormat.Format24bppRgb); // Draw logo on source image. Graphics grPhoto = Graphics.FromImage(bmPhoto); bmpGraphics.DrawImage(imgPhoto, TargetBitmap.Width - imgPhoto.Width, 1); // Add copyright info. bmpGraphics.DrawString(@"Copyright 2004 - " + this.Copyright, new Font("Arial Black",12), new SolidBrush( Color.FromArgb(153, 255, 255, 255)),3, SourceBitmap.Height - 24); // Save image to disk. TargetBitmap.Save(this.SaveFileAs, ImageFormat.Jpeg); // Create thumbnail. thumbnail = TargetBitmap.GetThumbnailImage( 72,98,null,IntPtr.Zero); ImageFormat format = TargetBitmap.RawFormat; thumbnail.Save(this.SaveFileAs + "_thumbnail.jpg", format); } catch { throw; } finally { // Dispose objects. if (SourceBitmap != null) SourceBitmap.Dispose(); if (TargetBitmap != null) TargetBitmap.Dispose(); if (bmpGraphics != null) bmpGraphics.Dispose(); if (thumbnail != null) thumbnail.Dispose(); } } } // set Drawing Quality for the image. }

namespace MiscClasses { /// <summary> /// The ImageHelper class wraps up some GDI+ functionalities. /// </summary> [ClassInterface(ClassInterfaceType.AutoDual)] [ProgId("VFPAndDotNet.ImageHelper")] public class ImageHelper { public ImageHelper() {} private string strImageFile; public string ImageFile { set { this.strImageFile = value; } get { return this.strImageFile; } } private string strCopyright; public string Copyright { set { this.strCopyright = value; } get { return this.strCopyright; } } private string strSaveFileAs; public string SaveFileAs { set { this.strSaveFileAs = value; } get { return this.strSaveFileAs; } } public void ProcessImage() { // Declare some variables. Bitmap SourceBitmap = null; Bitmap TargetBitmap = null; Graphics bmpGraphics = null; Image thumbnail = null; try { // Create a bitmap representation of the source image. SourceBitmap = new Bitmap(this.ImageFile); // Create an empty bitmap for the target image. TargetBitmap = new Bitmap(SourceBitmap.Width, SourceBitmap.Height); // Create a graphic object based on our target bitmap size. bmpGraphics = Graphics.FromImage(TargetBitmap);

www.code-magazine.com

COM Interop: Making .NET and VFP Talk to Each Other

59

Figure 4: Creating a new Web service project in Visual Studio .NET.

form and language-neutral fashion. This means that any language and operating system can call any Web service no matter how the Web service was created. This, of course, means that Visual FoxPro can call Web services created in Visual Studio .NET. Well show you how to create a .NET Web service before we call one. You can easily do this using the Visual Studio .NET IDE. (Note: If you do not have Visual Studio .NET installed, you can probably follow the example by calling an existing Web service such as one of the many Web services found listed on www.UDDI.org). If youre following along, you have Visual Studio .NET loaded. First create a new ASP.NET Web service project. The language you choose to use does not matter. This example will use Visual Basic .NET but if you are more familiar with C#, you should have no difficulty following the examples. Figure 4 shows the New Project dialog box. When you create a new ASP.NET Web service project, the Visual Studio .NET IDE automatically includes all the required references and creates a Web service source code file (Service1.asmx), with a hello world method as a template. For our purposes, well delete that method and instead change the code to what you see in Listing 2. You may have noticed that most of the code in Listing 2 is inside a designer region, which means that developers should never have to touch it. The important part of Listing 2 is the following method:
<WebMethod()> _ Public Function GetCurrentTime() _ As DateTime Return DateTime.Now End Function

Figure 5: Testing our simple Web service using the provided test bench application through Internet Explorer.

You can start your Web service project (simply press F5) to see a test bench interface in Internet Explorer. In this example, the service is rather simple since it only has one exposed method. Click the link to that method and then click the Invoke button to run the service. (Note: If your method accepted parameters, this interface provides textboxes to enter those parameters.) You can see the result in Figure 5. The return value of the method is wrapped in XML, which is the key mechanism that allows you to call this service from Visual FoxPro. You can register a Web service in VFP through the Task Pane Manager under its special Web Services tab. Click the first link provided in this window, Register an XML Web Service. In the Web Service Registration dialog box (Figure 6) youll specify a URL that describes the Web service and tells VFP what methods as well as parameters and return values are supported by the service. ASP.NET-based Web services provide a WSDL (Web Service Description Language) URL that provides exactly that information. You can find the

Recommended Links to Interoperation Between VFP and .NET:


A lot of information related to this topic can be found on www.VFPConversion.com. The following is a list of detailed reading recommendations: .NET Interop for Visual FoxPro Applications:
http://west-wind.com/presentations/VfpDotNetInterop/vfpDotNetInterop.htm

Using Visual FoxPro to call .NET Web services:


http://www.eps-cs.com/VFPConversion/Article.aspx?quickid=03 0074

VFP COM interoperability with Visual Basic .NET:


http://www.epscs.com/pdf/whitepaper_vfpcominterop.pdf

This method simply returns the current date and time as a DateTime data type. The only unusual aspect about this is the <WebMethod()> attribute. This attribute tells the ASP.NET runtime that this method is to be exposed through a Web service according to the SOAP standard.

COM Interop in Visual Studio .NET:


Figure 6: Adding a Web service reference using the Visual FoxPro 9.0 Task Pane. http://www.eps-cs.com/VFPConversion/Article.aspx?quickid=02 03031

www.code-magazine.com

COM Interop: Making .NET and VFP Talk to Each Other

61

Note that in a real-life scenario, you need to replace localhost with the name of the domain the Web service resides on (such as www.codefocus.com). After you register a Web service with the VFP Task Pane, you can test it immediately through the Task Pane Manager. Simply pick the service you would like to test (Service 1 in our example) and the method you would like to call, and click on the icon next to the method drop-down list. You can see the result in Figure 7. You now know that the Web service works in VFP and you can start using it from within a VFP program. Doing this requires a little bit of code. The good news is that the Task Pane also provides sample code (the bottom left of Figure 7 shows the start of the sample code) that you can use directly by dragging and dropping that code into a source code window. Listing 3 shows code created based on the sample code provided by the Task Pane. Note that Listing 3 contains a lot of code that is not strictly required including error handling. The important code is in this next code snippet. Figure 7: Our sample Web service is registered in the VFP Task Pane Manager and ready to be tested.

URL by launching the test bench in Visual Studio .NET (click F5), to launch the service test bench start page. At the very top of the page there is a link to the Service Description of the Web service. In our example, the URL is similar to the following:
http://localhost/ExampleService/ Service1.asmx?WSDL

Many companies have relied on COM components in the last couple of years. That includes Microsoft.

Listing 2: A first Web service created by ASP.NET and the Visual Studio .NET IDE
Imports System.Web.Services <System.Web.Services.WebService(Namespace := _ "http://tempuri.org/ExampleService/Service1")> _ Public Class Service1 Inherits System.Web.Services.WebService #Region " Web Services Designer Generated Code " Public Sub New() MyBase.New() 'This call is required by the Designer. InitializeComponent() 'Add your own initialization code after 'the InitializeComponent() call End Sub 'Required by the Web Services Designer Private components As _ System.ComponentModel.IContainer 'NOTE: The following procedure is required by the 'Web Services Designer 'It can be modified using the Designer. 'Do not modify it using the code editor. <System.Diagnostics.DebuggerStepThrough()> _ Private Sub InitializeComponent() components = New _ System.ComponentModel.Container() End Sub Protected Overloads Overrides Sub Dispose( _ ByVal disposing As Boolean) 'CODEGEN: This procedure is required by 'the Web Services Designer 'Do not modify it using the code editor. If disposing Then If Not (components Is Nothing) Then components.Dispose() End If End If MyBase.Dispose(disposing) End Sub #End Region <WebMethod()> _ Public Function GetCurrentTime() As DateTime Return DateTime.Now End Function End Class

64

COM Interop: Making .NET and VFP Talk to Each Other

www.code-magazine.com

loWSHandler = NEWOBJECT("WSHandler",; IIF(VERSION(2)=0,"",; HOME()+"FFC\")+"_ws3client.vcx") loSvc = loWSHandler.SetupClient(; "http://.../Svc.asmx?WSDL",; "Service1", "Service1Soap") MessageBox(loSvc.GetCurrentTime( ))

that you must parse before VFP can use it. In the case of a DataSet, VFP has an XMLAdapter class. (Note: For more information about the XMLAdapter class, see the Whats New with Data in Visual FoxPro 9.0 article in this issue, or search for XMLAdapter on www.code-magazine.com.) For other complex objects, parsing the XML may be a little more complex, but using tools like XMLDOM, it is never overly hard.

Exposing VFP Objects as Web Services


Visual FoxPro does not support a native way to expose VFP objects as Web services, but there are several other Microsoft tools and technologies that you can use to accomplish this goal. In previous versions of VFP, Microsoft recommended the SOAP Toolkit (and in fact provided tools to automatically publish VFP Web services using this toolkit). This approach is now not recommended anymore, mainly because the SOAP Toolkit uses either ASP or ISAPI listeners to react to incoming requests. Neither technology is recommended at this point, and is only supported by Microsoft based on the standard Microsoft support policy. The better way to go at this point is to expose VFP objects through modern ASP.NET Web services. The overall idea for this approach is simple: First, create a VFP object and expose it as a COM object. You can access this COM object from ASP.NET using a simple wrapper service to expose individual methods. For instance, consider the following VFP object:
DEFINE CLASS TestClass AS Custom OLEPublic FUNCTION GetName() AS String RETURN "John Smith" ENDFUNC ENDDEFINE

Note: We shortened the URL to make it more readable. Please replace the URL with the URL of the service you created. The sample code instantiates the WSHandler object, which is VFPs connector to a Web service. This object is then configured with the WSDL URL. Subsequently, we call the GetCurrentTime() method, which returns a .NET DateTime variable. VFP automatically assigns the return value to a VFP DataTime variable even though the two formats internally differ slightly. Since the value is returned as a DateTime you can perform additional operations on it. For instance, you can retrieve the time portion using the following commands:
LOCAL ldCurrentDateTime ldCurrentDateTime = ; loService1.GetCurrentTime() ? TToC(ldCurrentDateTime,2)

Note that automatic type assignment does not happen all the time. It is possible, some would even say likely, that the Web service will return a data type that is not natively supported in Visual FoxPro. This typically happens when the return value is a complex object, such as an ADO.NET DataSet. In that case the return value would be complex XML,

Listing 3: Calling the ASP.NET Web service from within a VFP program
LOCAL loService1 AS "XML Web Service" * LOCAL loService1 AS "MSSOAP.SoapClient30" * Do not remove or alter following line. It is used to * support IntelliSense for your XML Web service. *__VFPWSDef__: loService1 = http://megger05/ExampleService/Service1.asmx?WSDL , Service1 , Service1Soap LOCAL loException, lcErrorMsg, loWSHandler TRY loWSHandler = NEWOBJECT("WSHandler",; IIF(VERSION(2)=0,"",HOME()+"FFC\")+; "_ws3client.vcx") loService1 = loWSHandler.SetupClient(; "http://megger05/Svc/Service1.asmx?WSDL", ; "Service1", "Service1Soap") LOCAL ldCurrentTime as Datetime ldCurrentTime = loService1.GetCurrentTime() MESSAGEBOX(Transform(ldCurrentTime)) CATCH TO loException lcErrorMsg="Error: "+; TRANSFORM(loException.Errorno)+; " - "+loException.Message DO CASE CASE VARTYPE(loService1)#"O" * Handle SOAP error connecting * to web service CASE !EMPTY(loService1.FaultCode) * Handle SOAP error calling method lcErrorMsg=lcErrorMsg+CHR(13)+; loService1.Detail OTHERWISE * Handle other error ENDCASE * Use for debugging purposes MESSAGEBOX(lcErrorMsg) FINALLY ENDTRY

www.code-magazine.com

COM Interop: Making .NET and VFP Talk to Each Other

65

Listing 4: Code to query Visual FoxPro data from ASP.NET


Public Sub GetFoxProData() Dim cConnString As String cConnString = "Provider=vfpoledb.1;" cConnString += Data Source=\\JAWATEST\NorthWind\NorthWind.dbc" cConnString += ";Exclusive=false;Nulls=false" '-- connect to database Dim oConn As New OleDbConnection oConn.ConnectionString = cConnString oConn.Open() '-- create a command object to query Dim oCMD As New OleDbCommand oCMD.CommandText = "select * from customers" oCMD.Connection = oConn '-- create the adapter class for filling '-- our dataset Dim oAdapter As New OleDbDataAdapter oAdapter.SelectCommand = oCMD '-- go get the data Dim oDS As New DataSet oAdapter.Fill(oDS, "customers") '-- clean up oConn.Close() '-- view the data Me.DataGrid1.DataSource = oDS.Tables("customers") Me.DataGrid1.DataBind() End Sub

Here is the wrapper class used to expose this object through ASP.NET as a Web service:
Imports System.Web.Services < WebService(Namespace := _ "http://MySvc.org/Service1")> _ Public Class TestService Inherits WebService #Region " Designer Generated Code " <WebMethod()> _ Public Function GetName() _ As String Dim oVFPObject As New _ TestProject.TestClass() Return oVFPObject.GetName() End Function End Class

Further details of accessing VFP data through OLE DB is beyond the scope of this article. The core concept however is relatively simple and pretty similar to accessing SQL Server data.

Conclusion
COM Interop makes it easier for the developer to use VFP components in a .NET application, preventing the developer from rewriting portions of logic such as data access and business rules when time constraints and budget dont allow for that. The same mechanism also enables the developer to use .NET classes from VFP, adding even more power to existing VFP applications.

For more details on how to use VFP COM objects in .NET, please refer to the earlier section on COM Interop.

XML Web services have been promoted more than any other feature in .NET, and they are indeed very useful.

Visual FoxPro and OLE DB


Another way to interact with Visual FoxPro data from .NET is via the Visual FoxPro OLE DB provider. Listing 4 demonstrates querying data from the sample NorthWind.DBC file and displaying it on an ASP.NET page. You can simply call this method from an event in an ASP.NET Web Form (such as the Load event). The code first opens an OLE DB connection to a VFP database container. Next the code executes a SELECT statement and fills the results into an ADO.NET DataSet using a DataAdapter. You can then use this DataSet like any other ADO.NET DataSet. In this example, we use it as the bound data source for a DataGrid.

Web services are a more open process and allow your VFP application to work with environments that do not support COM or .NET. Web services work over the Internet, hence automatically adding remote invocation as a free benefit. Interop on the database level is also a viable option. This works both ways: .NET can access VFP data through OLE DB. VFP, on the other hand, can access many of the data sources .NET uses, such as SQL Server. (We skipped this topic because SQL Server data access with VFP has been discussed many times). Claudio Lassala, Markus Egger, and Rod Paddock

66

COM Interop: Making .NET and VFP Talk to Each Other

www.code-magazine.com

Вам также может понравиться