<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="http://helpcentral.componentone.com/CS/utility/FeedStylesheets/atom.xsl" media="screen"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><title type="html">Win Dev Blog</title><subtitle type="html">Putting the &amp;quot;Win&amp;quot; in WinForms</subtitle><id>http://helpcentral.componentone.com/CS/blogs/windev/atom.aspx</id><link rel="alternate" type="text/html" href="http://helpcentral.componentone.com/CS/blogs/windev/default.aspx" /><link rel="self" type="application/atom+xml" href="http://helpcentral.componentone.com/CS/blogs/windev/atom.aspx" /><generator uri="http://communityserver.org" version="4.1.40407.4157">Community Server</generator><updated>2009-09-29T18:48:00Z</updated><entry><title>Become an Expert Part 5: Generating Reports</title><link rel="alternate" type="text/html" href="/CS/blogs/windev/archive/2010/03/08/BecomeAnExpertPart5.aspx" /><id>/CS/blogs/windev/archive/2010/03/08/BecomeAnExpertPart5.aspx</id><published>2010-03-08T15:02:00Z</published><updated>2010-03-08T15:02:00Z</updated><content type="html">&lt;h2&gt;Part 5 Overview&lt;/h2&gt;
&lt;p&gt;This is the fifth part in the six-part &amp;quot;Become an Expert&amp;quot; series. In this part we will add a report generation feature to the Sales Management Application. This feature will be implemented simply by using the &lt;strong&gt;C1Report&lt;/strong&gt; control, which is part of &lt;a href="http://www.componentone.com/SuperProducts/StudioWinForms/"&gt;ComponentOne Studio for WinForms&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In the previous posts we used the &lt;strong&gt;C1FlexGrid&lt;/strong&gt; and &lt;strong&gt;C1Chart&lt;/strong&gt; controls to visualize our data. Now, we will add a reporting feature which is essential to this type of application. We will use the print feature of each control and the &lt;strong&gt;C1PdfDocument&lt;/strong&gt; component as well. For the reporting we will utilize various features of the rich &lt;strong&gt;C1Report&lt;/strong&gt; component, and we&amp;#39;ll show how to print preview and actually print the report using the &lt;strong&gt;C1PrintPreviewControl&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;Final Result&lt;/h2&gt;
&lt;p&gt;Here is a preview of the final result. When we click the &amp;quot;Report&amp;quot; button on the main form, we will open a print preview window.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/4375.application3.png"&gt;&lt;img src="http://helpcentral.componentone.com/CS/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/4375.application3.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;From the print preview screen you can display file output or directly print the document.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/1663.reportPreview.png"&gt;&lt;img src="http://helpcentral.componentone.com/CS/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/1663.reportPreview.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;So, let&amp;#39;s add this reporting feature to our application.&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;Design a report with C1Report control&lt;/h2&gt;
&lt;p&gt;The first step is to generate a report definition file (xml) that will define the layout and structure of our report. We will use the &lt;strong&gt;C1ReportDesigner&lt;/strong&gt; application to create this report definition through a WYSIWYG design surface. The &lt;strong&gt;C1ReportDesigner&lt;/strong&gt; application installs alongside Studio for WinForms.&lt;/p&gt;
&lt;p&gt;Launch the &lt;strong&gt;C1ReportDesigner&lt;/strong&gt; from the Start menu, under ComponentOne and Reports for .NET.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/8507.reportdesigner1.png"&gt;&lt;img src="http://helpcentral.componentone.com/CS/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/8507.reportdesigner1.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Select &amp;quot;File&amp;quot;&amp;nbsp;--&amp;gt; &amp;quot;Add New Report&amp;quot; to create a new report.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/2627.reportwizard_5F00_datasource.png"&gt;&lt;img src="http://helpcentral.componentone.com/CS/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/2627.reportwizard_5F00_datasource.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In the first page of the &lt;strong&gt;C1Report&lt;/strong&gt; wizard will connect to the database. Click on the &amp;quot;Build Connection String&amp;quot; button on the top right to display the &amp;quot;Data Link Properties&amp;quot; dialog.&lt;/p&gt;
&lt;p&gt;Enter the server name, user name, password and select the database to create the connection string.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/2543.reportwizard_5F00_connection.png"&gt;&lt;img src="http://helpcentral.componentone.com/CS/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/2543.reportwizard_5F00_connection.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Next, write a SQL query to get the data to be displayed in the report.&lt;/p&gt;
&lt;p&gt;We will be getting data from the Sales and Category data tables. The two tables are related through the CategoryCode field. We will get &amp;quot;Date&amp;quot;, &amp;quot;Proceeds&amp;quot;, &amp;quot;Payments&amp;quot;, &amp;quot;GrossMargin&amp;quot;, and &amp;quot;GrossMarginRate&amp;quot; from the Sales table and &amp;quot;Category Name&amp;quot; from the Category table. &lt;/p&gt;
&lt;p&gt;Please refer to &lt;a href="http://helpcentral.componentone.com/CS/blogs/windev/archive/2010/01/29/BecomeAnExpertPart1.aspx"&gt;Part 1&lt;/a&gt; and &lt;a href="http://helpcentral.componentone.com/CS/blogs/windev/archive/2010/02/05/BecomeAnExpertPart2.aspx"&gt;Part 2&lt;/a&gt; of this series for details on the layout of Sales and Category tables. The SQL create statements can be found in the attached samples.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/5751.reportwizard_5F00_sql.png"&gt;&lt;img src="http://helpcentral.componentone.com/CS/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/5751.reportwizard_5F00_sql.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;We could also use the SQL builder to create our SQL query. The SQL builder dialog can be accessed by clicking the &amp;quot;Build SQL statement&amp;quot; button on the top right.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/1050.sqlbuilder.png"&gt;&lt;img src="http://helpcentral.componentone.com/CS/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/1050.sqlbuilder.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;You must be careful about predefined keywords in the &lt;strong&gt;C1Report&lt;/strong&gt; control. In our case the word &amp;quot;Date&amp;quot; is a keyword so we use the AS operator to change the name of the column to &amp;quot;SalesDate.&amp;quot;　&lt;/p&gt;
&lt;p&gt;Once the SQL statement has been created, click on the &amp;quot;Next&amp;quot; button. We will group our data based on &amp;quot;CategoryName&amp;quot;. Move &amp;quot;CategoryName&amp;quot; to the &amp;quot;Groups&amp;quot; box and all other fields to the &amp;quot;Detail&amp;quot; box on the &lt;strong&gt;C1Report&lt;/strong&gt; Wizard dialog.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/1541.report_5F00_fields.png"&gt;&lt;img src="http://helpcentral.componentone.com/CS/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/1541.report_5F00_fields.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Click &amp;quot;Next&amp;quot; to select the layout. We&amp;#39;ll select Outline, Portrait and Adjust Fields to fit page options.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/2727.reportwizard_5F00_layout.png"&gt;&lt;img src="http://helpcentral.componentone.com/CS/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/2727.reportwizard_5F00_layout.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Click &amp;quot;Next&amp;quot; to select a preset report style and enter the title of the report.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/7026.reportwizard_5F00_style.png"&gt;&lt;img src="http://helpcentral.componentone.com/CS/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/7026.reportwizard_5F00_style.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/2046.reportwizard_5F00_title.png"&gt;&lt;img src="http://helpcentral.componentone.com/CS/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/2046.reportwizard_5F00_title.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Finally, click &amp;quot;Finish&amp;quot; and Voila! The initial settings of your report are done.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/6036.reportdesigner2.png"&gt;&lt;img src="http://helpcentral.componentone.com/CS/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/6036.reportdesigner2.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Now we will decide where the report will be displayed and set the font for the report. These settings are easy to do by just dragging and dropping or setting some properties. We can also set the date format and currency format for our fields here. &lt;/p&gt;
&lt;p&gt;Save the report definition file to complete the report design process.&lt;/p&gt;
&lt;h2&gt;Load and Print the report design using C1Report&lt;/h2&gt;
&lt;p&gt;Now, let&amp;#39;s use the actual &lt;strong&gt;C1Report&lt;/strong&gt; component in our Sales Management application.&lt;/p&gt;
&lt;p&gt;Place the &lt;strong&gt;C1Report&lt;/strong&gt; component on the Form. To get the&lt;strong&gt; C1Report&lt;/strong&gt; control, add the control to the toolbox by right clicking on it and accessing the &amp;quot;Choose Toolbox Items&amp;quot; dialog.　For details on how to add controls refer to previous articles.&lt;/p&gt;
&lt;p&gt;Use the Load method to load the report definition file. Set the parameters of the Load method to report definition file path and the name of the report. A single report definition file can contain many reports. In our case, we have only 1 report in our definition file but we still need to provide the report name. &lt;/p&gt;
&lt;pre&gt;this.c1Report1.Load(&amp;quot;Report File Name &amp;amp; Path&amp;quot;, &amp;quot;Report Name&amp;quot;);&lt;/pre&gt;
&lt;p&gt;Then, call the Print method of the Document property to print the report.&lt;/p&gt;
&lt;pre&gt;this.c1Report1.Document.Print();&lt;/pre&gt;
&lt;h2&gt;Preview and Export the Report&lt;/h2&gt;
&lt;p&gt;Printing starts the moment we click on the Report button because we are calling the Print method. However, it would be better if we get a print preview first and allow the user to change settings before the actual printing takes place. Just like other applications. It would also be great if we can get the output of the report in other formats besides simply printing. That would enhance the usability of this application tremendously. So let&amp;#39;s try adding these two features.&lt;/p&gt;
&lt;p&gt;Exporting the report as a file is easy; &lt;strong&gt;C1Report&lt;/strong&gt;&amp;#39;s RenderToFile method is available for generating outputted formats such as rich text, Html, PDF and Excel.&lt;/p&gt;
&lt;p&gt;We can easily generate a preview using the standard PrintPreviewControl or the PrintPreviewDialog control. However, there will be some limitations on paging and zooming if you use the standard controls. So for better performance we will use &lt;strong&gt;C1PrintPreviewControl&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;C1PrintPreviewControl&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;C1PrintPreviewControl&lt;/strong&gt; is also include in Studio for WinForms. This control is optimized for&lt;strong&gt; C1Report&lt;/strong&gt; and already contains features like preview, printing, export etc. Just a few quick settings are needed to implement these features in our application. &lt;/p&gt;
&lt;p&gt;First, place the &lt;strong&gt;C1PrintPreviewControl&lt;/strong&gt; on the screen where we want the report to be displayed. For this sample, we add a new Form window called Print Preview.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/8883.print_5F00_preview.png"&gt;&lt;img src="http://helpcentral.componentone.com/CS/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/8883.print_5F00_preview.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Change the settings of &lt;strong&gt;C1PrintPreviewControl&lt;/strong&gt; to decide on the commands to be used or if the navigation panel is to be displayed or not.&lt;/p&gt;
&lt;pre&gt;// Set the tools to be used.
this.c1PrintPreviewControl1.AvailablePreviewActions = C1PreviewActionFlags.All;
// Hide the navigation panel.
this.c1PrintPreviewControl1.NavigationPanelVisible = false;&lt;/pre&gt;
&lt;p&gt;Then, assign the Document property of &lt;strong&gt;C1Report&lt;/strong&gt; to the Document property of &lt;strong&gt;C1PrintPreviewControl&lt;/strong&gt;.&lt;/p&gt;
&lt;pre&gt;// Display document preview.
this.c1PrintPreviewControl1.Document = this.c1Report1.Document;&lt;/pre&gt;
&lt;p&gt;That&amp;#39;s it! Replace the line of code that prints our report from earlier and display our new Print Preview window.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/1663.reportPreview.png"&gt;&lt;img src="http://helpcentral.componentone.com/CS/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/1663.reportPreview.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Use the buttons on the Print Preview of the &lt;strong&gt;C1PrintPreviewControl&lt;/strong&gt; to print or get the file output.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In this part, we used the&lt;strong&gt; C1ReportDesigner&lt;/strong&gt; application to create a report. We used the &lt;strong&gt;C1Report&lt;/strong&gt; and &lt;strong&gt;C1PrintPreviewControl&lt;/strong&gt; to load and preview our report in the Sales Management application. Unlike PDF export explained in &lt;a href="http://helpcentral.componentone.com/CS/blogs/windev/archive/2010/02/09/BecomeAnExpertPart3.aspx"&gt;Part 3&lt;/a&gt;, here we are able to export our data to PDF format in a better design.&lt;/p&gt;
&lt;p&gt;In the final part, we will use some interesting features provided by various controls and complete the application.&lt;/p&gt;
&lt;h2&gt;Acknowledgements&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Article copyright &amp;copy; 2009&amp;nbsp;Shogo Takayama&amp;nbsp;＆ Shoeisha Co., Ltd. http://www.shoeisha.co.jp/ &lt;/li&gt;
&lt;li&gt;&lt;a href="http://codezine.jp/"&gt;http://codezine.jp/&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;These contents were made by GrapeCity inc., Japan. &lt;a href="http://www.grapecity.com/japan/"&gt;http://www.grapecity.com/japan/&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;Translated by Vidhi Kapoor (GrapeCity India Pvt. Ltd.)&lt;/li&gt;
&lt;li&gt;Edited by Greg Lutz (ComponentOne)&lt;/li&gt;
&lt;/ul&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://helpcentral.componentone.com/CS/aggbug.aspx?PostID=229757" width="1" height="1"&gt;</content><author><name>C1_GregL</name><uri>http://helpcentral.componentone.com/CS/members/C1_5F00_GregL/default.aspx</uri></author><category term="WinForms" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/WinForms/default.aspx" /><category term="C1ReportDesigner" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/C1ReportDesigner/default.aspx" /><category term="C1Report" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/C1Report/default.aspx" /><category term="Become an Expert" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/Become+an+Expert/default.aspx" /><category term="C1PrintPreviewControl" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/C1PrintPreviewControl/default.aspx" /></entry><entry><title>Creating Outlines and Trees with the C1FlexGrid Control</title><link rel="alternate" type="text/html" href="/CS/blogs/windev/archive/2010/03/03/creating-outlines-and-trees-with-the-c1flexgrid-control.aspx" /><link rel="enclosure" type="application/x-compressed" length="15376" href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.FlexGridTrees/6327.C1FlexGridOutlinesAndTrees.zip" /><id>/CS/blogs/windev/archive/2010/03/03/creating-outlines-and-trees-with-the-c1flexgrid-control.aspx</id><published>2010-03-03T18:52:00Z</published><updated>2010-03-03T18:52:00Z</updated><content type="html">&lt;p&gt;One of the unique and popular features of the &lt;b&gt;C1FlexGrid&lt;/b&gt; control is the ability to add hierarchical grouping to regular unstructured data.&lt;/p&gt;
&lt;p&gt;To achieve this, the &lt;b&gt;C1FlexGrid &lt;/b&gt;introduces the concept of &lt;b&gt;Node &lt;/b&gt;rows. Node rows do not contain regular data. Instead, they act as headers under which similar data is grouped, exactly like nodes in a regular &lt;b&gt;TreeView &lt;/b&gt;control. Like nodes in a &lt;b&gt;TreeView &lt;/b&gt;control, node rows can be collapsed and expanded, hiding or showing the data they contain. Also like nodes in a &lt;b&gt;TreeView &lt;/b&gt;control, node rows have a &lt;b&gt;Level &lt;/b&gt;property that defines the node hierarchy. Lower level nodes contain higher level nodes.&lt;/p&gt;
&lt;p&gt;For example, suppose you had a grid showing customer name, country, city, and sales amounts. This typical grid would normally look like this:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.FlexGridTrees/7041.RegularGrid.png"&gt;&lt;img src="http://helpcentral.componentone.com/CS/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.FlexGridTrees/7041.RegularGrid.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;All the information is there, but it&amp;#39;s hard to see the total sales for each country or customer. You could use the &lt;b&gt;C1FlexGrid&lt;/b&gt;&amp;#39;s outlining features to group the data by country (level 0), then by city within each country (level 1), then by customer within each city (level 2). Here is the same grid with after adding the outline:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.FlexGridTrees/8424.OutlineGrid.png"&gt;&lt;img src="http://helpcentral.componentone.com/CS/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.FlexGridTrees/8424.OutlineGrid.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This grid shows the same information as the previous one (it is bound to the same data source), but it adds a tree where each node contains a summary of the data below it. Nodes can be collapsed to show only the summary, or expanded to show the detail. Note that each node row can show summaries for more than one column (in this case, total units sold and total amount).&lt;/p&gt;
&lt;p&gt;In this article, we will walk you through the process of turning a regular grid into a richer outline grid.&lt;/p&gt;
&lt;h3&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.FlexGridTrees/6327.C1FlexGridOutlinesAndTrees.zip"&gt;Download Visual Studio Sample (C#)&lt;/a&gt;&lt;/h3&gt;
&lt;h2&gt;Loading the Data&lt;/h2&gt;
&lt;p&gt;Loading data into an outline grid is exactly the same as loading it into a regular grid. If your data source is available at design time, you can use the Visual Studio Property Window to set the grid&amp;#39;s &lt;b&gt;DataSource &lt;/b&gt;property and bind the grid to the data without writing any code. &lt;/p&gt;
&lt;p&gt;If the data source is not available at design time, you can set the grid&amp;#39;s &lt;b&gt;DataSource &lt;/b&gt;property in code. The data binding code typically looks like this:&lt;/p&gt;
&lt;pre&gt;public Form1()
{
    InitializeComponent();

    // get data
    var fields = @&amp;quot;
        Country, 
        City, 
        SalesPerson, 
        Quantity,
        ExtendedPrice&amp;quot;;
    var sql = string.Format(&amp;quot;SELECT {0} FROM Invoices ORDER BY {0}&amp;quot;, fields);
    var da = new OleDbDataAdapter(sql, GetConnectionString());
    da.Fill(_dt);

    &lt;b&gt;// bind grid to data
    this._flex.DataSource = _dt;&lt;/b&gt;
    
    // format ExtendedPrice column
    _flex.Cols[&amp;quot;ExtendedPrice&amp;quot;].Format = &amp;quot;n2&amp;quot;;
}&lt;/pre&gt;
&lt;p&gt;The code uses an &lt;b&gt;OleDbDataAdapter &lt;/b&gt;to fill a &lt;b&gt;DataTable &lt;/b&gt;with data, then assigns the &lt;b&gt;DataTable &lt;/b&gt;to the grid&amp;#39;s &lt;b&gt;DataSource&lt;/b&gt; property.&lt;/p&gt;
&lt;p&gt;After running this code, you would get the &amp;quot;regular grid&amp;quot; shown in the first image. To turn this regular grid into the outline grid shown in the second image, we need to insert the node rows that make up the outline.&lt;/p&gt;
&lt;h3&gt;Creating Node Rows&lt;/h3&gt;
&lt;p&gt;Node rows are almost identical to regular rows, except for the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Node rows are not data bound. When the grid is bound to a data source, each regular row corresponds to an item in the data source. Node rows do not. Instead, they exist to group regular rows that contain similar data.&lt;/li&gt;
&lt;li&gt;Node rows can be collapsed or expanded. When a node row is collapsed, all its data and child nodes are hidden. If the outline tree is visible, users can collapse and expand nodes using the mouse or the keyboard. If the outline tree is not visible, then nodes can only be expanded or collapsed using code.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To determine whether a row is a node or not, you can use the &lt;b&gt;IsNode &lt;/b&gt;property:&lt;/p&gt;
&lt;pre&gt;var row = _flex.Rows[rowIndex];
if (row.IsNode)
{
    // row is a node
    var node = row.Node;
    DoSomethingWithTheNode(node);
}
else
{
    // this row is not a node
}&lt;/pre&gt;
&lt;p&gt;Node rows can be created in three ways:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Use the &lt;b&gt;Rows.InsertNode &lt;/b&gt;method. This will insert a new node row at a specified index. Once the node row has been created, you can use it like you would any other row (set the data for each column, apply styles, etc). This is the &amp;#39;low-level&amp;#39; way of inserting totals and building outlines. It gives the most control and flexibility and is demonstrated below. &lt;/li&gt;
&lt;li&gt;Use the &lt;b&gt;Subtotal &lt;/b&gt;method. This method scans the entire grid and automatically inserts node rows with optional subtotals at locations where the grid data changes. This is the &amp;#39;high-level&amp;#39; way of inserting totals and building outlines. It requires very little code, but makes some assumptions about how the data is structured on the grid and what the outline should look like.&lt;/li&gt;
&lt;li&gt;If the grid is unbound, then you can turn regular rows into node rows by setting the &lt;b&gt;IsNode &lt;/b&gt;property to true. Note that this only works when the grid is &lt;i&gt;unbound&lt;/i&gt;. Trying to turn a regular data bound row into a node will cause the grid to throw an exception. &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The code below shows how you could implement a &lt;b&gt;GroupBy&lt;/b&gt; method that inserts node rows grouping identical values on a given column.&lt;/p&gt;
&lt;pre&gt;// group on a given column inserting nodes of a given level
void GroupBy(string columnName, int level)
{
    object current = null;
    for (int r = _flex.Rows.Fixed; r &amp;lt; _flex.Rows.Count; r++)
    {
        if (!_flex.Rows[r].IsNode)
        {
            var value = _flex[r, columnName];
            if (!object.Equals(value, current))
            {
                // value changed: insert node
                &lt;b&gt;_flex.Rows.InsertNode(r, level);&lt;/b&gt;

                // show group name in first scrollable column
                _flex[r, _flex.Cols.Fixed] = value;

                // update current value
                current = value;
            }
        }
    }
}
&lt;/pre&gt;
&lt;p&gt;The code scans all the columns, skipping existing node rows (so it can be called to add several levels of nodes), and keeps track of the current value for the column being grouped on. When the current value changes, a node row is inserted showing the new in the first scrollable column.&lt;/p&gt;
&lt;p&gt;Back to our example, you could use this method to create a two-level outline by calling:&lt;/p&gt;
&lt;pre&gt;void _btnGroupCountryCity_Click(object sender, EventArgs e)
{
    GroupBy(&amp;quot;Country&amp;quot;, 0);
    GroupBy(&amp;quot;City&amp;quot;, 1);
}&lt;/pre&gt;
&lt;p&gt;Very simple, but there are some caveats. First, the method assumes that the data is sorted according to the outline structure. In this example, if the data were sorted by &lt;b&gt;SalesPerson&lt;/b&gt; instead of by &lt;b&gt;Country&lt;/b&gt;, the outline would have several level-0 nodes for each country, which probably is not what you want.&lt;/p&gt;
&lt;p&gt;Also, the &lt;b&gt;GroupBy &lt;/b&gt;method may insert may rows, which would cause the grid to flicker. To avoid this, you would normally set the &lt;b&gt;Redraw &lt;/b&gt;property to false before making the updates and set it back to true when done.&lt;/p&gt;
&lt;p&gt;To handle these issues, the code that creates the outline should be re-written as follows:&lt;/p&gt;
&lt;pre&gt;void _btnGroupCountryCity_Click(object sender, EventArgs e)
{
    // suspend redrawing while updating
    using (new DeferRefresh(_flex))
    {
        // restore original sort (by Country, City, etc.)
        ResetBinding();

        // group by Country, City
        GroupBy(&amp;quot;Country&amp;quot;, 0);
        GroupBy(&amp;quot;City&amp;quot;, 1);
    }
}&lt;/pre&gt;
&lt;p&gt;The &lt;b&gt;DeferRefresh &lt;/b&gt;class is a simple utility that sets the grid&amp;#39;s &lt;b&gt;Redraw &lt;/b&gt;property to false and restores its original value when it is disposed. This ensures that&lt;b&gt; Redraw&lt;/b&gt; is properly restored even when exceptions happen during the updates. Here is the implementation of the &lt;b&gt;DeferRefresh &lt;/b&gt;class:&lt;/p&gt;
&lt;pre&gt;/// Utility class used to encapsulate grid lengthy operations in a Redraw block.
/// This avoids flicker and ensures the Redraw property is reset properly in case 
/// an exception is thrown during the operation.
class DeferRefresh : IDisposable
{
    C1FlexGrid _grid;
    bool _redraw;
    public DeferRefresh(C1FlexGrid grid)
    {
        _grid = grid;
        _redraw = grid.Redraw;
        &lt;b&gt;grid.Redraw = false;&lt;/b&gt;
    }
    public void Dispose()
    {
        &lt;b&gt;_grid.Redraw = _redraw;&lt;/b&gt;
    }
}&lt;/pre&gt;
&lt;p&gt;The &lt;b&gt;BindGrid &lt;/b&gt;method ensures that the grid is sorted in the order required by our outline structure. In our example, the sort order is by &lt;b&gt;Country&lt;/b&gt;, &lt;b&gt;City&lt;/b&gt;, and &lt;b&gt;SalesPerson&lt;/b&gt;. The code looks like this:&lt;/p&gt;
&lt;pre&gt;// unbind and re-bind grid in order to reset everything
void ResetBinding()
{
    // unbind grid
    _flex.DataSource = null;
    
    // reset any custom sorting
    _dt.DefaultView.Sort = string.Empty;

    // re-bind grid
    _flex.DataSource = _dt;
    
    // format ExtendedPrice column
    _flex.Cols[&amp;quot;ExtendedPrice&amp;quot;].Format = &amp;quot;n2&amp;quot;;
    
    // auto-size the columns to fit their content
    _flex.AutoSizeCols();
}&lt;/pre&gt;
&lt;p&gt;If you run this code now, you will notice that the node rows are created as expected, but the outline tree is not visible, so you can&amp;#39;t expand and collapse the nodes. The outline tree is described in the next section.&lt;/p&gt;
&lt;h2&gt;Outline Tree &lt;/h2&gt;
&lt;p&gt;The outline tree is very similar to the one you see in a regular &lt;b&gt;TreeView&lt;/b&gt; control. It shows an indented structure with collapse/expand icons next to each node row so the user can expand and collapse the outline to see the desired level of detail.&lt;/p&gt;
&lt;p&gt;The outline tree can be displayed in any column, defined by the &lt;b&gt;Tree.Column &lt;/b&gt;property. By default, this property is set to -1, which causes the tree not to be displayed at all. To show the outline tree in the example given above, you would use this code:&lt;/p&gt;
&lt;pre&gt;void _btnTreeCountryCity_Click(object sender, EventArgs e)
{
    using (new DeferRefresh(_flex))
    {
        // group by country and city as before
        _btnGroupCountryCity_Click(this, EventArgs.Empty);

        &lt;b&gt;// show outline tree
        _flex.Tree.Column = 0;
&lt;/b&gt;
        // autosize to accommodate tree
        _flex.AutoSizeCol(_flex.Tree.Column);

        // collapse detail nodes
        _flex.Tree.Show(1);
    }
}&lt;/pre&gt;
&lt;p&gt;The code calls the previous method to build the outline, then sets the &lt;b&gt;Tree.Column &lt;/b&gt;property to zero in order to show the outline tree in the first column. It also calls the &lt;b&gt;AutoSizeCol&lt;/b&gt; method to ensure that the column is wide enough to accommodate the outline tree. Finally, it calls the &lt;b&gt;Tree.Show &lt;/b&gt;method to display all level-0 nodes (cities in this case) and hide all the detail.&lt;/p&gt;
&lt;p&gt;The &lt;b&gt;Tree &lt;/b&gt;property returns a reference to a &lt;b&gt;GridTree &lt;/b&gt;object that exposes several methods and properties used to customize the outline tree. The main ones are listed below:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Column: &lt;/b&gt;Gets or sets the index of the column that contains the outline tree. Setting this property to -1 causes the outline tree to be hidden from the users.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Indent: &lt;/b&gt;Gets or sets the indent, in pixels, between adjacent node levels. Higher indent levels cause the tree to become wider.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Style: &lt;/b&gt;Gets or sets the type of outline tree to display. Use this property to determine whether the tree should include a button bar at the top to allow users to collapse/expand the entire tree, whether lines and/or symbols should be displayed, and whether lines should be displayed connecting the tree to data rows as well as node rows.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;LineColor: &lt;/b&gt;Gets or sets the color of the tree&amp;#39;s connecting lines.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;LineStyle: &lt;/b&gt;Gets or sets the style of the tree&amp;#39;s connecting lines.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example, by changing the code above to include these two lines:&lt;/p&gt;
&lt;pre&gt;// show outline tree
_flex.Tree.Column = 0;
_flex.Tree.Style = TreeStyleFlags.CompleteLeaf;
_flex.Tree.LineColor = Color.White;
_flex.Tree.Indent = 30;&lt;/pre&gt;
&lt;p&gt;The outline tree would change as follows:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.FlexGridTrees/5381.ButtonBar.png"&gt;&lt;img src="http://helpcentral.componentone.com/CS/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.FlexGridTrees/5381.ButtonBar.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Notice the buttons labeled &amp;quot;1&amp;quot;, &amp;quot;2&amp;quot;, and &amp;quot;*&amp;quot; on the top left cell. Clicking these buttons would cause the entire tree to collapse or expand to the corresponding level. Also notice the much wider indentation and the lines connecting the tree to regular rows (&amp;quot;Anne Dodsworth&amp;quot;) as well as to node rows.&lt;/p&gt;
&lt;h2&gt;Adding Subtotals&lt;/h2&gt;
&lt;p&gt;So far we have covered the creation of node rows and outline trees. To make the outlines really useful, however, the node rows should include summary information for the data they contain. &lt;/p&gt;
&lt;p&gt;If you create an outline tree using the &lt;b&gt;Subtotal &lt;/b&gt;method, then the subtotals are added automatically. This will be described in a later section.&lt;/p&gt;
&lt;p&gt;If you created the outline tree using the &lt;b&gt;Rows.InsertNode &lt;/b&gt;method as described above, then you should use the &lt;b&gt;Aggregate &lt;/b&gt;method to calculate the subtotals for each group of rows and insert the result directly into the node rows.&lt;/p&gt;
&lt;p&gt;The &lt;b&gt;Subtotal &lt;/b&gt;method listed below shows how to do this:&lt;/p&gt;
&lt;pre&gt;// add subtotals to each node at a given level
void AddSubtotals(int level, string colName)
{
    // get column we are going to total on
    int colIndex = _flex.Cols.IndexOf(colName);

    // scan rows looking for nodes at the right level
    for (int r = _flex.Rows.Fixed; r &amp;lt; _flex.Rows.Count; r++)
    {
        if (_flex.Rows[r].IsNode)
        {
            var node = _flex.Rows[r].Node;
            if (node.Level == level)
            {
                // found a node, calculate the sum of extended price
                var range = node.GetCellRange();
                &lt;b&gt;var sum = _flex.Aggregate(AggregateEnum.Sum, 
                    range.r1, colIndex, range.r2, colIndex,
                    AggregateFlags.ExcludeNodes);&lt;/b&gt;

                // show the sum on the grid 
                // (will use the column format automatically)
                _flex[r, colIndex] = sum;
            }
        }
    }
}&lt;/pre&gt;
&lt;p&gt;The &lt;b&gt;AddSubtotals &lt;/b&gt;method scans the grid rows looking for node rows. When a node row of the desired level is found, the method uses the &lt;b&gt;Node.GetCellRange &lt;/b&gt;method to retrieve the node&amp;#39;s child rows. Then it uses the &lt;b&gt;Aggregate &lt;/b&gt;method to calculate the sum of the values on the target column over the entire range. The call to &lt;b&gt;Aggregate &lt;/b&gt;includes the &lt;b&gt;ExcludeNodes &lt;/b&gt;flag to avoid double-counting existing nodes. Once the subtotal has been calculated, it is assigned to the node row&amp;#39;s cell with the usual &lt;b&gt;_flex[row, col]&lt;/b&gt; indexer.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;Note that this does not affect the data source in any way, since node rows are not bound to the data.&lt;/p&gt;
&lt;p&gt;Note also that the method can be used to add multiple totals to each node row. In this example, we will add totals for the &lt;b&gt;Quantity &lt;/b&gt;and &lt;b&gt;ExtendedPrice &lt;/b&gt;columns. In addition to sums, you could add other aggregates such as average, maximum, minimum, etc.&lt;/p&gt;
&lt;p&gt;We can now use this method to create a complete outline, with node rows, outline tree, and subtotals:&lt;/p&gt;
&lt;pre&gt;void _btnTreeCountryCity_Click(object sender, EventArgs e)
{
    using (new DeferRefresh(_flex))
    {
        // restore original sort (by Country, City, SalesPerson)
        ResetBinding();

        // group by Country, City
        GroupBy(&amp;quot;Country&amp;quot;, 0); // group by country (level 0)
        GroupBy(&amp;quot;City&amp;quot;, 1);    // group by city (level 1)

        // add totals per Country, City
        AddSubtotals(0, &amp;quot;ExtendedPrice&amp;quot;);  // extended price per country (level 0)
        AddSubtotals(0, &amp;quot;Quantity&amp;quot;);       // quantity per country (level 0)
        AddSubtotals(1, &amp;quot;ExtendedPrice&amp;quot;);  // extended price per city (level 1)
        AddSubtotals(1, &amp;quot;Quantity&amp;quot;);       // quantity per city (level 1)

        // show outline tree
        _flex.Tree.Column = 0;
        _flex.AutoSizeCol(_flex.Tree.Column);
        _flex.Tree.Show(1);
    }
}
&lt;/pre&gt;
&lt;p&gt;If you run the project now, you will see a tree with node rows that show the total quantity and amount sold for each country and city. This is very nice, but there is a little problem. If you expand any of the node rows, you will see a lot of duplicate values. All rows under a given city node have the same country and city:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.FlexGridTrees/2148.Duplicates.png"&gt;&lt;img src="http://helpcentral.componentone.com/CS/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.FlexGridTrees/2148.Duplicates.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This is correct, but it is also a waste of screen real estate. Eliminating these duplicate values is easy, all you have to do is set the &lt;b&gt;Width &lt;/b&gt;of the columns that are being grouped on to zero. When you do that, however, you should remember to set the grid&amp;#39;s &lt;b&gt;AllowMerging &lt;/b&gt;property to &lt;b&gt;Nodes&lt;/b&gt;, so the text assigned to the node rows will spill into the visible columns. (Another option would be to assign the node text to the first visible column, but merging is usually a better solution because it allows you to use longer text for the node rows).&lt;/p&gt;
&lt;p&gt;Here is the revised code and the final result:&lt;/p&gt;
&lt;pre&gt;void _btnTreeCountryCity_Click(object sender, EventArgs e)
{
    using (new DeferRefresh(_flex))
    {
        // restore original sort (by Country, City, SalesPerson)
        ResetBinding();

        // group by Country, City
        GroupBy(&amp;quot;Country&amp;quot;, 0); // group by country (level 0)
        GroupBy(&amp;quot;City&amp;quot;, 1);    // group by city (level 1)

        // hide columns that we grouped on 
        // (they only have duplicate values which already appear on the tree nodes)
        // (but don&amp;#39;t make them invisible, that would also hide the node text)
        _flex.Cols[&amp;quot;Country&amp;quot;].Width = 0;
        _flex.Cols[&amp;quot;City&amp;quot;].Width = 0;

        // allow node content to spill onto next cell
        _flex.AllowMerging = AllowMergingEnum.Nodes;

        // add totals per Country, City
        AddTotals(0, &amp;quot;ExtendedPrice&amp;quot;);  // extended price per country (level 0)
        AddTotals(0, &amp;quot;Quantity&amp;quot;);       // quantity per country (level 0)
        AddTotals(1, &amp;quot;ExtendedPrice&amp;quot;);  // extended price per city (level 1)
        AddTotals(1, &amp;quot;Quantity&amp;quot;);       // quantity per city (level 1)
        
        // show outline tree
        _flex.Tree.Column = 0;
        _flex.AutoSizeCol(_flex.Tree.Column);
        _flex.Tree.Show(1);
    }
}
&lt;/pre&gt;
&lt;p&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.FlexGridTrees/8015.NoDuplicates.png"&gt;&lt;img src="http://helpcentral.componentone.com/CS/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.FlexGridTrees/8015.NoDuplicates.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The &lt;b&gt;Country &lt;/b&gt;and &lt;b&gt;City &lt;/b&gt;columns are now invisible, but their values still appear in the node rows. Collapsing the tree shows totals for each country and city.&lt;/p&gt;
&lt;h2&gt;Using the Subtotal Method&lt;/h2&gt;
&lt;p&gt;We mentioned earlier that you could also create trees using the &lt;b&gt;C1FlexGrid&lt;/b&gt;&amp;#39;s &lt;b&gt;Subtotal &lt;/b&gt;method. The &lt;b&gt;Subtotal &lt;/b&gt;method performs the same tasks as the &lt;b&gt;GroupBy &lt;/b&gt;and &lt;b&gt;AddSubtotals &lt;/b&gt;methods described above, except it does both things in a single step and is therefore a little more efficient.&lt;/p&gt;
&lt;p&gt;The code below shows how you can use the &lt;b&gt;Subtotal&lt;/b&gt; method to accomplish the same thing we did before, only a little faster and without using any helper methods:&lt;/p&gt;
&lt;pre&gt;void _btnTreeCountryCity_Click(object sender, EventArgs e)
{
    using (new DeferRefresh(_flex))
    {
        // restore original sort (by Country, City, SalesPerson)
        ResetBinding();

        // group and total by country and city
        _flex.Subtotal(AggregateEnum.Sum, 0, &amp;quot;Country&amp;quot;, &amp;quot;ExtendedPrice&amp;quot;);
        _flex.Subtotal(AggregateEnum.Sum, 0, &amp;quot;Country&amp;quot;, &amp;quot;Quantity&amp;quot;);
        _flex.Subtotal(AggregateEnum.Sum, 1, &amp;quot;City&amp;quot;, &amp;quot;ExtendedPrice&amp;quot;);
        _flex.Subtotal(AggregateEnum.Sum, 1, &amp;quot;City&amp;quot;, &amp;quot;Quantity&amp;quot;);

        // hide columns that we grouped on 
        // (they only have duplicate values which already appear on the tree nodes)
        // (but don&amp;#39;t make them invisible, that would also hide the node text)
        _flex.Cols[&amp;quot;Country&amp;quot;].Width = 0;
        _flex.Cols[&amp;quot;City&amp;quot;].Width = 0;
        _flex.AllowMerging = AllowMergingEnum.Nodes;

        // show outline tree
        _flex.Tree.Column = 0;
        _flex.AutoSizeCol(_flex.Tree.Column);
        _flex.Tree.Show(1);
    }
}&lt;/pre&gt;
&lt;p&gt;The &lt;b&gt;Subtotal &lt;/b&gt;method is very convenient and flexible. It has a number of overloads that allow you to specify which columns should be grouped on and totaled on by index or by name, whether to include a caption in the node rows that it inserts, how to perform the grouping, and so on. The summary below describes the overloads available:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;b&gt;Subtotal(AggregateEnum aggType)&lt;/b&gt;&lt;br /&gt;This version of the method takes only one aggregate type as a parameter. It is useful only for removing existing subtotals before inserting new ones. In this case, the &lt;b&gt;&lt;i&gt;aggType &lt;/i&gt;&lt;/b&gt;parameter is set to &lt;b&gt;AggregateEnum.Clear&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Subtotal(&lt;/b&gt;AggregateEnum aggType, &lt;b&gt;int groupBy, int totalOn)&lt;br /&gt;Subtotal(&lt;/b&gt;AggregateEnum aggType, &lt;b&gt;string groupBy, string totalOn)&lt;br /&gt;&lt;/b&gt;These are the most commonly used overloads. The parameters are the type of aggregate to insert and the columns to group by and total on. The columns may be referenced by index or by name. The latter is the one we used in the example above. &lt;/li&gt;
&lt;li&gt;&lt;b&gt;Subtotal(&lt;/b&gt;AggregateEnum aggType, int groupBy, int totalOn, &lt;b&gt;string caption)&lt;br /&gt;Subtotal(&lt;/b&gt;AggregateEnum aggType, string groupBy, string totalOn, &lt;b&gt;string caption)&lt;br /&gt;&lt;/b&gt;These overloads add an extra &lt;i&gt;&lt;b&gt;caption &lt;/b&gt;&lt;/i&gt;parameter. The &lt;i&gt;&lt;b&gt;caption &lt;/b&gt;&lt;/i&gt;parameter determines the text that is added to the new node rows to identify the value being grouped on. By default, the value being grouped on is shown, so if you are grouping by country, the node rows would show &amp;quot;Argentina&amp;quot;, &amp;quot;Brazil&amp;quot;, and so on. If you set the &lt;i&gt;&lt;b&gt;caption &lt;/b&gt;&lt;/i&gt;parameter to a string such as &amp;quot;Country: {0}&amp;quot;, then the node rows would show &amp;quot;Country: Argentina&amp;quot; instead.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Subtotal(&lt;/b&gt;AggregateEnum aggType, &lt;b&gt;int groupFrom, int groupTo,&lt;/b&gt; int totalOn, string caption&lt;b&gt;)&lt;br /&gt;Subtotal(&lt;/b&gt;AggregateEnum aggType, &lt;b&gt;string groupFrom, string groupTo,&lt;/b&gt; string totalOn, string caption&lt;b&gt;)&lt;br /&gt;&lt;/b&gt;These overloads separate the &lt;i&gt;&lt;b&gt;groupBy &lt;/b&gt;&lt;/i&gt;parameter into two: &lt;i&gt;&lt;b&gt;groupFrom &lt;/b&gt;&lt;/i&gt;and &lt;i&gt;&lt;b&gt;groupTo&lt;/b&gt;&lt;/i&gt;. By default, the &lt;b&gt;Subtotal &lt;/b&gt;method inserts a node row whenever the value of the &lt;i&gt;&lt;b&gt;groupBy &lt;/b&gt;or any previous column &lt;/i&gt;changes.&lt;br /&gt;For example, if you a row has the same value in the &amp;quot;City&amp;quot; column as the previous row, but a different value in&amp;nbsp; the &amp;quot;Country&amp;quot; column, then the &lt;b&gt;Subtotal&lt;/b&gt; method assumes the rows should be in different groups and inserts a new node row even though the value of the &lt;i&gt;&lt;b&gt;groupBy &lt;/b&gt;&lt;/i&gt;column is the same. These aggregates let you override that behavior and specify the range of columns that should be considered when identifying a group.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Outline Maintenance&lt;/h2&gt;
&lt;p&gt;So far we have discussed how to create outlines with trees and totals using the high-level &lt;b&gt;Subtotal &lt;/b&gt;method as well as lower-level &lt;b&gt;Rows.InsertNode &lt;/b&gt;and &lt;b&gt;Aggregate &lt;/b&gt;methods.&lt;/p&gt;
&lt;p&gt;At this point, it is important to remember that the outline tree is created based on the data, but is not bound to it in any way, and is not automatically maintained when there are changes to the grid or to the data.&lt;/p&gt;
&lt;p&gt;If the user modifies a value in the &amp;quot;ExtendedPrice&amp;quot; column for example, the subtotals will not be automatically updated. If the user sorts the grid, the data will be refreshed and the subtotals will disappear.&lt;/p&gt;
&lt;p&gt;There are two common ways to maintain the outlines:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Prevent the user from making any changes that would invalidate the outline. This is the easiest option. You would set the grid&amp;#39;s &lt;b&gt;AllowEditing&lt;/b&gt;, &lt;b&gt;AllowDragging&lt;/b&gt;, and &lt;b&gt;AllowSorting &lt;/b&gt;properties to false and prevent any changes that would affect the outline.&lt;/li&gt;
&lt;li&gt;Update the outline when there are changes to the data or to the grid. You would attach handlers to the grid&amp;#39;s &lt;b&gt;AfterDataRefresh&lt;/b&gt;, &lt;b&gt;AfterSort&lt;/b&gt;, and &lt;b&gt;AfterEdit &lt;/b&gt;events and re-generate the outline appropriately.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Option 2 is usually more interesting since it provides a quick and simple tool for dynamic data analysis. This approach is illustrated by the &lt;b&gt;Analyze &lt;/b&gt;sample provided with the &lt;b&gt;C1FlexGrid&lt;/b&gt;. The sample creates an initial outline and allows users to reorder the columns. When the column order changes, the sample automatically re-sorts the data and re-creates the outline. The user can easily create simple reports showing sales by country, by product, by salesperson, and so on.&lt;/p&gt;
&lt;h2&gt;Using the Node class&lt;/h2&gt;
&lt;p&gt;The &lt;b&gt;Node &lt;/b&gt;class provides a number of methods and properties that can be used to create and manage outline trees. Many of these methods and properties are based on the standard &lt;b&gt;TreeView &lt;/b&gt;object model,&amp;nbsp; so they should be familiar to most developers.&lt;/p&gt;
&lt;p&gt;To obtain a &lt;b&gt;Node &lt;/b&gt;object, you can either:&lt;/p&gt;
&lt;p&gt;Use the return value of the &lt;b&gt;Rows.InsertNode &lt;/b&gt;method:&lt;/p&gt;
&lt;pre&gt;var node = _flex.Rows.InsertNode(index, level);&lt;/pre&gt;
&lt;p&gt;Or you can retrieve the node for an existing row using the row&amp;#39;s &lt;b&gt;Node &lt;/b&gt;property:&lt;/p&gt;
&lt;pre&gt;var node = _flex.Rows[index].IsNode
    ? _flex.Rows[index].Node
    : null;&lt;/pre&gt;
&lt;p&gt;Either way, once you have a &lt;b&gt;Node &lt;/b&gt;object you can manipulate it using the following properties and methods:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Level&lt;/b&gt;: Gets or sets the node level in the outline tree.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Data&lt;/b&gt;: Gets or sets the value in the cell defined by &lt;b&gt;Node.Row &lt;/b&gt;and the &lt;b&gt;Tree.Column&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Image&lt;/b&gt;: Gets or sets the image in the cell defined by &lt;b&gt;Node.Row &lt;/b&gt;and the &lt;b&gt;Tree.Column&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Checked&lt;/b&gt;: Gets or sets the check state of the cell defined by &lt;b&gt;Node.Row &lt;/b&gt;and the &lt;b&gt;Tree.Column&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Collapsed/Expanded&lt;/b&gt;: Gets or sets the node&amp;#39;s collapsed/expanded state.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can also explore the outline structure using the following methods:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;GetCellRange()&lt;/b&gt;: Gets a CellRange object that described the range of rows that belong to this node.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Children&lt;/b&gt;: Gets the number of child nodes under this node.&lt;/li&gt;
&lt;li&gt;&amp;nbsp;&lt;b&gt;Nodes&lt;/b&gt;: Gets a node array containing this node&amp;#39;s child nodes.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;GetNode&lt;/b&gt;: Gets the node that has a given relationship to this node (parent, first child, next sibling, and so on).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The discussion above focused on bound scenarios, where the grid is attached to a data source that provides the data. You can also create trees and outlines in unbound scenarios. Things are actually somewhat simpler in this case, since you can turn any row into a node row by setting its &lt;b&gt;IsNode &lt;/b&gt;property to true.&lt;/p&gt;
&lt;p&gt;If the grid is unbound, it owns all the data that is displayed, and you do things that are not possible when a data source owns the data. For example, you can move nodes around the tree using the &lt;b&gt;Node.Move &lt;/b&gt;method as shown by the &lt;b&gt;TreeNode &lt;/b&gt;sample provided with the &lt;b&gt;C1FlexGrid&lt;/b&gt;.&lt;/p&gt;
&lt;p&gt;Using nodes in an unbound grid is very similar to using nodes in a regular &lt;b&gt;TreeView &lt;/b&gt;control.&lt;/p&gt;
&lt;h3&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.FlexGridTrees/6327.C1FlexGridOutlinesAndTrees.zip"&gt;Download Visual Studio Sample (C#)&lt;/a&gt;&lt;/h3&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://helpcentral.componentone.com/CS/aggbug.aspx?PostID=229604" width="1" height="1"&gt;</content><author><name>C1_GregL</name><uri>http://helpcentral.componentone.com/CS/members/C1_5F00_GregL/default.aspx</uri></author><category term="WinForms" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/WinForms/default.aspx" /><category term="C1FlexGrid" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/C1FlexGrid/default.aspx" /></entry><entry><title>Become an Expert Part 4: Adding Charts to Analyze Sales Data</title><link rel="alternate" type="text/html" href="/CS/blogs/windev/archive/2010/02/17/BecomeAnExpertPart4.aspx" /><id>/CS/blogs/windev/archive/2010/02/17/BecomeAnExpertPart4.aspx</id><published>2010-02-17T15:50:00Z</published><updated>2010-02-17T15:50:00Z</updated><content type="html">&lt;h2&gt;Part 4 Overview&lt;/h2&gt;
&lt;p&gt;In this series, we will create a Sales Management application to show how easily anyone can &amp;quot;Become an Expert&amp;quot;. Up to this point, we have created a sales data list using &lt;strong&gt;C1FlexGrid&lt;/strong&gt; (part of ComponentOne Studio for WinForms), as well as added exporting options including Excel, XML and PDF. We&amp;#39;ve also refashioned the application window to have the Microsoft Office look-and-feel using C1Ribbon.&lt;/p&gt;
&lt;p&gt;*See &lt;a href="http://helpcentral.componentone.com/CS/blogs/windev/archive/2010/01/29/BecomeAnExpertPart1.aspx"&gt;Part One&lt;/a&gt;, &lt;a href="http://helpcentral.componentone.com/CS/blogs/windev/archive/2010/02/05/BecomeAnExpertPart2.aspx"&gt;Part&amp;nbsp;Two&lt;/a&gt;, and &lt;a href="http://helpcentral.componentone.com/CS/blogs/windev/archive/2010/02/09/BecomeAnExpertPart3.aspx"&gt;Part Three&lt;/a&gt; before continuing.&lt;/p&gt;
&lt;p&gt;Now in part 4, we will use the &lt;strong&gt;C1Chart&lt;/strong&gt; control (also part of Studio for WinForms) to display different kinds of charts. The data from our &lt;strong&gt;C1FlexGrid&lt;/strong&gt;, will be bound and displayed in the &lt;strong&gt;C1Chart&lt;/strong&gt; control.&lt;/p&gt;
&lt;p&gt;So let&amp;#39;s continue!&lt;/p&gt;
&lt;h2&gt;Application Set up&lt;/h2&gt;
&lt;p&gt;We will use the &lt;strong&gt;C1Chart&lt;/strong&gt; control to display charts. Just like before, we need to add the control to the toolbox. Right click on the toolbox and display &amp;quot;Choose Toolbox Items&amp;quot;. For details please refer to &lt;a href="http://helpcentral.componentone.com/CS/blogs/windev/archive/2010/01/29/BecomeAnExpertPart1.aspx"&gt;Part 1&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;First, add a new Form to the project. Change the Form to inherit the&lt;strong&gt; C1RibbonForm&lt;/strong&gt; class to match it with the application we created in Part 3. Set the form title and other properties. &lt;/p&gt;
&lt;h2&gt;Creating another Ribbon Interface&lt;/h2&gt;
&lt;p&gt;We need to create another Ribbon interface for the new form using the&lt;strong&gt; C1Ribbon&lt;/strong&gt; control.&lt;/p&gt;
&lt;p&gt;On the Home tab create three RibbonGroups:&lt;/p&gt;
&lt;p&gt;&lt;img src="http://helpcentral.componentone.com/CS/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/8345.Ribbon_5F00_chartTab.png" border="0" alt="" /&gt;&lt;/p&gt;
&lt;h2&gt;Home Tab&lt;/h2&gt;
&lt;p&gt;Category&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Select Category combobox&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Type&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Chart Type combobox&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Display Settings&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;3D Effect checkbox&lt;/li&gt;
&lt;li&gt;Stacked checkbox&lt;/li&gt;
&lt;li&gt;Change Axis checkbox&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Refer to &lt;a href="http://helpcentral.componentone.com/CS/blogs/windev/archive/2010/02/09/BecomeAnExpertPart3.aspx"&gt;Part Three&lt;/a&gt;, implementing the Ribbon Interface, for more information on setting up the ribbon.&lt;/p&gt;
&lt;h2&gt;Display settings for C1Chart&lt;/h2&gt;
&lt;p&gt;Now let&amp;#39;s add the &lt;strong&gt;C1Chart&lt;/strong&gt; control to the form. Drag and drop the &lt;strong&gt;C1Chart&lt;/strong&gt; control onto the new form under the ribbon. Dock the &lt;strong&gt;C1Chart&lt;/strong&gt; control to fill the entire form. The display area for the chart is created!&lt;/p&gt;
&lt;p&gt;The next step is to create a BindingSource to use as the data source for the &lt;strong&gt;C1Chart&lt;/strong&gt; control. Place a BindingSource object on the form, named bindingSource1.&lt;/p&gt;
&lt;p&gt;We will create a data class in the project called SalesData and place this in a class file called SalesDataList.cs. Members of this data class will be &amp;quot;Date&amp;quot;, &amp;quot;Category&amp;quot;, &amp;quot;Proceeds&amp;quot;, &amp;quot;Payments&amp;quot; and &amp;quot;GrossMargin&amp;quot;. Members can be in the form of properties or public variables (Note: In the sample, all the members are properties).&lt;/p&gt;
&lt;p&gt;&lt;code&gt;
&lt;p&gt;public class SalesData&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; public SalesData()&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; private DateTime _date = DateTime.Now;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; public DateTime Date&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; get { return this._date; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; set { this._date = value; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; private string _categoryCode = string.Empty;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; public string CategoryCode&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; get { return this._categoryCode; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; set { this._categoryCode = value; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; private int _proceeds = 0;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; public int Proceeds&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; get { return this._proceeds; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; set { this._proceeds = value; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; private int _payments = 0;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; public int Payments&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; get { return this._payments; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; set { this._payments = value; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; private int _grossMargin = 0;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; public int GrossMargin&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; get { return this._grossMargin; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; set { this._grossMargin = value; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;/p&gt;
&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Set this data class as the data source of the BindingSource we just added to the form above (Note: An array has to be set in the data source, so a List is used).&lt;/p&gt;
&lt;p&gt;&lt;code&gt;
&lt;p&gt;public class SalesDataList : List&amp;lt;SalesData&amp;gt;&lt;/p&gt;
&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;
&lt;p&gt;Now let&amp;#39;s set more specifics for our chart. Launch the chart Wizard from the &lt;strong&gt;C1Chart Tasks&lt;/strong&gt; (the smart tag).&lt;/p&gt;
&lt;p&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/3542.displaywizard.png"&gt;&lt;img src="http://helpcentral.componentone.com/CS/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/3542.displaywizard.png" alt="Display Wizard" border="0" style="border:0;" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Follow these settings in the wizard:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Select chart type&lt;br /&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/3058.chartwizard_5F00_type.png"&gt;&lt;img src="http://helpcentral.componentone.com/CS/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/3058.chartwizard_5F00_type.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Set the captions for the chart Header, X Axis and Y Axis&lt;br /&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/8420.chartwizard_5F00_setup.png"&gt;&lt;img src="http://helpcentral.componentone.com/CS/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/8420.chartwizard_5F00_setup.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Select the data source to be displayed in the &lt;strong&gt;C1Chart&lt;/strong&gt; control. Select the bindingSource1 BindingSource created earlier.&lt;br /&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/4682.chartwizard_5F00_data.png"&gt;&lt;img src="http://helpcentral.componentone.com/CS/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/4682.chartwizard_5F00_data.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Select which fields to use for data binding for three each of the three data series (Proceeds, Payments and GrossMargin). Each data series&amp;#39; X values will be bound to the Date field.&lt;br /&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/5287.chartwizard_5F00_series.png"&gt;&lt;img src="http://helpcentral.componentone.com/CS/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/5287.chartwizard_5F00_series.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That&amp;#39;s it! Click Finish and the chart display settings are complete.&lt;/p&gt;
&lt;h2&gt;Binding C1FlexGrid data to C1Chart&lt;/h2&gt;
&lt;p&gt;We will bind the &lt;strong&gt;C1FlexGrid&lt;/strong&gt; data to the &lt;strong&gt;C1Chart&lt;/strong&gt; using the SalesDataList created earlier. The SalesData class is populated from the data displayed in &lt;strong&gt;C1FlexGrid&lt;/strong&gt; on the main form. The SalesDataList class is generated as an array to be fed into the &lt;strong&gt;C1Chart&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This property below will be used for the get/set of our SalesData class. Add this property to the chart Form&amp;#39;s (Form2) code behind. &lt;/p&gt;
&lt;/p&gt;
&lt;p&gt;&lt;code&gt;
&lt;p&gt;private SalesDataList _salesDataList = null;&lt;br /&gt;public SalesDataList SalesDataList&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; get { return this._salesDataList; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; set { this._salesDataList = value; }&lt;br /&gt;}&lt;/p&gt;
&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;
&lt;p&gt;We will also need a category property for our Form2 class. This property will store the selected category on which to chart data from.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;private List&amp;lt;DictionaryEntry&amp;gt; category;&lt;br /&gt;public List&amp;lt;DictionaryEntry&amp;gt; Category&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;get { return this.category; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;set&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.category = value;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.initializeCategory();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;}&lt;/code&gt; &lt;/p&gt;
&lt;p&gt;The initializeCategory method should fill the Ribbon combobox with all possible categories.&lt;/p&gt;
&lt;p&gt;Now we need to make a few adjustments to the main form. First, add a Chart button to the ribbon, and in its click event, we need to launch a new chart form.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/2526.application2.png"&gt;&lt;img src="http://helpcentral.componentone.com/CS/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/2526.application2.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;code&gt;
&lt;p&gt;Form2 chartForm = new Form2();&lt;br /&gt;chartForm.SalesDataList = this.createDataList();&lt;br /&gt;chartForm.Category = this.category;&lt;br /&gt;chartForm.ShowDialog();&lt;/p&gt;
&lt;/code&gt;
&lt;p&gt;We create the SalesDataList pulling data from the &lt;strong&gt;C1FlexGrid&lt;/strong&gt; using this method:&lt;/p&gt;
&lt;/p&gt;
&lt;p&gt;&lt;code&gt;private SalesDataList createDataList()&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SalesDataList list = new SalesDataList();&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for (int i = 1; i &amp;lt; this.c1FlexGrid1.Rows.Count; i++)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (this.c1FlexGrid1[i, 0] != null&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;amp;&amp;amp; (this.c1FlexGrid1[i, 2] != null&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;|| this.c1FlexGrid1[i, 3] != null))&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SalesData data = new SalesData();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data.Date = DateTime.Parse(this.c1FlexGrid1[i, 0].ToString());&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data.CategoryCode = this.c1FlexGrid1[i, 1] == null ? &amp;quot;0&amp;quot; : this.c1FlexGrid1[i, 1].ToString();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data.Proceeds = this.c1FlexGrid1[i, 2] == null ? 0 : int.Parse(this.c1FlexGrid1[i, 2].ToString());&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data.Payments = this.c1FlexGrid1[i, 3] == null ? 0 : int.Parse(this.c1FlexGrid1[i, 3].ToString());&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; data.GrossMargin = this.c1FlexGrid1[i, 4] == null ? 0 : int.Parse(this.c1FlexGrid1[i, 4].ToString());&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;list.Add(data);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; return list;&lt;br /&gt;}&lt;/code&gt;
&lt;p&gt;Here, we declare a new SalesDataList and cycle through the &lt;strong&gt;C1FlexGrid&lt;/strong&gt; rows collection filling in the data (so long as it&amp;#39;s not null).&lt;/p&gt;
&lt;p&gt;Back on Form2, we set the SalesDataList as the data source of the BindingSource.&lt;/p&gt;
&lt;code&gt;
&lt;p&gt;private void setDataSource(string categoryCode)&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.bindingSource1.DataSource = this._salesDataList.SelectCategory(categoryCode);&lt;/p&gt;
&lt;/code&gt;
&lt;p&gt;Finally, we will show the data based on the category that has been selected. The following code displays only the desired category&amp;#39;s data from the data source.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;public SalesDataList SelectCategory(string code)&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; SalesDataList value = new SalesDataList();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; List&amp;lt;SalesData&amp;gt; list = this.FindAll(delegate(SalesData data)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; return (data.CategoryCode == code);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; });&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; value.AddRange(list);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; return value;&lt;br /&gt;}&lt;/code&gt;
&lt;p&gt;Now when we run the application, our chart form displays data only from the selected category.&lt;/p&gt;
&lt;/p&gt;
&lt;/p&gt;
&lt;p&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/7725.chart1.png"&gt;&lt;img src="http://helpcentral.componentone.com/CS/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/7725.chart1.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Modifying the chart type &lt;/h2&gt;
&lt;p&gt;The &lt;strong&gt;C1Chart&lt;/strong&gt; control supports all popular chart types. For this application, will be using the Bar and Line chart types.&lt;/p&gt;
&lt;p&gt;We will use some simple code with the Chart Type combobox in the Ribbon to select the chart type. We will add two possible values to the combobox: Line and Bar Graph.&lt;/p&gt;
&lt;p&gt;The Ribbon combobox&amp;#39;s SelectedIndexChanged event will capture when the user changes chart type.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;
&lt;p&gt;if (this.ribbonComboBox2.SelectedIndex == 0)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.c1Chart1.ChartGroups[0].ChartType = Chart2DTypeEnum.XYPlot;&lt;br /&gt;else&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.c1Chart1.ChartGroups[0].ChartType = Chart2DTypeEnum.Bar;&lt;/p&gt;
&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Here we are simply setting the ChartType property to in the &lt;strong&gt;C1Chart&lt;/strong&gt; control. The Chart2DTypeEnum is set to Chart2DTypeEnum.XYPlot for a line chart and Chart2DTypeEnum.Bar for a bar chart.&lt;/p&gt;
&lt;p&gt;When we select Bar Graph we will notice the chart displays this type.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/4113.chart2.png"&gt;&lt;img src="http://helpcentral.componentone.com/CS/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/4113.chart2.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;All it takes is changing one property to completely change the chart type with &lt;strong&gt;C1Chart&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;Printing C1Chart&lt;/h2&gt;
&lt;p&gt;Now that we have been able to display different types of charts, we will add the feature to print them. We just need to call a simple print method in &lt;strong&gt;C1Chart&lt;/strong&gt; control for this.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;
&lt;p&gt;this.c1Chart1.PrintChart();&lt;/p&gt;
&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;In our sample, we&amp;#39;ve added a Print button to the Miscellaneous RibbonTab in our chart form. When this button is clicked, we call the PrintChart method and the following print dialog is displayed:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/1488.printchart.png"&gt;&lt;img src="http://helpcentral.componentone.com/CS/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/1488.printchart.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;Modifying Other Chart Properties&lt;/h2&gt;
&lt;p&gt;Let&amp;#39;s extend the users options for viewing charted data in &lt;strong&gt;C1Chart&lt;/strong&gt;. The &lt;strong&gt;C1Chart&lt;/strong&gt; control also contains 3D effects and stacked charts. We will include these in the display features available on the UI. &lt;/p&gt;
&lt;p&gt;The Use3D property is set for the 3D effect feature. Similarly, the Stacked property is used for the stacked display in a chart. Set the following properties when the user checks or unchecks the 3D Effect and Stacked checkboxes we created earlier.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;
&lt;p&gt;this.c1Chart1.ChartGroups[0].Use3D = &amp;quot;true/false&amp;quot;;&lt;br /&gt;this.c1Chart1.ChartGroups[0].Stacked = &amp;quot;true/false&amp;quot;;&lt;/p&gt;
&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/5658.chart_5F00_3d.png"&gt;&lt;img src="http://helpcentral.componentone.com/CS/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/5658.chart_5F00_3d.png" alt="Chart Display (3D)" border="0" style="border:0;" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/2022.chart_5F00_stacked.png"&gt;&lt;img src="http://helpcentral.componentone.com/CS/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/2022.chart_5F00_stacked.png" alt="Chart Display (Stacked)" border="0" style="border:0;" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;As you can see, we get a completely different look for the chart just by easily setting these couple properties. &lt;/p&gt;
&lt;p&gt;Finally, let&amp;#39;s invert the X and Y axes of the chart. We just need to set the Inverted property to true for the ChartArea. &lt;/p&gt;
&lt;p&gt;&lt;code&gt;
&lt;p&gt;this.c1Chart1.ChartArea.Inverted = &amp;quot;true/false&amp;quot;;&lt;/p&gt;
&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/0882.chart_5F00_inverted.png"&gt;&lt;img src="http://helpcentral.componentone.com/CS/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/0882.chart_5F00_inverted.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In addition to changing the chart type with just one property, we can also modify other chart settings with only a few properties.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In this section we have charted data from our &lt;strong&gt;C1FlexGrid&lt;/strong&gt; control using &lt;strong&gt;C1Chart&lt;/strong&gt;. Data binding was done through some simple coding and we were able to integrate the features of each control into our application. We further modified many chart display settings using only a few simple properties. &lt;/p&gt;
&lt;p&gt;In part 5 we will use more features of &lt;strong&gt;C1Chart&lt;/strong&gt; to further improve our application by integrating it with other controls.&lt;/p&gt;
&lt;h2&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/5280.Part4.zip"&gt;Download Part 4 Sample Project&lt;/a&gt;&lt;/h2&gt;
&lt;h2&gt;Points to Remember&lt;/h2&gt;
&lt;p&gt;This application has been created in Visual Studio 2008 and uses SQL Server 2005 or higher. Installation of .NET Framework 3.5 is a prerequisite for running the sample application.&lt;/p&gt;
&lt;h2&gt;Acknowledgements&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Article copyright &amp;copy; 2009&amp;nbsp;Shogo Takayama&amp;nbsp;＆ Shoeisha Co., Ltd. http://www.shoeisha.co.jp/ &lt;/li&gt;
&lt;li&gt;&lt;a href="http://codezine.jp/"&gt;http://codezine.jp/&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;These contents were made by GrapeCity inc., Japan. &lt;a href="http://www.grapecity.com/japan/"&gt;http://www.grapecity.com/japan/&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;Translated by Vidhi Kapoor (GrapeCity India Pvt. Ltd.)&lt;/li&gt;
&lt;li&gt;Edited by Greg Lutz (ComponentOne)&lt;/li&gt;
&lt;/ul&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://helpcentral.componentone.com/CS/aggbug.aspx?PostID=228904" width="1" height="1"&gt;</content><author><name>C1_GregL</name><uri>http://helpcentral.componentone.com/CS/members/C1_5F00_GregL/default.aspx</uri></author><category term="WinForms" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/WinForms/default.aspx" /><category term="C1FlexGrid" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/C1FlexGrid/default.aspx" /><category term="C1Chart" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/C1Chart/default.aspx" /><category term="Become an Expert" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/Become+an+Expert/default.aspx" /><category term="C1Ribbon" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/C1Ribbon/default.aspx" /></entry><entry><title>Become an Expert Part 3: Adding a Ribbon Interface and Export Options</title><link rel="alternate" type="text/html" href="/CS/blogs/windev/archive/2010/02/09/BecomeAnExpertPart3.aspx" /><id>/CS/blogs/windev/archive/2010/02/09/BecomeAnExpertPart3.aspx</id><published>2010-02-09T17:14:00Z</published><updated>2010-02-09T17:14:00Z</updated><content type="html">&lt;h2&gt;Part 3 Overview&lt;/h2&gt;
&lt;p&gt;In this series, we will use controls from ComponentOne Studio for WinForms to create a Sales Management application and show how easily anyone can &amp;quot;Become an Expert&amp;quot;. Keep in mind that up until now* we have created a table to display sales data, implemented features to add/delete data and added a feature to automatically show totals. &lt;/p&gt;
&lt;p&gt;* See &lt;a target="_blank" href="http://helpcentral.componentone.com/CS/blogs/windev/archive/2010/01/29/BecomeAnExpertPart1.aspx"&gt;Part One&lt;/a&gt; and &lt;a target="_blank" href="http://helpcentral.componentone.com/CS/blogs/windev/archive/2010/02/05/BecomeAnExpertPart2.aspx"&gt;Part Two&lt;/a&gt; before continuing.&lt;/p&gt;
&lt;p&gt;In Part 3, we&amp;#39;ll use the &lt;b&gt;C1Ribbon&lt;/b&gt; control (part of Studio for WinForms) to change the look and feel of the application. We&amp;#39;ll also use the &lt;b&gt;C1PdfDocument&lt;/b&gt; control to export a report to a PDF file, and we&amp;#39;ll also add Excel export options using the &lt;b&gt;C1FlexGrid&lt;/b&gt; control.&lt;/p&gt;
&lt;p&gt;So let&amp;#39;s get started!&lt;/p&gt;
&lt;h2&gt;Implementing the Ribbon Interface 1&lt;/h2&gt;
&lt;h3&gt;Modify the menu using the C1Ribbon control:&lt;/h3&gt;
&lt;p&gt;Up until now we&amp;#39;ve used basic Windows controls in the Sales Management Application menu and form itself. To enhance the look and feel of the application, we&amp;#39;ll incorporate the Ribbon interface using the&amp;nbsp;&lt;b&gt;C1Ribbon&lt;/b&gt; control.&lt;/p&gt;
&lt;p&gt;In order to use the &lt;b&gt;C1Ribbon&lt;/b&gt; control, we need to add it to the toolbox. To add the control, right click on the toolbox and open &amp;quot;Choose Toolbox Items&amp;quot;.&lt;/p&gt;
&lt;p style="padding-left:30px;"&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/1663.ribbon_5F00_toolbox.png"&gt;&lt;img src="http://helpcentral.componentone.com/CS/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/1663.ribbon_5F00_toolbox.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Please refer to the previous articles for details regarding this topic. &lt;/p&gt;
&lt;h2&gt;Modify preview&lt;/h2&gt;
&lt;p&gt;Let&amp;#39;s begin by adding the &lt;b&gt;C1Ribbon&lt;/b&gt; control to the display area of the form. It looks very different from MenuStrip. &lt;b&gt;C1Ribbon&lt;/b&gt; provides a tabbed interface using menu items which are divided into tabs and groups for better visual effect. In this application we will divide our items into two tabs: one for basic functionality and another for file exporting features.&lt;/p&gt;
&lt;p&gt;We have a C1RibbonForm class which is used to get a form with &lt;b&gt;C1Ribbon&lt;/b&gt; control on it. So let&amp;#39;s shift from the common Form class to the C1RibbonForm class. Use the following code to modify the base Form class (parent class).&lt;/p&gt;
&lt;h3&gt;Before modification&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;public partial class Form1 : Form&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;After modification&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;using C1.Win.C1Ribbon; &amp;nbsp;// Add&lt;br /&gt;：&lt;br /&gt;：（Omitted）&lt;br /&gt;：&lt;br /&gt;public partial class Form1 : C1RibbonForm&lt;/code&gt;&lt;/p&gt;
&lt;p style="padding-left:30px;"&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/6661.ribbon_5F00_form.png"&gt;&lt;img src="http://helpcentral.componentone.com/CS/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/6661.ribbon_5F00_form.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The&amp;nbsp;form has changed completely! We&amp;#39;ll use this as a base and add buttons and textboxes to the &lt;b&gt;C1Ribbon&lt;/b&gt; control to create the menu.&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;Implementing the Ribbon Interface 2&lt;/h2&gt;
&lt;h3&gt;Add Tabs and Groups:&lt;/h3&gt;
&lt;p&gt;Now we will add tabs and groups to the Ribbon. To add tabs, select the Application Menu (round section) from the top left of the screen and click &amp;quot;Actions&amp;quot; and then &amp;quot;Add Tab&amp;quot;.&lt;/p&gt;
&lt;p style="padding-left:30px;"&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/6153.ribbon_5F00_addtab.png"&gt;&lt;img src="http://helpcentral.componentone.com/CS/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/6153.ribbon_5F00_addtab.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;For changing the text of the tab, select the tab to display its context-sensitive toolbar and click on &amp;quot;Text Settings&amp;quot;. Text settings and tooltip settings can be done from this popup window.&lt;/p&gt;
&lt;p style="padding-left:30px;"&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/4377.ribbon_5F00_edittext.png"&gt;&lt;img src="http://helpcentral.componentone.com/CS/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/4377.ribbon_5F00_edittext.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;You can also modify Text settings and add more groups in the same way. Similarly, controls can also be added to groups. Select the default Group and drop-down the actions menu. Select to add a Button.&lt;/p&gt;
&lt;p&gt;Now, let&amp;#39;s change the icons. To change the Button&amp;#39;s icon, click on &amp;quot;Change Image&amp;quot; from its toolbar.&lt;/p&gt;
&lt;p style="padding-left:30px;"&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/0842.ribbon_5F00_editimage.png"&gt;&lt;img src="http://helpcentral.componentone.com/CS/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/0842.ribbon_5F00_editimage.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;From the Change Image pop-up, select the icon to be changed. Modify &amp;quot;Large Image&amp;quot; for large icons and &amp;quot;Small Image&amp;quot; for small icons. Depending on whether or not you select a large image will determine if the button is large or small. Even if you want a large button, it&amp;#39;s also recommended to choose a small image for use in the Quick Access Toolbar.&lt;/p&gt;
&lt;p style="padding-left:30px;"&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/2110.ribbon_5F00_selectimage.png"&gt;&lt;img src="http://helpcentral.componentone.com/CS/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/2110.ribbon_5F00_selectimage.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Try changing icons for all the controls in the same way. Continue to fill out the Ribbon with the following tabs and groups. Some controls are for features we have already implemented and some are for the new features we plan to implement. For PDF export we will have to add a separate &lt;b&gt;C1PdfDocument&lt;/b&gt; component. &lt;b&gt;Note:&lt;/b&gt; details about this feature will be covered later.&lt;/p&gt;
&lt;p style="padding-left:30px;"&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/6710.ribbon_5F00_hometab.png"&gt;&lt;img src="http://helpcentral.componentone.com/CS/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/6710.ribbon_5F00_hometab.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Home Tab&lt;/h3&gt;
&lt;p&gt;DB&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Read&lt;/li&gt;
&lt;li&gt;Write&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Data Manipulation&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add Data&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Print&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Print&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;PDF&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PDF Output&lt;/li&gt;
&lt;li&gt;Include Password&lt;/li&gt;
&lt;li&gt;Password&lt;/li&gt;
&lt;/ul&gt;
&lt;p style="padding-left:30px;"&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/5305.ribbon_5F00_filetab.png"&gt;&lt;img src="http://helpcentral.componentone.com/CS/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/5305.ribbon_5F00_filetab.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;File Tab&lt;/h3&gt;
&lt;p&gt;Excel&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Output&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;XML&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Export&lt;/li&gt;
&lt;li&gt;Import&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/5305.ribbon_5F00_filetab.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Adding Excel Export&lt;/h2&gt;
&lt;p&gt;Now that we have modified the appearance, let&amp;#39;s add new features. Usually in business applications, users want data to be exported to a file. &lt;/p&gt;
&lt;p&gt;Let&amp;#39;s first add a feature for exporting our data to Excel. First, add a RibbonButton event. This can be done by simply double clicking on the RibbonButton, just like a usual button control. We will use the &amp;quot;Output&amp;quot; button in the &amp;quot;Excel&amp;quot; group of the file tab created earlier.&lt;/p&gt;
&lt;p&gt;To accomplish the Excel exporting we&amp;#39;ll simply use C1FlexGrid&amp;#39;s export feature. Use the following code to export C1FlexGrid data to an Excel file format.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;this.c1FlexGrid1.SaveExcel(&amp;quot;File Name&amp;quot;, &amp;quot;Sheet Name&amp;quot;, &amp;quot;Output Options&amp;quot;);&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Data from &lt;b&gt;C1FlexGrid&lt;/b&gt; will be displayed in the specified sheet. We have fixed rows (header rows) and fixed columns (header columns) in the Sales Management Application. Specify FileFlags.IncludeFixedCells in the output option to get fixed cells in the Excel file. &lt;/p&gt;
&lt;p&gt;If you open the exported Excel file, you&amp;#39;ll notice the window outline is fixed for the sections with fixed cells. Also, our cell styles are respected in the Excel format, but our custom OwnerDraw progress bars are not.&lt;/p&gt;
&lt;p style="padding-left:30px;"&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/4087.flexdata_5F00_excel.png"&gt;&lt;img src="http://helpcentral.componentone.com/CS/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/4087.flexdata_5F00_excel.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Adding XML Export &lt;/h2&gt;
&lt;p&gt;In addition to Excel export, we can also export C1FlexGrid data to XML. Exporting to Excel limits reusability from the customer&amp;#39;s end because it&amp;#39;s restricted to the software being used. By exporting to XML we can load and export data in a format more native to the FlexGrid. We will export C1FlexGrid data to an XML file and then read data from this file.&lt;/p&gt;
&lt;p&gt;We&amp;#39;ll use the &amp;quot;Export&amp;quot; button in the XML group. Use the following code to export C1FlexGrid data to an XML file format.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;this.c1FlexGrid1.WriteXml(&amp;quot;File Name&amp;quot;);&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Just write one line of code and that&amp;#39;s it! &lt;/p&gt;
&lt;h3&gt;Data exported to XML&lt;/h3&gt;
&lt;p style="padding-left:30px;"&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/5270.flexgrid_5F00_xml.gif"&gt;&lt;img src="http://helpcentral.componentone.com/CS/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/5270.flexgrid_5F00_xml.gif" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Let&amp;#39;s read the data from the XML file back to the grid. Execute the following code to load the XML file and paste the data back to &lt;b&gt;C1FlexGrid&lt;/b&gt;. Use the &amp;quot;Import&amp;quot; button of XML group in the Ribbon.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;this.c1FlexGrid1.ReadXml(&amp;quot;File Name&amp;quot;);&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;And just like file exporting, we only need one line of code here too. Background color has also been read back with the data! So when data is exported to XML, font, background color, and other similar information is also exported to the file.&lt;/p&gt;
&lt;h2&gt;Adding PDF Export&lt;/h2&gt;
&lt;p&gt;Finally, since we are creating a Sales Management application, let&amp;#39;s try adding a reporting feature. We will use &lt;b&gt;C1PdfDocument&lt;/b&gt; component to create a PDF file. &lt;b&gt;C1PdfDocument &lt;/b&gt;(part of Studio for WinForms) is used for generating PDF documents.&lt;/p&gt;
&lt;p style="padding-left:30px;"&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/6866.pdf_5F00_toolbox.png"&gt;&lt;img src="http://helpcentral.componentone.com/CS/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/6866.pdf_5F00_toolbox.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;So let&amp;#39;s start creating the PDF file! Add the &lt;b&gt;C1PdfDocument&lt;/b&gt; component from the toolbox, just like you did for &lt;b&gt;C1Ribbon&lt;/b&gt;. Then add the click event of the &amp;quot;PDF Output&amp;quot; button under the PDF group in the Home tab. &lt;/p&gt;
&lt;p&gt;Now let&amp;#39;s add some code.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;this.c1PdfDocument1.Clear();&lt;br /&gt;this.c1PdfDocument1.DocumentInfo.Title = &amp;quot;Title&amp;quot;;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This code initializes the C1PdfDocument component and sets the PDF file title. The title can be verified in the PDF file properties. &lt;/p&gt;
&lt;p&gt;&lt;code&gt;RectangleF rcPage = this.c1PdfDocument1.PageRectangle;&lt;br /&gt;rcPage.Inflate(-100, -100);&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Here we get the page area and determine the display area of the page by setting the top, bottom, left, right margins. &lt;/p&gt;
&lt;p&gt;Next we&amp;#39;ll set the title (heading) style of the report.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;// Get and set the height.&lt;br /&gt;rc.Height = this.c1PdfDocument1.MeasureString(&amp;quot;Title (Heading)&amp;quot;, &amp;quot;Font&amp;quot;, rc.Width).Height;&lt;/code&gt; &lt;code&gt;// Draw title.&lt;br /&gt;this.c1PdfDocument1.DrawString(&amp;quot;Title (Heading)&amp;quot;, &amp;quot;Font&amp;quot;, Brushes.Black, &amp;quot;Drawing area&amp;quot;);&lt;/code&gt; &lt;code&gt;// Adjust the drawing location.&lt;br /&gt;rc.Offset(0, rc.Height + 5); &lt;/code&gt;&lt;/p&gt;
&lt;p&gt;The code above set the height of the drawing area using the MeasureString method. We then draw the title using the DrawString method. Then, modify Brushes.Black to change the color of the text. And finally, adjust the drawing location by an amount equal to the height of the title plus the row padding.&lt;/p&gt;
&lt;p&gt;The following code will draw the header section. The basic sequence will be the same as drawing the title; however, in this case it will be for multiple columns, so the horizontal width needs to be calculated. (In our application, we are simply dividing the width by the number of columns. Also, for setting the height of the drawing area, we are calculating based on the height of the tallest cell).&lt;/p&gt;
&lt;p&gt;&lt;code&gt;// Measure cell width&lt;br /&gt;rcCell.Width = rc.Width / fields.Length;&lt;br /&gt;&lt;br /&gt;// Get the height. Select the highest one and set it.&lt;br /&gt;float height = this.c1PdfDocument1.MeasureString(&amp;quot;Header String&amp;quot;, &amp;quot;Font&amp;quot;, rcCell.Width).Height;&lt;br /&gt;rcCell.Height = Math.Max(rcCell.Height, height);&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Next, fill the drawing area and draw the header string. Adjust the position of the drawing area and our drawing of the header is complete. &lt;/p&gt;
&lt;p&gt;&lt;code&gt;// Fill the cell.&lt;br /&gt;this.c1PdfDocument1.FillRectangle(Brushes.Black, rcCell);&lt;/code&gt; &lt;code&gt;// Draw header.&lt;br /&gt;this.c1PdfDocument1.DrawString(&amp;quot;Header String&amp;quot;, &amp;quot;Font&amp;quot;, Brushes.White, rcCell);&lt;/code&gt; &lt;code&gt;// Position is set by adjusting the horizontal width only.&lt;br /&gt;rcCell.Offset(rcCell.Width, 0);&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Finally, we will draw the data section. Drawing the data section is similar to the header section. It contains numeric values, strings and date values. The numeric values will be left aligned and all other data types will be right aligned. &lt;/p&gt;
&lt;p&gt;&lt;code&gt;StringFormat sf = new StringFormat();&lt;br /&gt;// Check if the value can be converted to double.&lt;br /&gt;double d;&lt;br /&gt;sf.Alignment = (double.TryParse(&amp;quot;Data&amp;quot;, out d)) ? StringAlignment.Far :StringAlignment.Near;&lt;/code&gt; &lt;code&gt;// Draw the data in the cell.&lt;br /&gt;this.c1PdfDocument1.DrawString(&amp;quot;Data&amp;quot;, &amp;quot;Font&amp;quot;, Brushes.Black, rcCell, sf);&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;We are also checking if the numeric data type or any other data type can be converted to double to avoid conversion errors. For numeric data, we set StringAlignment to Far, and for other data types set it to Near. Then draw the data string by specifying the string format at runtime while using the DrawString method. Finally, once the file is saved using the Save method of C1PdfDocument, our process is complete. Page breaks have also been implemented in this sample application.&lt;/p&gt;
&lt;p style="padding-left:30px;"&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/3225.flexdata_5F00_pdf.png"&gt;&lt;img src="http://helpcentral.componentone.com/CS/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/3225.flexdata_5F00_pdf.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Setting Password in the PDF File&lt;/h2&gt;
&lt;p&gt;The generated PDF file now contains sales data. This might be confidential information since this is a business application, so let&amp;#39;s set a password for the PDF file. This password will be required to open and view the PDF file. &lt;/p&gt;
&lt;p&gt;Check the checkbox &amp;quot;Include Password&amp;quot; in the PDF group of Home tab. The following code is implemented when you click on &amp;quot;PDF Output&amp;quot; after entering a string in the &amp;quot;Password&amp;quot; textbox. &lt;/p&gt;
&lt;p&gt;&lt;code&gt;this.c1PdfDocument1.Security.UserPassword = &amp;quot;Password String&amp;quot;;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Here we just need to use the UserPassword property of the PdfSecurity class to set the password. The security of the PDF file is enhanced this way so the users who do not have the password will not be able to open the PDF file. &lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In this part we modified the look and feel of the application with the help of the &lt;b&gt;C1Ribbon&lt;/b&gt; control. We implemented features like Excel export and XML export and import. We also created a PDF file and set a password for it using the &lt;b&gt;C1PdfDocument &lt;/b&gt;component. By making a few code changes to the controls we were able to enhance the features in the application immensely. &lt;/p&gt;
&lt;p&gt;In &lt;a href="http://helpcentral.componentone.com/CS/blogs/windev/archive/2010/02/17/BecomeAnExpertPart4.aspx"&gt;Part 4&lt;/a&gt;, we&amp;#39;ll add some more interesting features to the application by binding sales data to a chart control and displaying the data in &lt;b&gt;C1FlexGrid &lt;/b&gt;in the form of a chart.&lt;/p&gt;
&lt;h2&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/0268.Part3.zip"&gt;Download Part&amp;nbsp;3&amp;nbsp;Sample Project&lt;/a&gt;&lt;/h2&gt;
&lt;h2&gt;Acknowledgements&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Article copyright &amp;copy; 2009&amp;nbsp;Shogo Takayama&amp;nbsp;＆ Shoeisha Co., Ltd. http://www.shoeisha.co.jp/ &lt;/li&gt;
&lt;li&gt;&lt;a href="http://codezine.jp/"&gt;http://codezine.jp/&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;These contents were made by GrapeCity inc., Japan. &lt;a href="http://www.grapecity.com/japan/"&gt;http://www.grapecity.com/japan/&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;Translated by Vidhi Kapoor (GrapeCity India Pvt. Ltd.)&lt;/li&gt;
&lt;li&gt;Edited by Greg Lutz (ComponentOne)&lt;/li&gt;
&lt;/ul&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://helpcentral.componentone.com/CS/aggbug.aspx?PostID=228544" width="1" height="1"&gt;</content><author><name>C1_GregL</name><uri>http://helpcentral.componentone.com/CS/members/C1_5F00_GregL/default.aspx</uri></author><category term="WinForms" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/WinForms/default.aspx" /><category term="C1FlexGrid" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/C1FlexGrid/default.aspx" /><category term="Become an Expert" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/Become+an+Expert/default.aspx" /><category term="C1Pdf" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/C1Pdf/default.aspx" /><category term="C1Ribbon" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/C1Ribbon/default.aspx" /><category term="Excel" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/Excel/default.aspx" /></entry><entry><title>Become an Expert Part 2: Adding Features to the Sales Management Application</title><link rel="alternate" type="text/html" href="/CS/blogs/windev/archive/2010/02/05/BecomeAnExpertPart2.aspx" /><id>/CS/blogs/windev/archive/2010/02/05/BecomeAnExpertPart2.aspx</id><published>2010-02-05T20:46:00Z</published><updated>2010-02-05T20:46:00Z</updated><content type="html">&lt;h2&gt;Part 2 Overview&lt;/h2&gt;
&lt;p&gt;Microsoft Visual Studio contains a number of standard controls. ComponentOne Studio Enterprise provides easy-to-use controls with enhanced features which can be used in Visual Studio along with standard controls. In this series, we make use of these controls to create a Sales Management Application. In &lt;a target="_blank" href="http://helpcentral.componentone.com/CS/blogs/windev/archive/2010/01/29/BecomeAnExpertPart1.aspx"&gt;Part 1&lt;/a&gt;; we started building a simple application using &lt;b&gt;C1FlexGrid&lt;/b&gt; to display and edit sales data. In this part, we&amp;#39;ll power up our application by using features provided by &lt;b&gt;C1FlexGrid&lt;/b&gt;.&lt;/p&gt;
&lt;h2&gt;Add Product Category&lt;/h2&gt;
&lt;p&gt;You may remember that in the previous section, &amp;quot;Proceeds&amp;quot; and &amp;quot;Payments&amp;quot; were added to the Sales Management Application based on dates. A real life organization, however, sells a number of products and might want to look at aggregate sales figures for each product category. For this, a category item needs to be added so that not just dates but category wise sales figures can also be represented.&lt;/p&gt;
&lt;p&gt;First, let&amp;#39;s add a column &amp;quot;Product Category&amp;quot; in the grid. Open the &amp;quot;C1FlexGrid Column Editor&amp;quot; by selecting Designer... from the C1FlexGrid&amp;#39;s smart tag to add/delete rows at any location in the grid. We will put this new column between the &amp;quot;Date&amp;quot; and &amp;quot;Proceeds&amp;quot; columns. Name it ProductCategory and set AllowEditing to False.&lt;/p&gt;
&lt;p style="padding-left:30px;"&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/flexgrid_5F00_columns.png"&gt;&lt;img src="http://helpcentral.componentone.com/CS/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/flexgrid_5F00_columns.png" alt="Setting the Value of Product Category" border="0" style="border:0;" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;We have a separate CategoryMaster table where the data values are translated into display values using DataMap (use CategoryMaster script included in the sample zip to create the CategoryMaster table, and the Sales script to recreate the Sales table adding a CategoryCode data field). Here the CategoryCode value is specified and the corresponding CategoryName is displayed on the grid. Values in &lt;b&gt;C1FlexGrid&lt;/b&gt; have been made non-editable in our application, but DataMaps can also be used where editing is possible in the grid and values are entered in a drop-down list.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ListDictionary dictionary = new ListDictionary();&lt;br /&gt;dictionary.Add(&amp;quot;CategoryCode&amp;quot;, &amp;quot;CategoryName&amp;quot;);&lt;br /&gt;this.c1FlexGrid1.Cols[1].DataType = typeof(int);&lt;br /&gt;this.c1FlexGrid1.Cols[1].DataMap = dictionary;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;CategoryCode is added as the key and CategoryName is added as the display value in the ListDictionary. With this, we have added a &amp;quot;ProductCategory&amp;quot; column in the grid and completed the settings for the DataMap.&lt;/p&gt;
&lt;h2&gt;Add a Window to Enter Data&lt;/h2&gt;
&lt;p&gt;Next, let&amp;#39;s change the way we enter data in the grid. In &lt;a target="_blank" href="http://helpcentral.componentone.com/CS/blogs/windev/archive/2010/01/29/BecomeAnExpertPart1.aspx"&gt;Part 1&lt;/a&gt;, we added/modified data directly through &lt;b&gt;C1FlexGrid&lt;/b&gt;, but this is very inconvenient in a real application. So we&amp;#39;ll create a new input window to enter data. When data is to be entered, a new window will pop up and for simple data input.&lt;/p&gt;
&lt;p style="padding-left:30px;"&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/input_5F00_form.png"&gt;&lt;img src="http://helpcentral.componentone.com/CS/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/input_5F00_form.png" alt="Pop up window to enter sales data" border="0" style="border:0;" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;First, we will add a Form to the project. This form will contain controls where data related to &amp;quot;Date&amp;quot;, &amp;quot;Category&amp;quot;, &amp;quot;Proceeds&amp;quot; and &amp;quot;Payments&amp;quot; can be entered. &lt;/p&gt;
&lt;p&gt;&lt;code&gt;public object CategoryCode&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; get { return this.comboBox1.SelectedValue; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; set { this.comboBox1.SelectedValue = value; }&lt;br /&gt;}&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Use the code below to get and set the entered value in the main window.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When new data is entered&lt;/li&gt;
&lt;/ul&gt;
&lt;p style="padding-left:60px;"&gt;&lt;code&gt;this.c1FlexGrid1.AddItem(new object[] { &amp;quot;Date&amp;quot;,&lt;br /&gt;　　　　　　　　　　　　&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　&amp;quot;CategoryCode&amp;quot;,&lt;br /&gt;　　　　　　　　　　　　　&amp;nbsp;&amp;nbsp;&amp;nbsp; 　&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　&amp;quot;Proceeds&amp;quot;, &lt;br /&gt;　　　　　　　　　　　　　　&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;quot;Payment&amp;quot; });&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When existing data is updated&lt;/li&gt;
&lt;/ul&gt;
&lt;p style="padding-left:60px;"&gt;&lt;code&gt;this.c1FlexGrid1[&amp;quot;rowNum&amp;quot;, 0] = &amp;quot;Date&amp;quot;;&lt;br /&gt;this.c1FlexGrid1[&amp;quot;rowNum &amp;quot;, 1] = &amp;quot;CategoryCode&amp;quot;;&lt;br /&gt;this.c1FlexGrid1[&amp;quot;rowNum &amp;quot;, 2] = &amp;quot;Proceeds&amp;quot;;&lt;br /&gt;this.c1FlexGrid1[&amp;quot;rowNum &amp;quot;, 3] = &amp;quot;Payments&amp;quot;;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;When the popup window is closed, the value entered will be retrieved automatically and reflected in &lt;b&gt;C1FlexGrid&lt;/b&gt;. Numeric values can also be entered easily through this window.&lt;/p&gt;
&lt;h2&gt;Adding Subtotals/Grand Total&lt;/h2&gt;
&lt;p&gt;Up untill now we have been making changes at the record level, but in a Sales Management Application we would need information about total sales. So let&amp;#39;s add a totals row to calculate sales for a month. If there are multiple data rows for the same date, we&amp;#39;ll calculate subtotals for that date also.&lt;/p&gt;
&lt;p&gt;In order to add Subtotals and Grand Total rows, we need to simply add up the values of each column and add a new row to display the result. But here we&amp;#39;ll use the data aggregation feature in &lt;b&gt;C1FlexGrid &lt;/b&gt;to display values.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use the Subtotal method to calculate aggregate value&lt;/li&gt;
&lt;/ul&gt;
&lt;p style="padding-left:60px;"&gt;&lt;code&gt;// Clear subtotal.&lt;br /&gt;this.c1FlexGrid1.Subtotal(AggregateEnum.Clear);&lt;br /&gt;// Sort the contents of the grid.&lt;br /&gt;this.c1FlexGrid1.Sort(SortFlags.Ascending, 0, 1);&lt;br /&gt;// Calculate the grandtotal.&lt;br /&gt;this.c1FlexGrid1.Subtotal(AggregateEnum.Sum, -1, -1, 2, &amp;quot;Total&amp;quot;);&lt;br /&gt;// Calculate the subtotal.&lt;br /&gt;this.c1FlexGrid1.Subtotal(AggregateEnum.Sum, 0, 0, 2, &amp;quot;{0:MM/dd} Subtotals&amp;quot;);&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;First let&amp;#39;s remove the existing aggregate row. The data in the grid is sorted on two fields, first being date and then classified as per the product category. The first parameter in the SubTotal method, used to calculate the total and subtotal values, is the aggregate function type (function type is Sum in this case). Parameters 2 ~ 4 are outline level, grouping columns and aggregate columns. Instead of a specific column, -1 is used as the grouping column of the GrandTotal row. For subtotals column, it is set to 0 (Date column). The aggregate column in both cases (GrandTotal and Subtotal) is column 2 (Proceeds) in the code above. Finally, set &amp;quot;Total&amp;quot; in the caption of the totals row and a date placeholder in the caption of the subtotals row. That&amp;#39;s all we need to do to set aggregate rows in C1FlexGrid!&lt;/p&gt;
&lt;p&gt;For the GrandTotal row we will specify the average of the Gross Margin Rage column. Please note that the Gross Margin Rate of the SubTotals row is not calculated. &lt;/p&gt;
&lt;p&gt;Let&amp;#39;s modify the display style for aggregate rows so that they are highlighted in the grid.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;CellStyle cellStyle = c1FlexGrid1.Styles[CellStyleEnum.GrandTotal];&lt;br /&gt;cellStyle.BackColor = Color.Blue;&lt;br /&gt;cellStyle.ForeColor = Color.White;&lt;br /&gt;cellStyle.Font = new Font(Font, FontStyle.Bold);&lt;br /&gt;cellStyle = this.c1FlexGrid1.Styles[CellStyleEnum.Subtotal0];&lt;br /&gt;cellStyle.BackColor = Color.Orchid;&lt;br /&gt;cellStyle.ForeColor = Color.White;&lt;br /&gt;cellStyle.Font = new Font(Font, FontStyle.Bold);&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Here, we specify the cell style to change the display. In this application we are setting the cell background of the totals row to blue and the values to white. These are in bold. Cell styles for subtotals are also set in the same way.&lt;/p&gt;
&lt;p&gt;Now let&amp;#39;s run the application. Notice the Totals row at the bottom of &lt;b&gt;C1FlexGrid&lt;/b&gt;. Values for a particular date will be displayed in the Subtotals rows. We&amp;#39;ve now demonstrated how it&amp;#39;s simple to add aggregate rows to the grid by using C1FlexGrid methods.&lt;/p&gt;
&lt;h2&gt;Visual Display for Gross Margin Rate&lt;/h2&gt;
&lt;p&gt;Let&amp;#39;s make some more changes. Just like the aggregate rows, we&amp;#39;ll change the color of the GrossMarginRate column. We want to show visually where the GrossMarginRate is higher and fill the cell proportionally according to the value in the gross margin rate, sort of like a Progress Bar. &lt;/p&gt;
&lt;p&gt;Let&amp;#39;s change the DrawMode property of &lt;b&gt;C1FlexGrid&lt;/b&gt; from Normal to OwnerDraw. Now the OwnerDrawCell event will be fired when the cell is rendered.&lt;/p&gt;
&lt;p&gt;The code for the OwnerDrawCell event is as follows:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;// Calculate horizontal width.&lt;br /&gt;Rectangle rc = this.c1FlexGrid1.GetCellRect(e.Row, &amp;quot;Column Index&amp;quot;);&lt;br /&gt;rc.Width = (int)(this.c1FlexGrid1.Cols[e.Col].WidthDisplay * &amp;quot;Gross Margin Rate&amp;quot;);&lt;br /&gt;// Draw the background of the cell.&lt;br /&gt;e.DrawCell(DrawCellFlags.Background | DrawCellFlags.Border);&lt;br /&gt;// Display bar graph.&lt;br /&gt;rc.Inflate(-2, -2);&lt;br /&gt;e.Graphics.FillRectangle(Brushes.LimeGreen, rc);&lt;br /&gt;rc.Inflate(-1, -1);&lt;br /&gt;e.Graphics.FillRectangle(Brushes.PaleGreen, rc);&lt;br /&gt;// Draw the contents of the cell.&lt;br /&gt;e.DrawCell(DrawCellFlags.Content);&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;First, we get the size of the cell (cell that displays the GrossMarginRate value). Then, calculate the horizontal width of the cell proportional to the actual Gross Margin Rate. Render the background, border, and so on based on the calculated value and set the colors for border and the fill color of the rectangle to display a horizontal bar graph. Finally, display the cell value (GrossMarginRate).&lt;/p&gt;
&lt;p style="padding-left:30px;"&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/application1.png"&gt;&lt;img src="http://helpcentral.componentone.com/CS/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/application1.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Set the placeholders &amp;quot;{0}&amp;quot; and &amp;quot;{1}&amp;quot; to display page number and total page numbers respectively.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In Part 2, we used the &lt;b&gt;C1FlexGrid&lt;/b&gt; features to add aggregate rows, draw cells, and print the grid data. All this was done by simply calling a few methods. In the &lt;a target="_blank" href="http://helpcentral.componentone.com/CS/blogs/windev/archive/2010/02/09/BecomeAnExpertPart3.aspx"&gt;Part 3&lt;/a&gt;, we&amp;#39;ll enhance our application&amp;#39;s capabilities further by adding a few more controls to it along with &lt;b&gt;C1FlexGrid&lt;/b&gt; and exporting data from the grid to a PDF file.&lt;/p&gt;
&lt;h2&gt;&lt;a href="http://helpcentral.componentone.com/CS/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/windev.BecomeAnExpert/8664.Part2.zip"&gt;Download Sample Project&lt;/a&gt;&lt;/h2&gt;
&lt;h2&gt;Acknowledgements&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Article copyright &amp;copy; 2009&amp;nbsp;Shogo Takayama&amp;nbsp;＆ Shoeisha Co., Ltd. http://www.shoeisha.co.jp/ &lt;/li&gt;
&lt;li&gt;&lt;a href="http://codezine.jp/"&gt;http://codezine.jp/&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;These contents were made by GrapeCity inc., Japan. &lt;a href="http://www.grapecity.com/japan/"&gt;http://www.grapecity.com/japan/&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;Translated by Vidhi Kapoor (GrapeCity India Pvt. Ltd.)&lt;/li&gt;
&lt;li&gt;Edited by Greg Lutz (ComponentOne)&lt;/li&gt;
&lt;/ul&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://helpcentral.componentone.com/CS/aggbug.aspx?PostID=228434" width="1" height="1"&gt;</content><author><name>C1_GregL</name><uri>http://helpcentral.componentone.com/CS/members/C1_5F00_GregL/default.aspx</uri></author><category term="WinForms" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/WinForms/default.aspx" /><category term="C1FlexGrid" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/C1FlexGrid/default.aspx" /><category term="Become an Expert" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/Become+an+Expert/default.aspx" /></entry><entry><title>Become an Expert Part 1: Create a Sales Management Application using ComponentOne Studio for WinForms</title><link rel="alternate" type="text/html" href="/CS/blogs/windev/archive/2010/01/29/BecomeAnExpertPart1.aspx" /><id>/CS/blogs/windev/archive/2010/01/29/BecomeAnExpertPart1.aspx</id><published>2010-01-29T20:00:00Z</published><updated>2010-01-29T20:00:00Z</updated><content type="html">&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Visual Studio provides a number of standard controls, but every so often you may wish you could do something more. Most of us developers are aware that trying something complicated with standard controls means a lot of time will be spent on coding.&lt;/p&gt;
&lt;p&gt;ComponentOne Studio for WinForms (hereafter Studio for WinForms) offers a number of useful components that implement enhanced functionalities which are not included in standard controls. These are useful as standalone components, but with a little bit of effort, one can combine the features of a number of these components and create amazing applications in minutes.&lt;/p&gt;
&lt;p&gt;In this series, we will make use of a Sales Management application to show how easily you can &amp;quot;Become an Expert&amp;quot;. The first step in this application is creating a table to display sales data. The basic structure of this application will be created using the &lt;b&gt;C1FlexGrid&lt;/b&gt; control (included in Studio for WinForms).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Part 1: Create a Sales Management Application using ComponentOne Studio for WinForms&lt;/li&gt;
&lt;li&gt;Part 2: &lt;a href="http://helpcentral.componentone.com/CS/blogs/windev/archive/2010/02/05/BecomeAnExpertPart2.aspx"&gt;Adding Features to the Sales Management Application&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Part 3: &lt;a href="http://helpcentral.componentone.com/CS/blogs/windev/archive/2010/02/09/BecomeAnExpertPart3.aspx"&gt;Adding a Ribbon Interface&amp;nbsp;and Export Options&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Part 4: &lt;a href="http://helpcentral.componentone.com/CS/blogs/windev/archive/2010/02/17/BecomeAnExpertPart4.aspx"&gt;Adding Charts to Analyze Sales Data&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Part 5: &lt;a href="http://helpcentral.componentone.com/CS/blogs/windev/archive/2010/03/08/BecomeAnExpertPart5.aspx"&gt;Generating Reports&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Part 1 Overview&lt;/h2&gt;
&lt;p&gt;Microsoft Visual Studio contains a number of standard controls. ComponentOne Studio provides easy to use controls with enhanced features which can be used in Visual Studio along with standard controls. In this series, we make use of these controls to create a Sales Management Application. In the first&amp;nbsp;part of this series, we create a table to display sales data using &lt;b&gt;C1FlexGrid&lt;/b&gt; (a control in ComponentOne Studio for WinForms) as the base.&lt;/p&gt;
&lt;h3&gt;Target Audience&lt;/h3&gt;
&lt;ul style="MARGIN-TOP:0in;"&gt;
&lt;li&gt;Those who have created or want to create Windows applications in .NET.&lt;/li&gt;
&lt;li&gt;Those who find standard controls somewhat limited in their usage.&lt;/li&gt;
&lt;li&gt;Those who have some preliminary knowledge of databases.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Environment&lt;/h3&gt;
&lt;ul style="MARGIN-TOP:0in;"&gt;
&lt;li&gt;Visual Studio 2005 or 2008&lt;/li&gt;
&lt;li&gt;SQL Server 2005&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Set-up the Application&lt;/h2&gt;
&lt;p&gt;Before you start, you&amp;rsquo;ll need to have ComponentOne Studio for WinForms downloaded and installed. This includes the &lt;b&gt;C1FlexGrid&lt;/b&gt; control.&lt;/p&gt;
&lt;p&gt;&lt;i&gt;Points to Remember: This application has been created in Visual Studio 2008. Installation of .NET Framework 3.5 is a prerequisite for running the provided sample. We will also be creating a SQL Server database, however you can skip over this section to create an unbound application.&lt;/i&gt;&lt;/p&gt;
&lt;ol style="MARGIN-TOP:0in;"&gt;
&lt;li&gt;Download Studio for WinForms at &lt;a href="http://www.componentone.com/SuperProducts/StudioWinForms/"&gt;http://www.componentone.com/SuperProducts/StudioWinForms/&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Create a WinForms application in Microsoft Visual Studio. Open Microsoft Visual Studio 2008, and select &amp;quot;File | New | Project&amp;quot; from the main menu. For this article, select C# as the language and select .NET Framework 2.0. Name the project &amp;quot;Sales Management App&amp;quot; in the Name field and click the OK button.&lt;b style="mso-bidi-font-weight:normal;"&gt; &lt;br /&gt;&lt;br /&gt;Note:&lt;/b&gt; &lt;b&gt;C1FlexGrid&lt;/b&gt; is supported under the 2.0 or 3.x Frameworks and can be used in either Visual Studio 2005 or 2008 environments. You can choose the Framework and the environment. The code in this tutorial will be in C#.&lt;/li&gt;
&lt;li&gt;Drag and drop &lt;b style="mso-bidi-font-weight:normal;"&gt;C1FlexGrid&lt;/b&gt; from your Toolbox onto your page.&lt;span style="font-family:Verdana;"&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Drag and drop a &lt;b style="mso-bidi-font-weight:normal;"&gt;DateTimePicker&lt;/b&gt; to the form and place it in the top right corner of Form1.&lt;/li&gt;
&lt;li&gt;Set the Format property of the DateTimePicker to Custom and set the Custom Format property to &amp;ldquo;yyyy/MM.&amp;rdquo;&lt;/li&gt;
&lt;li&gt;Set the Size of the DateTimePicker to Width=96, Height=20.&lt;/li&gt;
&lt;li&gt;Give Form1 a Title of &amp;ldquo;Sales Management&amp;rdquo; by settings its Text property.&lt;/li&gt;
&lt;li&gt;In Form1.cs, add the following line of code above the namespace. This will give us direct access to the SqlClient library for connecting to our database.&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;&lt;code&gt;using System.Data.SqlClient;&lt;/code&gt; &lt;/blockquote&gt;
&lt;h2&gt;Create the User Interface&lt;/h2&gt;
&lt;p&gt;Now let&amp;rsquo;s decide on the content of our application. A Sales Management application should contain the following items:&lt;/p&gt;
&lt;ul style="MARGIN-TOP:0in;"&gt;
&lt;li&gt;Date&lt;/li&gt;
&lt;li&gt;Proceeds&lt;/li&gt;
&lt;li&gt;Payments (Cost)&lt;/li&gt;
&lt;li&gt;Gross Margin&lt;/li&gt;
&lt;li&gt;Gross Margin Rate&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let&amp;rsquo;s create a table to display these 5 items.&lt;/p&gt;
&lt;p&gt;By default, &lt;b&gt;C1FlexGrid&lt;/b&gt; consists of 50 rows &amp;times; 10 columns. Since we need only 5 columns in our application, we will set Cols.Count property (located under the Cols node) to 5 in the FlexGrid property window. We do not need any rows in the grid at the start of the application, so we will set the Rows.Count property to 1.&lt;/p&gt;
&lt;p class="Art"&gt;&lt;a href="http://helpcentral.componentone.com/CS/blogs/windev/Print2.png"&gt;&lt;img height="546" width="335" src="http://helpcentral.componentone.com/CS/blogs/windev/BecomeAnExpert/Print2.png" border="0" style="width:335px;height:546px;" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Setting up the Columns&lt;/h3&gt;
&lt;p&gt;We need to label each of our five columns. To do this we can simply select a column and modify the Column Caption property using the smart tag. Name the five columns Date, Proceeds, Payments, GrossMargin, and GrossMarginRate.&lt;/p&gt;
&lt;p class="MsoNormal"&gt;&lt;a href="http://helpcentral.componentone.com/CS/blogs/windev/Print3.png"&gt;&lt;img height="410" width="566" src="http://helpcentral.componentone.com/CS/blogs/windev/BecomeAnExpert/Print3.png" border="0" style="width:566px;height:410px;" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Setting Input Constraints&lt;/h3&gt;
&lt;p&gt;Next we want to format the &amp;ldquo;Proceeds&amp;rdquo; and &amp;ldquo;Payments&amp;rdquo; columns to allow only numeric input. To set input constraints and captions for each column, we can make the desired changes by simply selecting a column and modifying the settings using the smart tag.&lt;/p&gt;
&lt;p&gt;Select the Proceeds column, and click the smart tag to open the C1FlexGrid Tasks menu. Locate the Format Strings textbox and click the ellipsis button. This opens the &amp;ldquo;Format String&amp;rdquo; dialog box. Select &amp;quot;Currency&amp;quot; as the &amp;quot;Format type&amp;quot;.&lt;/p&gt;
&lt;p&gt;Repeat the steps above for the Payments column.&lt;/p&gt;
&lt;p class="Art"&gt;&lt;span style="mso-bidi-font-size:10.5pt;"&gt;&lt;/span&gt;&lt;a href="http://helpcentral.componentone.com/CS/blogs/windev/Print4.png"&gt;&lt;img height="473" width="566" src="http://helpcentral.componentone.com/CS/blogs/windev/BecomeAnExpert/Print4.png" border="0" style="width:566px;height:473px;" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The GrossMargin and GrossMarginRate columns will contain computed values; therefore, we won&amp;rsquo;t allow editing for these columns.&lt;/p&gt;
&lt;p&gt;Select the GrossMargin column, and click the smart tag to open the C1FlexGrid Tasks menu. Uncheck the &amp;ldquo;Allow Editing&amp;rdquo; option. Repeat the steps for the GrossMarginRate column.&lt;/p&gt;
&lt;p class="Art"&gt;&lt;a href="http://helpcentral.componentone.com/CS/blogs/windev/Print5.png"&gt;&lt;img height="413" width="384" src="http://helpcentral.componentone.com/CS/blogs/windev/BecomeAnExpert/Print5.png" border="0" style="width:384px;height:413px;" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;To sum up, only numeric values can be entered in &amp;ldquo;Proceeds&amp;rdquo; and &amp;ldquo;Payments&amp;rdquo; columns while input is disabled in other columns.&lt;/p&gt;
&lt;p&gt;So by simply setting a few properties, we can restrict the entry of certain type values in some columns and prohibit entering any values in other columns.&lt;/p&gt;
&lt;h3&gt;Calculating Values&lt;/h3&gt;
&lt;p&gt;Next, let&amp;rsquo;s calculate the values of GrossMargin and GrossMarginRate columns. To do this, add C1FlexGrid&amp;rsquo;s AfterEdit event to the application. This event fires after the cell has been edited. Add the following code:&lt;/p&gt;
&lt;blockquote&gt;&lt;code&gt;double proceeds = this.c1FlexGrid1[e.Row, 1] == null&lt;br /&gt;? 0 : double.Parse(this.c1FlexGrid1[e.Row, 1].ToString());&lt;br /&gt;double payments = this.c1FlexGrid1[e.Row, 2] == null&lt;br /&gt;? 0 : double.Parse(this.c1FlexGrid1[e.Row, 2].ToString());&lt;br /&gt;double grossMargin = proceeds - payments;&lt;br /&gt;this.c1FlexGrid1[e.Row, 3] = grossMargin;&lt;br /&gt;this.c1FlexGrid1[e.Row, 4] = proceeds != 0 ? grossMargin / proceeds : 0;&lt;/code&gt; &lt;/blockquote&gt;
&lt;p&gt;Values of Proceeds and Payments columns are converted to Double to calculate the GrossMargin. Null value is converted to 0. Subtract Payments from Proceeds to get GrossMargin amount (GrossMargin = Proceeds &amp;ndash; Payments). GrossMarginRate is calculated by dividing GrossMargin with Proceeds (GrossMarginRate = GrossMargin/Proceeds). When the value in the Proceeds column is 0, GrossMarginRate is also changed to 0. &lt;/p&gt;
&lt;p&gt;This is how we calculate GrossMargin and GrossMarginRate after the values in Proceeds and Payments columns have been entered.&lt;/p&gt;
&lt;h3&gt;Setting Date&lt;/h3&gt;
&lt;p&gt;Now let&amp;#39;s see how we can set dates in the fixed column for each row. Of course it is possible to enter the date every time, but for our convenience, let&amp;rsquo;s try setting the date automatically. Month and year are set to display the date on a monthly basis.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s use the DateTimePicker to set the date and month. Add the following code to dynamically change the row count of FlexGrid and values of the fixed column to the ValueChanged event of DateTimePicker.&lt;/p&gt;
&lt;blockquote&gt;&lt;code&gt;This.setDateCell();&lt;/code&gt; &lt;/blockquote&gt;
&lt;p&gt;Now we need to add the setDateCell method which deletes all rows excep the fixed row and adds rows with the selected date.&lt;/p&gt;
&lt;blockquote&gt;&lt;code&gt;private void setDateCell()&lt;br /&gt;{&lt;br /&gt;this.c1FlexGrid1.Rows.RemoveRange(1, this.c1FlexGrid1.Rows.Count - 1);&lt;br /&gt;&lt;br /&gt;DateTime startDate =&lt;br /&gt;new DateTime(this.dateTimePicker1.Value.Year,&lt;br /&gt;this.dateTimePicker1.Value.Month,&lt;br /&gt;1);&lt;br /&gt;&lt;br /&gt;DateTime value = startDate;&lt;br /&gt;&lt;br /&gt;while (value.Month == startDate.Month)&lt;br /&gt;{&lt;br /&gt;this.c1FlexGrid1.AddItem(value.ToShortDateString());&lt;br /&gt;value = value.AddDays(1);&lt;br /&gt;}&lt;br /&gt;}&lt;/code&gt; &lt;/blockquote&gt;
&lt;p&gt;By using this simple piece of code, we can add the date in the heading of each row and also add and delete rows.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s add a couple lines of code to the Form&amp;rsquo;s Load event so that the &lt;b&gt;C1FlexGrid&lt;/b&gt; is displaying some dates before we even select one.&lt;/p&gt;
&lt;blockquote&gt;&lt;code&gt;this.dateTimePicker1.Value = DateTime.Now;&lt;br /&gt;this.setDateCell();&lt;/code&gt; &lt;/blockquote&gt;
&lt;h3&gt;Loading and Registering Data&lt;/h3&gt;
&lt;p&gt;Now let&amp;rsquo;s try registering the entered data and loading this registered data into the grid. A &amp;#39;Sales&amp;#39;&amp;nbsp;table has to be created for registering the sales data. You can use the DBScript that&amp;#39;s included with the &lt;a href="http://helpcentral.componentone.com/CS/blogs/windev/BecomeAnExpert/Part1.zip"&gt;downloaded sample&lt;/a&gt; to generate the sales table.&lt;/p&gt;
&lt;p&gt;Add a button to the form with the text &amp;ldquo;Register,&amp;rdquo; and in its click event write the method for registering the data to the database.&lt;/p&gt;
&lt;p&gt;The method of registering data to the database and loading it to the grid has been summarized below (implement steps 1-7 in order). The following code should be placed in the Register button&amp;rsquo;s click event.&lt;/p&gt;
&lt;ol style="MARGIN-TOP:0in;"&gt;
&lt;li&gt;Create an instance of SqlConnection&lt;code&gt;&lt;br /&gt;&lt;br /&gt;SqlConnection connection = new SqlConnection(this.getConnectionString());&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Open database connection&lt;code&gt;&lt;br /&gt;&lt;br /&gt;connection.Open();&lt;br /&gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Create an instance of SqlCommand&lt;br /&gt;&lt;br /&gt;&lt;code&gt;SqlCommand command = new SqlCommand();&lt;/code&gt; &lt;/li&gt;
&lt;blockquote class="MsoNormal"&gt;&lt;code&gt;Set SQL command&lt;br /&gt;command.Connection = connection;&lt;br /&gt;command.CommandType = CommandType.Text;&lt;/code&gt; &lt;/blockquote&gt;
&lt;li&gt;Delete data by executing SQL commands&lt;/li&gt;
&lt;blockquote&gt;&lt;code&gt;DateTime selectedMonth&lt;br /&gt;= new DateTime(this.dateTimePicker1.Value.Year, this.dateTimePicker1.Value.Month, 1);&lt;br /&gt;&lt;br /&gt;command.CommandText =&lt;br /&gt;string.Format(&amp;quot;DELETE FROM Sales WHERE Date &amp;gt;= &amp;#39;{0}&amp;#39; AND Date &amp;lt; &amp;#39;{1}&amp;#39;&amp;quot;, &lt;br /&gt;selectedMonth, &lt;br /&gt;selectedMonth.AddMonths(1));&lt;br /&gt;command.ExecuteNonQuery();&lt;br /&gt;&lt;br /&gt;for (int i = 1; i &amp;lt; this.c1FlexGrid1.Rows.Count; i++)&lt;br /&gt;{&lt;br /&gt;if (this.c1FlexGrid1[i, 1] != null&lt;br /&gt;|| this.c1FlexGrid1[i, 2] != null)&lt;br /&gt;{&lt;br /&gt;string insert = &lt;br /&gt;string.Format(&amp;quot;INSERT Sales (Date, Proceeds, Payments, GrossMargin, GrossMarginRate) VALUES (&amp;#39;{0}&amp;#39;, {1}, {2}, {3}, {4})&amp;quot;,&lt;br /&gt;this.c1FlexGrid1[i, 0],&lt;br /&gt;this.c1FlexGrid1[i, 1] == null ? 0 : this.c1FlexGrid1[i, 1],&lt;br /&gt;this.c1FlexGrid1[i, 2] == null ? 0 : this.c1FlexGrid1[i, 2],&lt;br /&gt;this.c1FlexGrid1[i, 3],&lt;br /&gt;this.c1FlexGrid1[i, 4]);&lt;br /&gt;&lt;br /&gt;command.CommandType = CommandType.Text;&lt;br /&gt;command.CommandText = insert;&lt;br /&gt;command.ExecuteNonQuery();&lt;br /&gt;}&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;/blockquote&gt;
&lt;li&gt;Dispose command&lt;/li&gt;
&lt;blockquote&gt;&lt;code&gt;command.Dispose();&lt;/code&gt; &lt;/blockquote&gt;
&lt;li&gt;Close database connection&lt;/li&gt;
&lt;blockquote&gt;&lt;code&gt;connection.Close();&lt;br /&gt;connection.Dispose();&lt;br /&gt;&lt;br /&gt;MessageBox.Show(&amp;quot;Data has been written to the database.&amp;quot;);&lt;/code&gt; &lt;/blockquote&gt;
&lt;/ol&gt;
&lt;p&gt;We used the following SQL command to delete records.&lt;/p&gt;
&lt;blockquote&gt;&lt;code&gt;DELETE FROM Sales &lt;br /&gt;WHERE Date &amp;gt;= &amp;#39;Start date of the month&amp;#39; AND Date &amp;lt; &amp;#39;Start date of the next month&amp;#39;&lt;/code&gt; &lt;/blockquote&gt;
&lt;p&gt;The range of dates selected in the table for sales data are specified in the WHERE clause. By setting a single month for the dates in the WHERE clause, we prevented registering data on a date that already existed in the table.&lt;/p&gt;
&lt;p&gt;We registered the sales data entered in &lt;b&gt;C1FlexGrid&lt;/b&gt; using the following SQL command.&lt;/p&gt;
&lt;blockquote&gt;&lt;code&gt;INSERT Sales (Date, Proceeds, Payments, GrossMargin, GrossMarginRate)&lt;br /&gt;VALUES (&amp;#39;Date&amp;#39;, &amp;lsquo;Proceeds&amp;rsquo;, &amp;lsquo;Payments&amp;rsquo;, &amp;lsquo;GrossMargin&amp;rsquo;, &amp;lsquo;GrossMarginRate&amp;rsquo;)&lt;/code&gt; &lt;/blockquote&gt;
&lt;p&gt;The data from &lt;b&gt;C1FlexGrid&lt;/b&gt; will be set as it is. Null values are acceptable in the &amp;ldquo;Proceeds&amp;rdquo; and &amp;ldquo;Payment&amp;rdquo; columns. They are registered as 0 in the database.&lt;/p&gt;
&lt;h3&gt;Getting Records&lt;/h3&gt;
&lt;p&gt;Let&amp;rsquo;s check the registered data. We will add another button to our Form and call it &amp;ldquo;Load.&amp;rdquo; In the Load button&amp;rsquo;s click event, add the following code:&lt;/p&gt;
&lt;blockquote&gt;&lt;code&gt;SqlConnection connection = new SqlConnection(this.getConnectionString());&lt;br /&gt;&lt;br /&gt;// Open database connection.&lt;br /&gt;connection.Open();&lt;br /&gt;&lt;br /&gt;// Generate an instance of SqlCommand from SqlConnection. &lt;br /&gt;SqlCommand command = connection.CreateCommand();&lt;br /&gt;&lt;br /&gt;DateTime selectedMonth &lt;br /&gt;= new DateTime(this.dateTimePicker1.Value.Year, this.dateTimePicker1.Value.Month, 1);&lt;br /&gt;&lt;br /&gt;string selectSQL &lt;br /&gt;= string.Format(&amp;quot;SELECT Date, Proceeds, Payments, GrossMargin, GrossMarginRate FROM Sales&amp;quot;&lt;br /&gt;+ &amp;quot; WHERE Date &amp;gt;= &amp;#39;{0}&amp;#39; AND Date &amp;lt; &amp;#39;{1}&amp;#39;&amp;quot;&lt;br /&gt;+ &amp;quot; ORDER BY Date&amp;quot;, selectedMonth, selectedMonth.AddMonths(1));&lt;br /&gt;&lt;br /&gt;// Set the SQL command to be executed.&lt;br /&gt;command.CommandText = selectSQL;&lt;br /&gt;&lt;br /&gt;// Execute the specified SQL command and create a SQLDataReader.&lt;br /&gt;SqlDataReader reader = command.ExecuteReader();&lt;br /&gt;&lt;br /&gt;// Dispose command. &lt;br /&gt;command.Dispose();&lt;br /&gt;&lt;br /&gt;// Move to the next record. &lt;br /&gt;while (reader.Read())&lt;br /&gt;{&lt;br /&gt;DateTime dbDate = Convert.ToDateTime(reader[&amp;quot;Date&amp;quot;]);&lt;br /&gt;&lt;br /&gt;this.c1FlexGrid1[dbDate.Day, 1] = reader[&amp;quot;Proceeds&amp;quot;];&lt;br /&gt;this.c1FlexGrid1[dbDate.Day, 2] = reader[&amp;quot;Payments&amp;quot;];&lt;br /&gt;this.c1FlexGrid1[dbDate.Day, 3] = reader[&amp;quot;GrossMargin&amp;quot;];&lt;br /&gt;this.c1FlexGrid1[dbDate.Day, 4] = reader[&amp;quot;GrossMarginRate&amp;quot;];&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;// Close reader.&lt;br /&gt;reader.Close();&lt;br /&gt;&lt;br /&gt;// Close database connection.&lt;br /&gt;connection.Close();&lt;br /&gt;connection.Dispose();&lt;br /&gt;&lt;br /&gt;MessageBox.Show(&amp;quot;Data has been loaded to the grid.&amp;quot;);&lt;/code&gt; &lt;/blockquote&gt;
&lt;p&gt;Here we are creating a SqlConnection just as we did before when registering the data. We are using the following SQL statement for getting the records.&lt;/p&gt;
&lt;blockquote&gt;&lt;code&gt;SELECT Date, Proceeds, Payments, GrossMargin, GrossMarginRate &lt;br /&gt;FROM Sales &lt;br /&gt;WHERE Date &amp;gt;= &amp;lsquo;Start date of the month&amp;#39; &lt;br /&gt;AND Date &amp;lt; &amp;#39;Start date of the next month&amp;#39; &lt;br /&gt;ORDER BY Date&lt;/code&gt; &lt;/blockquote&gt;
&lt;p&gt;Only the date is added initially, and then the corresponding data for a particular date is added in the other columns. Therefore, data retrieved is based on the sorted date column.&lt;/p&gt;
&lt;p&gt;The following screenshot shows the sample application for registering the data.&lt;/p&gt;
&lt;p class="Art"&gt;&lt;a href="http://helpcentral.componentone.com/CS/blogs/windev/Print6.png"&gt;&lt;img height="580" width="487" src="http://helpcentral.componentone.com/CS/blogs/windev/BecomeAnExpert/Print6.png" border="0" style="width:487px;height:580px;" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In Part 1 we have introduced Studio for WinForms and have explained the usage and features of &lt;b&gt;C1FlexGrid&lt;/b&gt;. Use &lt;b&gt;C1FlexGrid&lt;/b&gt; to create a simple data list display. Although the sample shown here is a Sales Management Application, with a few minor changes it can also be used as an accounting application. &lt;/p&gt;
&lt;p&gt;In &lt;a target="_blank" href="http://helpcentral.componentone.com/CS/blogs/windev/archive/2010/02/05/BecomeAnExpertPart2.aspx"&gt;Part 2&lt;/a&gt;, we will use more features from &lt;b&gt;C1FlexGrid&lt;/b&gt; and use the grid with other ComponentOne controls to create a feature rich application.&lt;/p&gt;
&lt;h2&gt;&lt;a href="http://helpcentral.componentone.com/CS/blogs/windev/BecomeAnExpert/Part1.zip"&gt;Download Part 1&amp;nbsp;Sample Project&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;Acknowledgements&lt;/h3&gt;
&lt;ul style="MARGIN-TOP:0in;"&gt;
&lt;li&gt;Article copyright &amp;copy; 2009&amp;nbsp;Shogo Takayama&amp;nbsp;＆ Shoeisha Co., Ltd. http://www.shoeisha.co.jp/&lt;/li&gt;
&lt;li&gt;&lt;a href="http://codezine.jp/"&gt;http://codezine.jp/&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;These contents were made by GrapeCity inc., Japan. &lt;a href="http://www.grapecity.com/japan/"&gt;http://www.grapecity.com/japan/&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;Translated by Vidhi Kapoor (GrapeCity India Pvt. Ltd.) &lt;/li&gt;
&lt;li&gt;Edited by Greg Lutz (ComponentOne)&lt;/li&gt;
&lt;/ul&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://helpcentral.componentone.com/CS/aggbug.aspx?PostID=227012" width="1" height="1"&gt;</content><author><name>C1_GregL</name><uri>http://helpcentral.componentone.com/CS/members/C1_5F00_GregL/default.aspx</uri></author><category term="WinForms" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/WinForms/default.aspx" /><category term="C1FlexGrid" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/C1FlexGrid/default.aspx" /><category term="Become an Expert" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/Become+an+Expert/default.aspx" /></entry><entry><title>Dynamic Connection Strings</title><link rel="alternate" type="text/html" href="/CS/blogs/windev/archive/2010/01/26/DynamicConnectionStrings.aspx" /><id>/CS/blogs/windev/archive/2010/01/26/DynamicConnectionStrings.aspx</id><published>2010-01-26T17:01:00Z</published><updated>2010-01-26T17:01:00Z</updated><content type="html">&lt;h1&gt;Dynamic Connection Strings&lt;/h1&gt;
&lt;p&gt;This blog post details a solution to the common problem of dynamically setting connection strings. The approach used allows end-users to change database connections at runtime through a friendly user interface, and save the connection for future uses. Provided with this post is source code for the connection form which accesses all SQL servers and databases on a computer and its networks, builds a connection string based upon user selection, and also saves this to an xml file in local data storage (see bottom of post for download).&lt;/p&gt;
&lt;p&gt;&lt;img style="WIDTH:304px;HEIGHT:373px;" title="Connection Wizard Form" alt="Connection Wizard Form" align="middle" src="http://blogs.componentone.com/CS/blogs/windev/DynamicConnectionStrings/ConnectionWizard.jpg" width="304" height="373" /&gt;&lt;/p&gt;
&lt;h2&gt;Scenario: &lt;/h2&gt;
&lt;p&gt;You create a project in Visual Studio which utilizes a backend database.&amp;nbsp;Typically, you first create your database, and then create a connection string in Visual Studio to link to your database, and then you complete the rest of your project. You store the database connection string in the settings of your project.&lt;/p&gt;
&lt;p&gt;&lt;img style="WIDTH:600px;HEIGHT:165px;" title="Project Settings" alt="Project Settings" src="http://blogs.componentone.com/CS/blogs/windev/DynamicConnectionStrings/settings1.jpg" width="600" height="165" /&gt;&lt;/p&gt;
&lt;h2&gt;The Problem:&lt;/h2&gt;
&lt;p&gt;Once you are happy with the application you package and distribute it to your end users. This is when the fun starts as your end users start to say that they can&amp;#39;t make a connection to the database on their server.&amp;nbsp;The reason for this is really quite simple. The connection string that you have created has Application scope and is strongly typed. You need to allow the connection string to be set on the users end.&lt;/p&gt;
&lt;p&gt;Because&amp;nbsp;the connection string is strongly typed, you can do something like:&lt;/p&gt;&lt;pre&gt;[C#] string myString = Properties.Settings.Default.MyConnectionString;&lt;/pre&gt;&lt;pre&gt;[VB.NET] Dim MyString As String = My.Settings.MyConnectionString&lt;/pre&gt;
&lt;p&gt;But if you try and reverse the logic:&lt;/p&gt;&lt;pre&gt;[C#] Properties.Settings.Default.MyConnectionString = &amp;quot;abc&amp;quot;;&lt;/pre&gt;&lt;pre&gt;[VB.NET] My.Settings.MyConnectionString = &amp;quot;abc&amp;quot;&lt;/pre&gt;
&lt;p&gt;You get an error.&lt;/p&gt;
&lt;p&gt;&lt;img style="WIDTH:492px;HEIGHT:104px;" title="Connection Error" alt="Connection Error" align="middle" src="http://blogs.componentone.com/CS/blogs/windev/DynamicConnectionStrings/connectionError.jpg" width="492" height="104" /&gt;&lt;/p&gt;
&lt;p&gt;The problem, of course, lies in the strongly typed connection string that you created when you designed the application. The real problem is that it is strongly typed.&lt;/p&gt;
&lt;p&gt;So how can you get around this?&amp;nbsp; It&amp;#39;s something that you would think has a ready solution available, but strangely there isn&amp;#39;t one.&amp;nbsp;A web search for ‘How do I dynamically reassign the connection string&amp;#39; will return hundreds of results. The realization is that you are not alone.&lt;/p&gt;
&lt;h2&gt;The Solution:&lt;/h2&gt;
&lt;p&gt;Well, it turns out that you can actually play with the settings, but to do this you need to access the settings events.&amp;nbsp;If you return to the Settings page of your project you&amp;#39;ll see a button that allows you to view the code.&lt;/p&gt;
&lt;p&gt;&lt;img style="WIDTH:600px;HEIGHT:165px;" title="Project Settings View Code" alt="Project Settings View Code" src="http://blogs.componentone.com/CS/blogs/windev/DynamicConnectionStrings/settings2.jpg" width="600" height="165" /&gt;&lt;/p&gt;
&lt;p&gt;Now, you still can&amp;#39;t directly assign the connection string here in the code page (because it&amp;#39;s still strongly typed), but you can make use of late binding. The following code is possible (in the Settings Load event):&lt;/p&gt;&lt;pre&gt;[C#] this[&amp;quot;MyConnectionString&amp;quot;] = &amp;quot;abc&amp;quot;;&lt;/pre&gt;&lt;pre&gt;[VB.NET] Me.Item(&amp;quot;MyConnectionString&amp;quot;) = &amp;quot;abc&amp;quot;&lt;/pre&gt;
&lt;p&gt;This is the key to solving the problem. And now that you know that you can change the connection string at runtime, you can expand this process by letting the user build the connection string rather than you, the developer, always being responsible for knowing it. To do this you can create a property in your Settings.cs or Settings.vb page.&lt;/p&gt;&lt;pre&gt;[C#] public string RuntimeSqlConnectionString {
	set
	{
		this[&amp;quot;MyConnectionString&amp;quot;] = value;
	}
}&lt;/pre&gt;&lt;pre&gt;[VB.NET] Public WriteOnly Property RuntimeSqlConnectionString() As String
	Set(ByVal value As String)
		My.Settings(&amp;quot;MyConnectionString&amp;quot;) = value
	End Set
End Property&lt;/pre&gt;
&lt;p&gt;The rest, as they say, is just a case of applying a little logic to your new found freedom.&amp;nbsp; The easiest approach (for you) is just to present the end user with a text box and have them fill in their connection string and assign that value. But to avoid all those future support calls, you should give them as much help as possible.&amp;nbsp; To achieve this you should display a form that actually searches the end user&amp;#39;s computer/network for active SQL Servers, and when they select one, automatically list all of the databases available.&lt;/p&gt;
&lt;p&gt;When they have made a successful test connection you write the information out to an xml file in the user&amp;#39;s application data directory. This approach allows each user to have their own connection string (each user could have different passwords!).&lt;/p&gt;
&lt;p&gt;Finally, on application startup, you read the xml file and dynamically assign the connection string to the property we created earlier. It&amp;#39;s that simple.&lt;/p&gt;
&lt;p&gt;The same logic can be used for other database connection strings, such as OleDb if you&amp;#39;re using C1Reports. Just&amp;nbsp;add another connection string setting and another property in your Settings code.&lt;/p&gt;
&lt;h2&gt;Download Sample:&lt;/h2&gt;
&lt;p&gt;Download source for this sample: &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a title="Download Connection Wizard Sample Source C#" href="http://blogs.componentone.com/CS/blogs/windev/DynamicConnectionStrings/ConnectionWizard.zip"&gt;ConnectionWizard.zip&lt;/a&gt; [C# - VS2008] 
&lt;li&gt;&lt;a title="Download Connection Wizard Sample VB.NET" href="http://blogs.componentone.com/CS/blogs/windev/DynamicConnectionStrings/ConnectionWizardVB.zip"&gt;ConnectionWizardVB.zip&lt;/a&gt; [VB.NET - VS2008] &lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;This sample provides the code for accessing all SQL servers and databases on a computer and its networks. It builds a connection string with the option for a password, and also saves the connection string to an xml file in local data storage. It may not look like&amp;nbsp;a &amp;quot;wizard&amp;quot; but you could certainly take the logic used and apply it within a wizard of your own.&lt;/p&gt;
&lt;p&gt;&lt;img style="WIDTH:304px;HEIGHT:373px;" title="Connection Wizard Form" alt="Connection Wizard Form" align="middle" src="http://blogs.componentone.com/CS/blogs/windev/DynamicConnectionStrings/ConnectionWizard.jpg" width="304" height="373" /&gt;&lt;/p&gt;
&lt;p&gt;Thanks to Dom Sinclair (&lt;a href="http://www.viewtolearn.net/"&gt;http://www.viewtolearn.net/&lt;/a&gt;) for submitting the content of this article.&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://helpcentral.componentone.com/CS/aggbug.aspx?PostID=227942" width="1" height="1"&gt;</content><author><name>C1_GregL</name><uri>http://helpcentral.componentone.com/CS/members/C1_5F00_GregL/default.aspx</uri></author><category term="SQL Server" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/SQL+Server/default.aspx" /><category term="C1Report" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/C1Report/default.aspx" /></entry><entry><title>Visual Studio 2010 beta Compatibility</title><link rel="alternate" type="text/html" href="/CS/blogs/windev/archive/2010/01/15/visual-studio-2010-beta-compatibility.aspx" /><id>/CS/blogs/windev/archive/2010/01/15/visual-studio-2010-beta-compatibility.aspx</id><published>2010-01-15T17:54:00Z</published><updated>2010-01-15T17:54:00Z</updated><content type="html">&lt;p&gt;If you convert an existing project or solution to Visual Studio 2010 beta there should be no problem. But if you are creating new projects within VS2010 beta there are a couple things you need to know when using ComponentOne WinForms controls.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;You cannot target .NET 4.0 Client Profile (which is the default for any new WindowsApplication.)&lt;/li&gt;
&lt;li&gt;The ISupportInitialize BeginInit and EndInit methods do not get generated automatically when you drop a new C1 control on the form.&lt;br /&gt;&lt;/li&gt;&lt;/ol&gt;
&lt;h2&gt;Workarounds&lt;/h2&gt;
&lt;h3&gt;1. Targeting .NET 4.0 Framework &lt;/h3&gt;
&lt;p&gt;To use &lt;strong&gt;ComponentOne WinForms&lt;/strong&gt; controls under .NET 4, you must target the &lt;strong&gt;full&lt;/strong&gt; .NET 4.0 Framework. By default, new projects are set to target the .NET 4.0 Client Profile. ComponentOne controls depend on the full framework to run properly. If your application is targeting the Client Profile, ComponentOne controls will not appear in the toolbox, and you’ll receive errors regarding System.Design.dll.&lt;/p&gt;
&lt;p&gt;To change this setting for &lt;font color="#009900"&gt;&lt;strong&gt;C# applications&lt;/strong&gt;&lt;/font&gt;: go to the project&amp;#39;s properties (from the Visual Studio menu, select Project | Properties...) and from the Application tab change the Target framework to &amp;quot;.NET Framework 4&amp;quot;.&lt;/p&gt;
&lt;p&gt;To change this setting for &lt;font color="#0066ff"&gt;&lt;strong&gt;VB applications&lt;/strong&gt;&lt;/font&gt;: go to the project&amp;#39;s properties (from the Visual Studio menu, select Project | Properties...) and from the Compile tab click the Advanced Compile Options…. From this window change the Target framework to &amp;quot;.NET Framework 4&amp;quot;.&lt;/p&gt;
&lt;p&gt;Upon changing this you will be prompted to close and reopen the project.&lt;/p&gt;
&lt;h3&gt;2. Adding New Controls to the Form &lt;/h3&gt;
&lt;p&gt;When you&amp;nbsp;add a new C1 control onto your form in Visual Studio 2010 beta, everything should appear fine until you go to run the form. The control will be missing. This is because of a bug in Visual Studio where the ISupportInitialize’s BeginInit and EndInit methods are not added automatically.&lt;/p&gt;
&lt;p&gt;The fix is real simple. Just open up your designer-generated code and add these missing method calls.&lt;/p&gt;
&lt;p&gt;For &lt;font color="#009900"&gt;&lt;strong&gt;C# applications&lt;/strong&gt;&lt;/font&gt;, open up &amp;lt;YourForm&amp;gt;.Designer.cs and add the following two lines of code above the form’s SuspendLayout and ResumeLayout methods (replace c1Chart1 with the name of&amp;nbsp;your control).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;font face="courier new,courier"&gt;((System.ComponentModel.&lt;font color="#00ccff"&gt;ISupportInitialize&lt;/font&gt;)(&lt;font color="#0000ff"&gt;this&lt;/font&gt;.c1Chart1)).BeginInit();&lt;br /&gt;&lt;font color="#0000ff"&gt;this&lt;/font&gt;.SuspendLayout();&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font face="courier new,courier"&gt;...&amp;nbsp;&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font face="courier new,courier"&gt;((System.ComponentModel.&lt;font color="#00ccff"&gt;ISupportInitialize&lt;/font&gt;)(&lt;font color="#0000ff"&gt;this&lt;/font&gt;.c1Chart1)).EndInit();&lt;br /&gt;&lt;font color="#0000ff"&gt;this&lt;/font&gt;.ResumeLayout(&lt;font color="#0000ff"&gt;false&lt;/font&gt;);&lt;/font&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;For &lt;font color="#0066ff"&gt;&lt;strong&gt;VB applications&lt;/strong&gt;&lt;/font&gt;, click the “Show All Files” button from the Solution Explorer and then open &amp;lt;YourForm&amp;gt;.Designer.vb. Add the following two lines of code above the form’s SuspendLayout and ResumeLayout methods (replace C1Chart1 with the name of&amp;nbsp;your control).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;font face="courier new,courier"&gt;&lt;font color="#0000ff"&gt;CType&lt;/font&gt;(&lt;font color="#0000ff"&gt;Me&lt;/font&gt;.C1Chart1, System.ComponentModel.&lt;font color="#00ccff"&gt;ISupportInitialize&lt;/font&gt;).BeginInit()&lt;br /&gt;&lt;font color="#0000ff"&gt;Me&lt;/font&gt;.SuspendLayout()&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font face="courier new,courier"&gt;...&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font face="courier new,courier"&gt;&lt;font color="#0000ff"&gt;CType&lt;/font&gt;(&lt;font color="#0000ff"&gt;Me&lt;/font&gt;.C1Chart1, System.ComponentModel.&lt;font color="#00ccff"&gt;ISupportInitialize&lt;/font&gt;).EndInit()&lt;br /&gt;&lt;font color="#0000ff"&gt;Me&lt;/font&gt;.ResumeLayout(&lt;font color="#0000ff"&gt;False&lt;/font&gt;)&lt;/font&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;You will need to do this for each C1 control added to your form.&lt;/p&gt;
&lt;p&gt;The good news is this bug will be fixed in the official release of VS2010.&lt;/p&gt;
&lt;h3&gt;Studio for WPF&amp;nbsp;&lt;/h3&gt;
&lt;p&gt;Regarding &lt;strong&gt;ComponentOne WPF&lt;/strong&gt; controls...if you convert your existing project to VS2010 beta you should have no problem. We have reported errors of adding C1Chart and C1DataGrid to the toolbox and dragging them to a new form. These will be addressed for the official release.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://helpcentral.componentone.com/CS/aggbug.aspx?PostID=227462" width="1" height="1"&gt;</content><author><name>C1_GregL</name><uri>http://helpcentral.componentone.com/CS/members/C1_5F00_GregL/default.aspx</uri></author><category term="WinForms" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/WinForms/default.aspx" /><category term="Visual Studio 2010" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/Visual+Studio+2010/default.aspx" /><category term="WPF" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/WPF/default.aspx" /></entry><entry><title>Reduce Data-entry Form Development Time by 60%</title><link rel="alternate" type="text/html" href="/CS/blogs/windev/archive/2010/01/06/InputPanelDemo.aspx" /><id>/CS/blogs/windev/archive/2010/01/06/InputPanelDemo.aspx</id><published>2010-01-06T18:23:00Z</published><updated>2010-01-06T18:23:00Z</updated><content type="html">Creating data-entry forms can be very time consuming and tedious, and maintaining them can be a nightmare. However, with &lt;a href="http://www.componentone.com/SuperProducts/InputPanelWinForms/" target="_blank"&gt;ComponentOne InputPanel™ for WinForms&lt;/a&gt; creating and maintaining data-entry forms is no problem at all. The control removes all redundant tasks such as control alignment, spacing, data-binding, labeling, accelerator key generation, and tab ordering. 
&lt;p&gt;This article demonstrates a two-part comparison of creating and maintaining the exact same data-entry form using standard toolbox controls versus using ComponentOne InputPanel. To best illustrate the development experiences, the actual demonstrating will be shown in a series of four videos.&lt;/p&gt;
&lt;h3&gt;Part 1: Creating the Data-Entry Form&lt;/h3&gt;
&lt;p&gt;Creating data-entry forms is perhaps one of the easiest tasks a .NET developer ever takes on in his career. The task primarily involves dragging and positioning many label and text box controls on the form. The interactive design surface in Microsoft Visual Studio makes this task easy enough for anyone who can click and move a mouse. It does not typically involve any coding. Even a data bound form, depending on how you set up your data source, can be completely created without writing much code.&lt;/p&gt;
&lt;p&gt;But when creating a data bound entry form that has many fields, the task becomes a bit more complex. It doesn’t get any faster or easier the more forms you have to create. Additional tasks you will most likely complete for each form include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Bind each individual input control to its data source&lt;/li&gt;
&lt;li&gt;Add a separate Label control for each input control or field&lt;/li&gt;
&lt;li&gt;Strategically assign accelerator keys to each field’s label so that none repeat&lt;/li&gt;
&lt;li&gt;Configure the TabIndex property for each control so that they go in order (unless fields are initially added to the form in the final, desired order)&lt;/li&gt;
&lt;li&gt;Position and align all label and input controls perfectly so that the form looks organized and professional &lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;Each task above is performed for each field on your form. So a form with 20 fields will take 10 times longer to create than one with 2 fields.&lt;/p&gt;
&lt;p&gt;Visual Studio can help you accomplish these tasks to a certain degree. You can setup data binding through the Properties grid and it can be as easy as setting a single property, no code; however, you still have to do this for every single field on your form. As you add controls onto your form, the TabIndex property for each auto-increments itself for you. So the TabIndex property will be 0 for the first control added, 9 for the tenth control added, and so on. But in most cases the order in which you add the controls will not be the final desired order. You will eventually be reconfiguring your tab indexes. (&lt;b&gt;Note:&lt;/b&gt; more on this in part two.) Visual Studio also provides an assortment of alignment and spacing buttons on the Layout toolbar. &lt;/p&gt;
&lt;p&gt;When using the &lt;b&gt;C1InputPanel&lt;/b&gt; control these tasks are all handled automatically for you. You simply set one data source property at the top level of the control and all labels and appropriate input controls are created and aligned for you. &lt;b&gt;C1InputPanel&lt;/b&gt; also has built-in tab ordering and an intelligent keyboard accelerator editor that generates unique keys for each field (accelerator keys are what allow users to jump to a specific field by pressing ALT+key).&lt;/p&gt;
&lt;p&gt;Watch the two videos below to see how we can create the same data-entry form with and without C1InputPanel.&lt;/p&gt;
&lt;h4&gt;Video 1: Creating a data-entry form with C1InputPanel&lt;/h4&gt;
&lt;p&gt;Watch this video to learn how to create a navigational data-entry form using the &lt;b&gt;C1InputPanel&lt;/b&gt; control. This demo is a bit more advanced than any of the samples that ship with the control because it uses a master-detail relationship and works with the &lt;b&gt;C1TrueDBGrid&lt;/b&gt; control. The development time spent in this video is roughly 3:30 minutes.&lt;/p&gt;
&lt;p&gt;&lt;a title="Click to launch Video 1 in a separate window" href="http://www.componentone.com/newimages/Products/Videos/StudioWinForms/InputPanelDemo1.html" target="_blank"&gt;&lt;img style="WIDTH:600px;HEIGHT:406px;" src="http://helpcentral.componentone.com/CS/blogs/windev/InputPanelDemo/DemoShot1.jpg" width="600" height="406" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;Video 2: Creating a data-entry form &lt;i&gt;without&lt;/i&gt; C1InputPanel&lt;/h4&gt;
&lt;p&gt;In this video, see how to create the exact same data-entry form as the previous video using only the standard toolbox controls that come with Visual Studio. This can be a great educational video for anyone who has never created navigational, data bound windows forms. The video skips setting up the data source, as this would be identical with or without &lt;b&gt;C1InputPanel&lt;/b&gt;. The development time spent in this video is roughly 8 minutes when we eliminate the chit-chat.&lt;/p&gt;
&lt;p&gt;&lt;a title="Click to launch Video 2 in a separate window" href="http://www.componentone.com/newimages/Products/Videos/StudioWinForms/InputPanelDemo3.html" target="_blank"&gt;&lt;img style="WIDTH:600px;HEIGHT:387px;" src="http://helpcentral.componentone.com/CS/blogs/windev/InputPanelDemo/DemoShot3.jpg" width="600" height="387" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Note that the time saved using the &lt;b&gt;C1InputPanel&lt;/b&gt; control in this example is relatively small, about 4:30 minutes, but this is more than half of the total time spent without &lt;b&gt;C1InputPanel&lt;/b&gt;. Imagine the time savings across many forms with reducing the development time more than 50%. We also cut several corners in the 2nd video, such as not naming labels and input controls, and the controls were never aligned perfectly. The accelerator keys were just copied from the first video so we let &lt;b&gt;C1InputPanel&lt;/b&gt; do all the work for this task. The time spent on these shortcuts should also be considered.&lt;/p&gt;
&lt;h3&gt;Part 2: Maintaining the Data-Entry Form&lt;/h3&gt;
&lt;p&gt;The developer doesn’t necessarily get the final say in the order and layout of a form. End-users may later request that some fields be moved higher in the order due to frequency of updates, or lower in the order, or they may even later request that certain fields be moved together. All of these requests would require maintenance to the forms you’ve already created. A typical data-entry form could go through many layout tweaks before the end users are finally satisfied.&lt;/p&gt;
&lt;p&gt;When using&amp;nbsp;standard toolbox&amp;nbsp;controls the maintenance procedure is very manual. If you need to rearrange the fields then to do this you will literally be picking them up and moving them piece by piece. Once you rearrange controls you then have to reconfigure tab ordering, layout spacing, sizing and sometimes you may want to reconfigure accelerator keys.&lt;/p&gt;
&lt;p&gt;When using &lt;b&gt;C1InputPanel&lt;/b&gt;, maintenance is almost non-existent. When you first create the data-entry form with &lt;b&gt;C1InputPanel&lt;/b&gt; you will inevitably make a few tweaks here and there, such as breaking the fields into multiple columns, or moving a field or two. These tweaks are easily handled in the Item Collection editor (see Video #1). Further maintenance to your data-entry form is no more difficult as you just open up the Item Collection editor to move fields and set column breaks to match the new desired layout. The difference between this approach and the manual approach is that here you are just setting a few properties and clicking a few buttons, which is a lot quicker than dragging controls around while trying to keep them perfectly aligned. &lt;strong&gt;C1InputPanel&lt;/strong&gt; will also automatically update the tab order when you rearrange controls, so you never have to worry about updating the TabIndex property for every input control on the form.&lt;/p&gt;
&lt;p&gt;Watch the two videos below to see the benefits of using &lt;b&gt;C1InputPanel&lt;/b&gt; to make modifications to the data-entry forms created in the previous videos. We will assume that these changes below have been requested and we’ll apply the same changes to both forms.&lt;/p&gt;
&lt;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img style="WIDTH:454px;HEIGHT:461px;" title="Data-Entry Form Changes" alt="Data-Entry Form Changes" align="absMiddle" src="http://helpcentral.componentone.com/CS/blogs/windev/InputPanelDemo/StickyNote.png" width="454" height="461" /&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;/blockquote&gt;
&lt;h4&gt;Video 3: Making changes to a data-entry form created with C1InputPanel&lt;/h4&gt;
&lt;p&gt;In this video we make the suggested changes shown on the sticky note to the app we just created in Video 1 using &lt;strong&gt;C1InputPanel&lt;/strong&gt;. The development time spent in this video is approximately 45 seconds.&lt;/p&gt;
&lt;p&gt;&lt;a title="Click to launch Video 3 in a separate window" href="http://www.componentone.com/newimages/Products/Videos/StudioWinForms/InputPanelDemo2.html" target="_blank"&gt;&lt;img style="WIDTH:600px;HEIGHT:405px;" src="http://helpcentral.componentone.com/CS/blogs/windev/InputPanelDemo/DemoShot2.jpg" width="600" height="405" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;Video 4: Making changes to a data-entry form created &lt;i&gt;without&lt;/i&gt; C1InputPanel&lt;/h4&gt;
&lt;p&gt;In this video we make the same changes&amp;nbsp;from the sticky note to the app created in Video 2, using only standard toolbox controls. The development time spent in this video is approximately 4:15 minutes.&lt;/p&gt;
&lt;p&gt;&lt;a title="Click to launch Video 4 in a separate window" href="http://www.componentone.com/newimages/Products/Videos/StudioWinForms/InputPanelDemo4.html" target="_blank"&gt;&lt;img style="WIDTH:600px;HEIGHT:387px;" src="http://helpcentral.componentone.com/CS/blogs/windev/InputPanelDemo/DemoShot4.jpg" width="600" height="387" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;When we combine the time saved between the two sets of videos we see about 60% less development time when using &lt;b&gt;C1InputPanel&lt;/b&gt; to create and maintain the same data-entry form. Of course, the alleviated work using &lt;b&gt;C1InputPanel&lt;/b&gt; is tedious and time consuming; nothing&amp;nbsp;overly difficult in the spectrum of developer tasks.&lt;/p&gt;
&lt;p&gt;Also keep in mind in this example we had to hold &lt;b&gt;C1InputPanel&lt;/b&gt; back in terms of features and functionality so that it somewhat matched the competing sample. There are more features that you get with &lt;b&gt;C1InputPanel&lt;/b&gt; that you wouldn’t otherwise have out-of-the-box. Consider these the icing on the cake:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;3 Microsoft Office Visual Styles that apply to the control itself and all embedded input controls&lt;/li&gt;
&lt;li&gt;Validation and Error handling: flag invalid input with a red box around the editor and optionally display error tooltips&lt;/li&gt;
&lt;li&gt;19 native controls including split buttons and rich labels&lt;/li&gt;
&lt;li&gt;Rich tooltips for all embedded controls&lt;/li&gt;
&lt;li&gt;Check out the &lt;a href="http://www.componentone.com/SuperProducts/InputPanelWinForms/"&gt;InputPanel&lt;/a&gt; product page for more features&lt;/li&gt;&lt;/ul&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://helpcentral.componentone.com/CS/aggbug.aspx?PostID=226995" width="1" height="1"&gt;</content><author><name>C1_GregL</name><uri>http://helpcentral.componentone.com/CS/members/C1_5F00_GregL/default.aspx</uri></author><category term="WinForms" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/WinForms/default.aspx" /><category term="data-entry" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/data-entry/default.aspx" /><category term="C1TrueDBGrid" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/C1TrueDBGrid/default.aspx" /><category term="C1InputPanel" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/C1InputPanel/default.aspx" /></entry><entry><title>Enhanced Printing and Previewing with C1FlexGrid</title><link rel="alternate" type="text/html" href="/CS/blogs/windev/archive/2009/12/28/FlexGridPrintPreview.aspx" /><id>/CS/blogs/windev/archive/2009/12/28/FlexGridPrintPreview.aspx</id><published>2009-12-28T17:00:00Z</published><updated>2009-12-28T17:00:00Z</updated><content type="html">&lt;h2&gt;Traditional Printing with the C1FlexGrid Control&lt;/h2&gt;
&lt;p&gt;The &lt;b&gt;C1FlexGrid&lt;/b&gt; control (part of &lt;a title="ComponentOne FlexGrid for WinForms" href="http://www.componentone.com/SuperProducts/FlexGridWinForms/" target="_blank"&gt;ComponentOne FlexGrid for WinForms&lt;/a&gt;) has always supported printing and previewing with the &lt;b&gt;PrintGrid&lt;/b&gt; method. The &lt;b&gt;PrintGrid&lt;/b&gt; method takes parameters that allow you to customize the print/preview process, including the ability to show a print preview dialog, scale the grid, provide headers and footers, and so on.&lt;/p&gt;
&lt;p&gt;The code below shows how you can create a print preview dialog for a &lt;b&gt;C1FlexGrid&lt;/b&gt; using the &lt;b&gt;PrintGrid&lt;/b&gt; method. It only takes one line of code:&lt;/p&gt;&lt;pre&gt;_flex.PrintGrid(&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;quot;Standard&amp;quot;,&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // document title&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; PrintGridFlags.ShowPreviewDialog,&amp;nbsp;&amp;nbsp; // options&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;quot;\tNorthWind Invoices&amp;quot;,&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // header&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;quot;\t\tPage {0}&amp;quot;);&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // footer &lt;/pre&gt;
&lt;p&gt;The &lt;b&gt;PrintGridFlags&lt;/b&gt; parameter allows you to specify whether the method should simply print the grid immediately, whether it should display a print preview or printer setup dialog, and various scaling options. In this example, the parameter is being used to invoke the print preview dialog.&lt;/p&gt;
&lt;p&gt;The strings used to specify the document&amp;#39;s header and footer contains tabs. The tabs break each string into three parts, which are aligned to the left, center, and right of the page. The header and footer strings may also contain a &amp;quot;{0}&amp;quot; placeholder that gets replaced automatically with the current page number.&lt;/p&gt;
&lt;p&gt;After calling this method, you would see a preview dialog like the one shown below:&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;a href="http://helpcentral.componentone.com/CS/blogs/windev/C1FlexGridPrintPreview/StandardPreview.png"&gt;&lt;img border="0" src="http://helpcentral.componentone.com/CS/blogs/windev/C1FlexGridPrintPreview/StandardPreview.png" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;As you can see, the &lt;b&gt;PrintGrid&lt;/b&gt; method is extremely simple. It works very well for common scenarios that only require basic printing support.&lt;/p&gt;
&lt;p&gt;However, this simplicity comes at a cost. The built-in preview dialog is the .NET &lt;b&gt;PrintPreviewDialog&lt;/b&gt; class, which suffers from several limitations, including its dated appearance, the fact that the entire document must be generated before it can be previewed, and the lack of export options.&lt;/p&gt;
&lt;p&gt;Also, the &lt;b&gt;PrintGrid&lt;/b&gt; method creates a &lt;b&gt;PrintDocument&lt;/b&gt; object where the grid is rendered. This is a simple design, but it means the grid &amp;quot;owns&amp;quot; the document, which makes adding other elements to the document a little difficult (imagine, for example, that you want to render a document with a title, three grids, and a couple of charts).&lt;/p&gt;
&lt;h2&gt;Advanced Printing with the C1FlexGrid Control&lt;/h2&gt;
&lt;p&gt;To address the limitations of the &lt;b&gt;PrintGrid&lt;/b&gt; method listed above, we introduced the &lt;b&gt;PrintDocumentGridRenderer&lt;/b&gt; class. This class is included in the &lt;b&gt;C1FlexGrid&lt;/b&gt; assembly and provides methods for rendering scaled parts of the grid into the pages of regular &lt;b&gt;PrintDocument&lt;/b&gt; objects.&lt;/p&gt;
&lt;p&gt;Using the &lt;b&gt;PrintDocumentGridRenderer&lt;/b&gt; involves the following steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create an instance of the &lt;b&gt;PrintDocumentGridRenderer&lt;/b&gt; class, passing it a grid that you want to render.&lt;/li&gt;
&lt;li&gt;Create a &lt;b&gt;PrintDocument&lt;/b&gt; object that will contain the grid and any other content you want.&lt;/li&gt;
&lt;li&gt;Attach handlers to the &lt;b&gt;PrintDocument&lt;/b&gt; object&amp;#39;s &lt;b&gt;BeginPrint&lt;/b&gt;, &lt;b&gt;PrintPage&lt;/b&gt;, and &lt;b&gt;EndPrint&lt;/b&gt; events.&lt;/li&gt;
&lt;li&gt;In the &lt;b&gt;PrintPage&lt;/b&gt; event handler, call the &lt;b&gt;PrintDocumentGridRenderer&lt;/b&gt; class to render parts of the grid one page at a time.&lt;/li&gt;
&lt;li&gt;Print or preview the document using a &lt;b&gt;PrintPreviewDialog&lt;/b&gt; or the document&amp;#39;s &lt;b&gt;Print&lt;/b&gt; method.&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;This is a lot more complex than simply calling &lt;b&gt;PrintGrid&lt;/b&gt;, but these steps are the standard way to create .NET print documents, and they aren’t too complicated. The benefits you get from the added complexity are significant.&lt;/p&gt;
&lt;p&gt;Using this approach, you have total control over the document. You could, for example, create several &lt;b&gt;PrintDocumentGridRenderer&lt;/b&gt; objects and use them to render several grids into the same document.&lt;/p&gt;
&lt;p&gt;This approach also lets you use any &lt;b&gt;PrintPreview&lt;/b&gt; dialog you want, including the &lt;b&gt;C1PrintPreview&lt;/b&gt; dialog described in a separate article called &lt;i&gt;Enhanced PrintPreviewDialog Class with PDF Output&lt;/i&gt; (also available on &lt;a title="www.codeproject.com" href="http://www.codeproject.com/KB/printing/CoolPrintPreviewDialog.aspx" target="_blank"&gt;http://www.codeproject.com/KB/printing/CoolPrintPreviewDialog.aspx&lt;/a&gt;). The &lt;b&gt;C1PrintPreview&lt;/b&gt; dialog has the following advantages over the standard .NET &lt;b&gt;PrintPreviewDialog&lt;/b&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It has a much nicer UI (ToolStrip-based, better mouse/keyboard support, and so on).&lt;/li&gt;
&lt;li&gt;It displays pages as they are generated (no need to wait for the whole document to render).&lt;/li&gt;
&lt;li&gt;It has PDF export built-in.&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;The image below shows the same grid, this time rendered by a &lt;b&gt;C1PrintPreview&lt;/b&gt; dialog:&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;a href="http://helpcentral.componentone.com/CS/blogs/windev/C1FlexGridPrintPreview/AdvancedPreview.png"&gt;&lt;img border="0" src="http://helpcentral.componentone.com/CS/blogs/windev/C1FlexGridPrintPreview/AdvancedPreview.png" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Notice the clean toolstrip with much easier-to-use paging controls, and the PDF export button on the left. PDF export is an extremely valuable feature, provided for free by the &lt;b&gt;C1PrintPreview&lt;/b&gt; (internally, the PDF export is implemented using our &lt;b&gt;C1Pdf&lt;/b&gt; control).&lt;/p&gt;
&lt;p&gt;Also notice the document title, rendered in bold above the grid. This illustrates the ability we now have to add external elements (text, images, charts, and so on) to the document. This would not have been possible using the &lt;b&gt;PrintGrid&lt;/b&gt; method.&lt;/p&gt;
&lt;p&gt;Another important feature is not shown on the image: the document has a &amp;quot;Page N of M&amp;quot; header. This is easy to accomplish because the &lt;b&gt;PrintDocumentGridRenderer&lt;/b&gt; provides &lt;b&gt;CurrentPage&lt;/b&gt; and &lt;b&gt;PageCount&lt;/b&gt; properties that specify how many pages the grid will require (if you were rendering multiple grids on a single document you would have to add the individual page counts yourself).&lt;/p&gt;
&lt;p&gt;To implement this in your applications, you should start by copying the &lt;b&gt;C1PrintPreview&lt;/b&gt; code into your project. To keep my projects organized, I put this code in a new folder within the project. &lt;/p&gt;
&lt;p&gt;Now that you have a class to show the document and export it to PDF, you need to create the document itself. Here&amp;#39;s the code that does that:&lt;/p&gt;&lt;pre&gt;&amp;nbsp;&amp;nbsp; // create regular PrintDocument, attach event handlers&lt;br /&gt;&amp;nbsp; var doc = new PrintDocument();&lt;br /&gt;&amp;nbsp; doc.DocumentName = &amp;quot;Advanced&amp;quot;;&lt;br /&gt;&amp;nbsp; doc.BeginPrint += doc_BeginPrint;&lt;br /&gt;&amp;nbsp; doc.PrintPage += doc_PrintPage;&lt;br /&gt;&amp;nbsp; doc.EndPrint += doc_EndPrint;&lt;br /&gt;&lt;br /&gt;&amp;nbsp; // show new document in Custom print preview dialog&lt;br /&gt;&amp;nbsp; using (var dlg = new C1PrintPreview.C1PrintPreviewDialog())&lt;br /&gt;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; dlg.Document = doc;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; dlg.ShowDialog();&lt;br /&gt;&amp;nbsp; } &lt;/pre&gt;
&lt;p&gt;The code creates a new &lt;b&gt;PrintDocument&lt;/b&gt;, attaches the event handlers required, and then shows the document in a &lt;b&gt;C1PrintPreview&lt;/b&gt; dialog. This is all straightforward .NET printing, nothing new at all.&lt;/p&gt;
&lt;p&gt;The interesting part is in the implementation of the event handlers. That is shown below:&lt;/p&gt;&lt;pre&gt;&amp;nbsp;&amp;nbsp; PrintDocumentGridRenderer _pdr;&lt;br /&gt;&amp;nbsp; void doc_BeginPrint(object sender, PrintEventArgs e)&lt;br /&gt;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // create PrintDocumentGridRenderer to render the grid into this document&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; _pdr = new PrintDocumentGridRenderer(_flex);&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // initialize any options you want&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; _pdr.Options = PrintGridFlags.ActualSize;&lt;br /&gt;&amp;nbsp; } &lt;/pre&gt;
&lt;p&gt;When the document begins printing, we create a new &lt;b&gt;PrintDocumentGridRenderer &lt;/b&gt;that will take care of rendering the grid into the document. The grid to be printed is passed as a parameter in the constructor.&lt;/p&gt;
&lt;p&gt;Next comes the code that gets executed when the document renders each page:&lt;/p&gt;&lt;pre&gt;&amp;nbsp;&amp;nbsp; void doc_PrintPage(object sender, PrintPageEventArgs e)&lt;br /&gt;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // print header on first page&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; if (_pdr.CurrentPage == 0)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; using (var font = new Font(&amp;quot;Verdana&amp;quot;, 14, FontStyle.Bold))&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // render the title&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; var title = &amp;quot;NorthWind Invoices&amp;quot;;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; e.Graphics.DrawString(title, font, Brushes.Black, e.MarginBounds.Location);&lt;br /&gt;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // push the grid rendering down to make room for the title&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; _pdr.FirstPageY =&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; e.MarginBounds.Location.Y +&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; (int)e.Graphics.MeasureString(title, font).Height;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // print the current page of the grid&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; _pdr.PrintPage(e);&lt;br /&gt;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // print a header on all pages&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; var header = string.Format(&amp;quot;NorthWind Invoices - Page {0}/{1}&amp;quot;,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; _pdr.CurrentPage,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; _pdr.PageCount);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; var rc = new Rectangle(e.MarginBounds.X, 0, e.MarginBounds.Width, e.MarginBounds.Bottom);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; var fmt = new StringFormat();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; fmt.Alignment = StringAlignment.Far;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; fmt.LineAlignment = StringAlignment.Center;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; using (var font = new Font(&amp;quot;Verdana&amp;quot;, 10, FontStyle.Bold))&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; e.Graphics.DrawLine(Pens.Black, rc.X, rc.Bottom, rc.Right, rc.Bottom);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; e.Graphics.DrawString(header, font, Brushes.Black, rc, fmt);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // keep printing if there are more pages&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; e.HasMorePages = _pdr.CurrentPage &amp;lt; _pdr.PageCount;&lt;br /&gt;&amp;nbsp; } &lt;/pre&gt;
&lt;p&gt;This looks like a lot of code, but it&amp;#39;s all very simple. The first part of the code renders the big bold title that appears before the grid. This is done using straight GDI+ code. After rendering the title, the code sets the &lt;b&gt;FirstPageY&lt;/b&gt; property on the grid renderer to a value that specifies the vertical position where the grid should start rendering. This prevents the grid renderer from painting the grid over the title we just created.&lt;/p&gt;
&lt;p&gt;After rendering the title, we render the part of the grid that fits on this page. This is done with a single call to the renderer&amp;#39;s &lt;b&gt;PrintPage&lt;/b&gt; method (shown above in bold). This call retrieves the part of the grid that fits on the current page, taking into account any scaling you may have specified when the renderer was created, and renders the grid on the page.&lt;/p&gt;
&lt;p&gt;Finally, the code adds a header to the page. Again, this is straight GDI+ code. The only interesting part here is when we specify the header text using the renderer&amp;#39;s &lt;b&gt;CurrentPage&lt;/b&gt; and &lt;b&gt;PageCount&lt;/b&gt; properties to create a &amp;quot;Page N of M&amp;quot; header. This would have been hard to do without the &lt;b&gt;PrintDocumentGridRenderer&lt;/b&gt; class.&lt;/p&gt;
&lt;p&gt;Finally, the &lt;b&gt;EndPrint&lt;/b&gt; event handler simply sets the renderer variable back to null. This is not strictly necessary, but is always good practice to clean up after yourself:&lt;/p&gt;&lt;pre&gt;&amp;nbsp;&amp;nbsp; void doc_EndPrint(object sender, PrintEventArgs e)&lt;br /&gt;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // done, we no longer need the PrintDocumentGridRenderer.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; _pdr = null;&lt;br /&gt;&amp;nbsp; } &lt;/pre&gt;
&lt;p&gt;That&amp;#39;s all there is to it. Feel free to play with the sample project that accompanies this article, and have fun adding all this great new functionality to your &lt;b&gt;C1FlexGrid&lt;/b&gt; applications.&lt;/p&gt;
&lt;p&gt;Download the C1FlexGrid sample project: &lt;a href="http://helpcentral.componentone.com/CS/blogs/windev/C1FlexGridPrintPreview/C1FlexGridPrintPreview_SampleProject.zip"&gt;C1FlexGridPrintPreview_SampleProject.zip&lt;/a&gt;&lt;br /&gt;&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://helpcentral.componentone.com/CS/aggbug.aspx?PostID=226637" width="1" height="1"&gt;</content><author><name>C1_GregL</name><uri>http://helpcentral.componentone.com/CS/members/C1_5F00_GregL/default.aspx</uri></author><category term="WinForms" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/WinForms/default.aspx" /><category term="PDF" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/PDF/default.aspx" /><category term="C1FlexGrid" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/C1FlexGrid/default.aspx" /><category term="Print Preview" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/Print+Preview/default.aspx" /></entry><entry><title>Charting in Reports for WinForms</title><link rel="alternate" type="text/html" href="/CS/blogs/windev/archive/2009/12/18/ChartingReports.aspx" /><id>/CS/blogs/windev/archive/2009/12/18/ChartingReports.aspx</id><published>2009-12-18T18:33:00Z</published><updated>2009-12-18T18:33:00Z</updated><content type="html">&lt;h1&gt;Charting in Reports for WinForms&lt;/h1&gt;
&lt;p&gt;This article describes &lt;b&gt;Aggregate Charting&lt;/b&gt;, one of the cool new features introduced in &lt;b&gt;ComponentOne Reports for WinForms&lt;/b&gt; in the 2009 v3 release.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;ComponentOne Reports for WinForms &lt;/b&gt;has always supported chart fields using its extensible custom field architecture. The &lt;b&gt;Chart &lt;/b&gt;field is implemented as a custom field in the C1.Win.C1Report.CustomFields.2.dll assembly, which is installed with the report designer application and is also included as a sample with full source code (&lt;b&gt;CustomFields&lt;/b&gt;). In this article, you’ll see how you can customize chart fields in reports using the &lt;b&gt;C1ReportDesigner&lt;/b&gt; application. The &lt;b&gt;C1ReportDesigner&lt;/b&gt; application is installed with both &lt;b&gt;ComponentOne Reports for WinForms&lt;/b&gt; and &lt;b&gt;ComponentOne Reports for WPF&lt;/b&gt;.&lt;/p&gt;
&lt;h2&gt;Charts in Flat Reports&lt;/h2&gt;
&lt;p&gt;Creating a simple chart is very easy. Here are the steps required:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open the &lt;b&gt;C1ReportDesigner&lt;/b&gt; application and create or open a report definition file.&lt;/li&gt;
&lt;li&gt;Add a &lt;b&gt;Chart &lt;/b&gt;field to the report, and then select it to show its properties in the designer&amp;#39;s property window.&lt;/li&gt;
&lt;li&gt;Set the chart&amp;#39;s &lt;b&gt;DataX&lt;/b&gt; property to the name of the field whose values should be displayed in the X axis (chart categories).&lt;/li&gt;
&lt;li&gt;Set the chart&amp;#39;s &lt;b&gt;DataY&lt;/b&gt; property to the name of the field whose values should be displayed in the Y axis (chart values).&lt;/li&gt;
&lt;li&gt;Optionally set additional properties such as &lt;b&gt;ChartType&lt;/b&gt; and &lt;b&gt;DataColor&lt;/b&gt;.&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;For example, the chart below was created based on the NorthWind Products table. In this case, the following properties were set:&lt;/p&gt;
&lt;p&gt;&lt;b&gt;DataX&lt;/b&gt; = &amp;quot;ProductName&amp;quot;&lt;br /&gt;&lt;b&gt;DataY&lt;/b&gt; = &amp;quot;UnitPrice&amp;quot; &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href="http://helpcentral.componentone.com/CS/blogs/windev/AggregateChart/FlatChart.png"&gt;&lt;img border="0" src="http://helpcentral.componentone.com/CS/blogs/windev/AggregateChart/FlatChart.png" alt="" /&gt;&lt;/a&gt;&amp;nbsp;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Note that for this chart type (&lt;b&gt;Bar&lt;/b&gt;), the value axis (where the &lt;b&gt;DataY&lt;/b&gt; field is displayed) is the horizontal one, and the category axis is the vertical one. &lt;/p&gt;
&lt;p&gt;In this case, a filter was applied to the data in order to limit the number of values shown. Without the filter, the chart would contain too many values and the vertical axis would not be readable.&lt;/p&gt;
&lt;h2&gt;Other Useful Chart Properties&lt;/h2&gt;
&lt;p&gt;In addition to the &lt;b&gt;DataX &lt;/b&gt;and &lt;b&gt;DataY &lt;/b&gt;properties mentioned above, the Chart object provides a few other properties that are commonly used:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;ChartType&lt;/b&gt;: This property allows you to select the type of chart to display. There are six options: &lt;b&gt;Bar &lt;/b&gt;(horizontal bars), &lt;b&gt;Column &lt;/b&gt;(vertical columns), &lt;b&gt;Scatter &lt;/b&gt;(X-Y values), &lt;b&gt;Line&lt;/b&gt;, &lt;b&gt;Area&lt;/b&gt;, and &lt;b&gt;Pie&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;DataColor&lt;/b&gt;: This property selects the color used to draw the bars, columns, areas, scatter symbols, and pie slices. If the chart contains multiple series, then the &lt;b&gt;Chart &lt;/b&gt;field automatically generates different shades of the selected color for each series. If you want to select specific colors for each series, use the &lt;b&gt;Palette &lt;/b&gt;property instead, and set its value to a semi-colon separated list containing the colors to use (for example &amp;quot;Red;Green;Blue&amp;quot;).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;FormatY, FormatX&lt;/b&gt;: These properties determine the format used to display the values along each axis. For example, setting &lt;b&gt;FormatY&lt;/b&gt; to &amp;quot;c&amp;quot; causes the &lt;b&gt;Chart &lt;/b&gt;field to format the values along the Y axis as currency values. This is analogous to the &lt;b&gt;Format &lt;/b&gt;property in regular report fields.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;XMin, XMax, YMin, YMax&lt;/b&gt;: These properties allow you to specify ranges for each axis. Setting any of them to -1 cause the &lt;b&gt;Chart &lt;/b&gt;to calculate the range automatically. For example, if you set the &lt;b&gt;YMax &lt;/b&gt;property to 100, then&amp;nbsp;any values higher than 100 will be truncated and won&amp;#39;t appear on the chart.&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;These properties apply to all chart types. There are a few additional properties that only apply to &lt;b&gt;Pie &lt;/b&gt;charts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;ShowPercentages&lt;/b&gt;: Each pie slice has a legend that shows the X value for the slice. If the &lt;b&gt;ShowPercentages &lt;/b&gt;property is set to true, the legend will also include a percentage value that indicates the size of the slice with respect to the pie. The percentage is formatted using the value specified by the &lt;b&gt;FormatY &lt;/b&gt;property. For example, if you set &lt;b&gt;FormatY&lt;/b&gt; to &amp;quot;p2&amp;quot;, then the legends will include the X value and the percentage with two decimal points (for example &amp;quot;North Region (15.23%)&amp;quot;).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;RadialLabels&lt;/b&gt;: This property specifies that instead of showing a legend on the right side of the chart, labels with connecting lines should be attached to each slice. This works well for pies with few slices (up to about ten).&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;The &lt;b&gt;Chart &lt;/b&gt;field is actually a wrapper for a &lt;b&gt;C1Chart &lt;/b&gt;control, which provides all the charting services and has an extremely rich object model of its own. If you want to customize the &lt;b&gt;Chart &lt;/b&gt;field even further, you can use the &lt;b&gt;ChartControl &lt;/b&gt;property to access the inner &lt;b&gt;C1Chart &lt;/b&gt;object using scripts.&lt;/p&gt;
&lt;p&gt;For example, the &lt;b&gt;Chart &lt;/b&gt;field does not have a property to control the position of the legend. But the &lt;b&gt;C1Chart&lt;/b&gt; control does, and you can access this property through the &lt;b&gt;ChartControl &lt;/b&gt;property. For example, the script below causes the chart legend to be positioned below the chart instead of on the right:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;#39; place legend below the chart&lt;br /&gt;chartField.ChartControl.Legend.Compass = &amp;quot;South&amp;quot;&lt;/code&gt; &lt;/p&gt;
&lt;p&gt;If you assign this script to the report&amp;#39;s &lt;b&gt;OnLoad &lt;/b&gt;property, the chart will look like the image below:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href="http://helpcentral.componentone.com/CS/blogs/windev/AggregateChart/PieChart.png"&gt;&lt;img border="0" src="http://helpcentral.componentone.com/CS/blogs/windev/AggregateChart/PieChart.png" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;The other properties used to create this chart are as follows:&lt;/p&gt;&lt;b&gt;ChartType&lt;/b&gt; = Pie&lt;br /&gt;&lt;b&gt;FormatY &lt;/b&gt;= &amp;quot;p1&amp;quot;&lt;br /&gt;&lt;b&gt;ShowPercentage&lt;/b&gt; = true&lt;b&gt;&lt;br /&gt;Palette &lt;/b&gt;= &amp;quot;Red;Gold;Orange;Beige;DarkGoldenrod;Goldenrod;&amp;quot; 
&lt;h2&gt;Charts with Multiple Series&lt;/h2&gt;
&lt;p&gt;To create charts with multiple series, simply set the &lt;b&gt;DataY&lt;/b&gt; property to a string that contains the names of each data field you want to chart, separated by semi-colons. For example, to create a chart showing product prices and discounts you would set the &lt;b&gt;DataY &lt;/b&gt;property as shown below:&lt;/p&gt;&lt;b&gt;DataY&lt;/b&gt; = &amp;quot;UnitPrice;Discount&amp;quot;&lt;br /&gt;
&lt;p&gt;If you want to specify the color used to display each series, set the &lt;b&gt;Palette&lt;/b&gt; property to a list of colors separated by semi-colons. For example, the value displayed below would cause the chart to show the UnitPrice&amp;quot; series in red and the &amp;quot;Discount&amp;quot; series in blue:&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Palette &lt;/b&gt;= &amp;quot;Red;Blue&amp;quot;&lt;/p&gt;
&lt;h2&gt;Series with Calculated Values&lt;/h2&gt;
&lt;p&gt;The &lt;b&gt;DataY &lt;/b&gt;property is not restricted to field names. The strings that specify the series are actually treated as full expressions, and are calculated like any regular field in the report.&lt;/p&gt;
&lt;p&gt;For example, to create a chart showing the actual price of each field you could set the &lt;b&gt;DataY &lt;/b&gt;property to the value shown below:&lt;br /&gt;&lt;b&gt;DataY&lt;/b&gt; = &amp;quot;UnitPrice * (1 - Discount)&amp;quot;&lt;/p&gt;
&lt;h2&gt;Charts in Grouped Reports&lt;/h2&gt;
&lt;p&gt;&lt;b&gt;Reports for WinForms &lt;/b&gt;allows you to create reports with multiple groups. For example, instead of listing all products in a single flat report, you could group products by category. Each group has a header and a footer section that allow you to display information about the group, including titles and subtotals, for example.&lt;/p&gt;
&lt;p&gt;If you add a chart to a group header, the chart will display only the data for the current group. By contrast, adding a chart to the report header or footer would include all the data in the report.&lt;/p&gt;
&lt;p&gt;To illustrate this, here is a diagram depicting a report definition as shown in the report designer and showing the effect of adding a &lt;b&gt;Chart &lt;/b&gt;field to the report header and to a group header:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href="http://helpcentral.componentone.com/CS/blogs/windev/AggregateChart/diagram1.png"&gt;&lt;img border="0" src="http://helpcentral.componentone.com/CS/blogs/windev/diagram1.png" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Continuing with the example mentioned above, if you added a chart to the group header and set the &lt;b&gt;DataX&lt;/b&gt; property to &amp;quot;ProductName&amp;quot; and the &lt;b&gt;DataY&lt;/b&gt; property to &amp;quot;UnitPrice&amp;quot;, the final report would contain one chart for each category, and each chart would display the unit prices for the products in that category.&lt;/p&gt;
&lt;p&gt;The images below show screenshots of the report described above with the group headers, the charts they contain, and a few detail records to illustrate:&lt;/p&gt;
&lt;blockquote&gt;&lt;a href="http://helpcentral.componentone.com/CS/blogs/windev/AggregateChart/Grouped1.png"&gt;&lt;img border="0" src="http://helpcentral.componentone.com/CS/blogs/windev/AggregateChart/Grouped1.png" alt="" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/blockquote&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote&gt;&lt;b&gt;&lt;i&gt;Chart showing unit prices for products in the &amp;quot;Beverages&amp;quot; category&lt;/i&gt;&lt;/b&gt;&lt;br /&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;a href="http://helpcentral.componentone.com/CS/blogs/windev/AggregateChart/Grouped2.png"&gt;&lt;img border="0" src="http://helpcentral.componentone.com/CS/blogs/windev/AggregateChart/Grouped2.png" alt="" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;b&gt;&lt;i&gt;Chart showing unit prices for products in the &amp;quot;Condiments&amp;quot; category&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;b&gt;DataX &lt;/b&gt;= &amp;quot;Product Name&amp;quot;&lt;br /&gt;&lt;b&gt;DataY &lt;/b&gt;= &amp;quot;Unit Price&amp;quot;&lt;/p&gt;
&lt;p&gt;Because the chart automatically selects the data based on the scope of the section that contains it, creating charts in grouped reports is very easy.&lt;/p&gt;
&lt;h2&gt;Aggregate Charts&lt;/h2&gt;
&lt;p&gt;The &lt;b&gt;Chart &lt;/b&gt;field included with the 2009 v3 release of &lt;b&gt;Reports for WinForms&lt;/b&gt; has a powerful new feature called &amp;quot;aggregated charting&amp;quot;. This feature allows you to create charts that automatically aggregate data values (&lt;b&gt;DataY&lt;/b&gt;) that have the same category (&lt;b&gt;DataX&lt;/b&gt;) using an aggregate function of your choice (sum, average, standard deviation, and so on).&lt;/p&gt;
&lt;p&gt;To illustrate this feature, consider an &amp;quot;Invoices&amp;quot; report that groups data by country, customer, and order ID. The general outline for the report is as follows:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href="http://helpcentral.componentone.com/CS/blogs/windev/AggregateChart/diagram2.png"&gt;&lt;img border="0" src="http://helpcentral.componentone.com/CS/blogs/windev/AggregateChart/diagram2.png" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Now imagine that you would like to add a chart to each &lt;b&gt;Country &lt;/b&gt;header displaying the total value of all orders placed by each customer in the current country.&lt;/p&gt;
&lt;p&gt;You would start by adding a &lt;b&gt;Chart &lt;/b&gt;field to the &amp;quot;Country&amp;quot; header section and then setting the &lt;b&gt;DataX&lt;/b&gt; and &lt;b&gt;DataY&lt;/b&gt; properties as follows:&lt;/p&gt;
&lt;p&gt;&lt;b&gt;DataX&lt;/b&gt; = &amp;quot;CustomerName&amp;quot;&lt;br /&gt;&lt;b&gt;DataY&lt;/b&gt; = &amp;quot;ExtendedPrice&amp;quot;&lt;/p&gt;
&lt;p&gt;This would not work. The data for each country usually includes several records for each customer, and the chart would create one data point for each record. The chart would not be able to guess that you really want to add the values for each customer into a single data point.&lt;/p&gt;
&lt;p&gt;To address this scenario, we added an &lt;b&gt;Aggregate &lt;/b&gt;property to the &lt;b&gt;Chart &lt;/b&gt;field. This property tells the chart how to aggregate values that have the same category into a single point in the chart. The &lt;b&gt;Aggregate &lt;/b&gt;property can be set to perform any of the common aggregation functions on the data: sum, average, count, maximum, minimum, standard deviation, and variance.&lt;/p&gt;
&lt;p&gt;Continuing with our example, we can now simply set the chart&amp;#39;s &lt;b&gt;Aggregate &lt;/b&gt;property to &amp;quot;Sum&amp;quot;. This will cause the chart to add all &amp;quot;ExtendedPrice&amp;quot; values for records that belong to the same customer into a single data point. The result is shown below:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href="http://helpcentral.componentone.com/CS/blogs/windev/AggregateChart/Invoices1.png"&gt;&lt;img border="0" src="http://helpcentral.componentone.com/CS/blogs/windev/AggregateChart/Invoices1.png" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Notice how each customer appears only once. The values shown on the chart correspond to the sum of the &amp;quot;ExtendedPrice&amp;quot; values for all fields with the same &amp;quot;Customer&amp;quot;.&lt;/p&gt;
&lt;p&gt;Because the chart appears in the &amp;quot;Country&amp;quot; header field, it is repeated for each country, showing all the customers in that country.&lt;/p&gt;
&lt;p&gt;If you place the chart in the report header section, it will aggregate data over the entire report. For example, suppose you want to start the &amp;quot;Invoices&amp;quot; report with a chart that shows the total amount ordered by each salesperson. To accomplish this, you would add a &lt;b&gt;Chart &lt;/b&gt;field to the report header section and would set the following properties:&lt;/p&gt;
&lt;p&gt;&lt;b&gt;DataX &lt;/b&gt;= &amp;quot;Salesperson&amp;quot;&lt;br /&gt;&lt;b&gt;DataY &lt;/b&gt;= &amp;quot;ExtendedPrice&amp;quot;&lt;br /&gt;&lt;b&gt;Aggregate &lt;/b&gt;= &amp;quot;Sum&amp;quot;&lt;/p&gt;
&lt;p&gt;The image below shows the resulting chart:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href="http://helpcentral.componentone.com/CS/blogs/windev/AggregateChart/Invoices2.png"&gt;&lt;img border="0" src="http://helpcentral.componentone.com/CS/blogs/windev/AggregateChart/Invoices2.png" alt="" /&gt;&lt;/a&gt;&amp;nbsp;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Since the chart is placed in the report header section, the values displayed include all countries and all customers. If you moved the chart field from the report header to the &amp;quot;Country&amp;quot; group header, you would obtain a similar chart for each country, showing the total amounts sold by each salesperson in that country.&lt;/p&gt;
&lt;p&gt;Aggregate charting is a powerful, yet simple and easy-to-use feature. We hope you enjoy it and use it to create spectacular reports!&lt;/p&gt;
&lt;h2&gt;Download&lt;/h2&gt;
&lt;p&gt;&lt;b&gt;ComponentOne Reports for WinForms&lt;/b&gt; is part of &lt;b&gt;ComponentOne Studio for WinForms&lt;/b&gt;. If you have not already, download the latest trial version at &lt;a href="http://www.componentone.com/SuperProducts/StudioWinForms/"&gt;http://www.componentone.com/SuperProducts/StudioWinForms/&lt;/a&gt; so you can start creating your reports.&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://helpcentral.componentone.com/CS/aggbug.aspx?PostID=226072" width="1" height="1"&gt;</content><author><name>C1_GregL</name><uri>http://helpcentral.componentone.com/CS/members/C1_5F00_GregL/default.aspx</uri></author><category term="WinForms" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/WinForms/default.aspx" /><category term="C1ReportDesigner" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/C1ReportDesigner/default.aspx" /><category term="reporting" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/reporting/default.aspx" /><category term="C1Report" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/C1Report/default.aspx" /><category term="C1Chart" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/C1Chart/default.aspx" /></entry><entry><title>A Visual SQL Query Designer</title><link rel="alternate" type="text/html" href="/CS/blogs/windev/archive/2009/11/06/QueryDesigner.aspx" /><id>/CS/blogs/windev/archive/2009/11/06/QueryDesigner.aspx</id><published>2009-11-06T15:29:00Z</published><updated>2009-11-06T15:29:00Z</updated><content type="html">&lt;h1&gt;A Visual SQL Query Designer&lt;/h1&gt;
&lt;ul class="download"&gt;
&lt;li&gt;&lt;a href="http://helpcentral.componentone.com/CS/blogs/windev/QueryDesigner/QueryDesignerSample.zip"&gt;Download demo project - 48 KB&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://helpcentral.componentone.com/CS/blogs/windev/QueryDesigner/QueryDesignerSource.zip"&gt;Download source (C#) - 85 KB&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://helpcentral.componentone.com/CS/blogs/windev/QueryDesigner/QueryDesignerSource_VB.zip"&gt;Download source (VB) - 81 KB&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;&lt;img style="WIDTH:600px;HEIGHT:381px;" title="Query Designer" alt="Query Designer" src="http://helpcentral.componentone.com/CS/blogs/windev/QueryDesigner/querydesigner.png" width="600" height="381" /&gt;&lt;/p&gt;
&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;This article describes the implementation of a &lt;code&gt;QueryDesignerDialog&lt;/code&gt; class that allows users to create SQL queries based on a given OleDb connection string.&lt;/p&gt;
&lt;p&gt;The designer is similar to the ones found in database tools such as the SQL Server Management Studio and Microsoft Access. It allows end users to build SQL queries with support for sorting, grouping, and filtering.&lt;/p&gt;
&lt;p&gt;The main limitation of the &lt;code&gt;QueryDesignerDialog&lt;/code&gt; is that it does not parse existing SQL statements. This is a one-way tool, you can use it to create new queries but not to edit existing ones. Also, it only supports OleDb data sources, which includes Sql Server and Access. Future versions may address these limitations.&lt;/p&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;The first version of the &lt;code&gt;QueryDesignerDialog&lt;/code&gt; was written for use with a Report Designer application. I could not find a tool (free or commercial) to do the job the way I wanted, so I decided to write it myself. After that, I re-used it in a few other applications and thought it might be useful to others as well.&lt;/p&gt;
&lt;h2&gt;Using the Code&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;QueryDesignerDialog&lt;/code&gt; has two main properties:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;ConnectionString&lt;/b&gt;: Gets or sets the OleDb connection string used to retrieve the database schema with the list of tables, views, fields, and relations from which the query will be built. &lt;/li&gt;
&lt;li&gt;&lt;b&gt;SelectStatement&lt;/b&gt;: Gets the SQL statement designed by the user. For now, this is a read-only property. The dialog cannot be used to edit existing SQL statements. Perhaps this will be added in future versions.&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;The code snippet below shows the &lt;code&gt;QueryDesignerDialog&lt;/code&gt; is typically used. You assign it a connection string, show the dialog, and read back the SQL query:&lt;/p&gt;&lt;pre&gt;// create the QueryDesignerDialog
using (var dlg = new QueryDesignerDialog())
{
  // set the connection string
  dlg.ConnectionString = ConnectionString;
  
  // show the dialog
  if (dlg.ShowDialog(this) == DialogResult.OK)
  {
    // get the new Sql query and do something with it
    string newSql = dlg.SelectStatement;
    DoSomething(newSql);
  }
}       &amp;nbsp;&lt;/pre&gt;
&lt;h2&gt;Implementation&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;QueryDesignerDialog&lt;/code&gt; relies on two important helper classes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;OleDbSchema&lt;/b&gt;: This class extends the ADO.NET &lt;code&gt;DataSet&lt;/code&gt; class. It takes an &lt;code&gt;OleDb&lt;/code&gt; connection string and fills the &lt;code&gt;DataSet&lt;/code&gt; with all the tables, views, columns, relations, and constraints defined in the database (it does not retrieve any data). Applications can then expose these elements in a visual UI to preview the data, create and edit queries, and so on. The &lt;code&gt;OleDbSchema&lt;/code&gt; class also provides utility methods for checking table types, encoding their names with brackets when necessary, managing stored procedure parameters, and so on.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;QueryBuilder&lt;/b&gt;: This class uses the &lt;code&gt;OleDbSchema&lt;/code&gt; class and maintains a list of query fields with properties that define sorting, grouping, etc. It is also responsible for building SQL statements based on the query fields and on the database schema.&lt;/li&gt;&lt;/ul&gt;
&lt;h3&gt;Selecting a Connection String&lt;/h3&gt;
&lt;p&gt;In order to use the &lt;code&gt;QueryDesignerDialog&lt;/code&gt;, you need an OleDb connection string. Some applications may use a list of pre-defined connection strings, others allow the user to create the connection string at run time. The sample included with this article falls in the second category.&lt;/p&gt;
&lt;p class="style1"&gt;The &lt;code&gt;OleDbConnString&lt;/code&gt; class included in the code provides utilities for dealing with connection strings. The main methods in that class are &lt;code&gt;GetConnectionString&lt;/code&gt; and &lt;code&gt;EditConnectionString&lt;/code&gt;, both of which show the &lt;code&gt;DataLinks&lt;/code&gt; dialog used to create or edit connection strings:&lt;/p&gt;&lt;pre&gt;string newConnString = OleDbConnString.GetConnectionString(this);&lt;/pre&gt;&lt;pre class="style1"&gt;string editConnString = OleDbConnString.EditConnectionString(this, editConnString);&lt;/pre&gt;
&lt;p&gt;These methods rely on the following system assemblies which must be referenced by the project:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;OLEDB32.dll&lt;/b&gt;: Contains the &lt;code&gt;DataLinks&lt;/code&gt; class (which used to be in MSDASC.DLL). This file can be found at &lt;i&gt;C:\Program Files\Common Files\System\Ole DB\OLEDB32.DLL&lt;/i&gt;.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ADODB.dll&lt;/b&gt;: This is required to read the COM object passed back from DataLinks. This file can be found at &lt;i&gt;C:\Program Files\Microsoft.NET\Primary Interop Assemblies\ADODB.DLL&lt;/i&gt;.&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;Once the user has picked a connection string, most applications will save it in a list for later re-use. Because the connection strings tend to be quite long, showing them to users may be challenging, so the &lt;code&gt;OleDbConnString&lt;/code&gt; class provides a &lt;code&gt;TrimConnectionString&lt;/code&gt; method that shortens the connection strings for display purposes, keeping only the provider and data source parts. The sample application provided with the article uses &lt;code&gt;TrimConnectionString&lt;/code&gt; to display recently used connection strings in an owner-drawn &lt;code&gt;ComboBox&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Retrieving the database Schema&lt;/h3&gt;
&lt;p&gt;When you assign a connection string to a &lt;code&gt;QueryDesignerDialog&lt;/code&gt;, it starts by retrieving the database schema so it can show the user a list of the tables and views available for use in the query. This job is done by the &lt;code&gt;OleDbSchema&lt;/code&gt; class mentioned above. The code looks like this: &lt;/p&gt;&lt;pre&gt;// get schema for the new connection string
OleDbSchema schema = OleDbSchema.GetSchema(connectionString);&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;OleDbSchema&lt;/code&gt; class extends the ADO.NET &lt;code&gt;DataSet&lt;/code&gt; class. You can use it to enumerate the elements available in the database, including tables, views, stored procedures, fields, relations, and constraints. If the connection string is invalid, or if some error occurs while getting the schema, then &lt;code&gt;GetSchema&lt;/code&gt; returns null.&lt;/p&gt;
&lt;p&gt;Some of the information retrieved from the database is stored in the &lt;code&gt;ExtendedProperties&lt;/code&gt; property of the tables and fields. For example, views and stored procedures in the database are represented by &lt;code&gt;DataTable&lt;/code&gt; objects and can be identified by their &lt;code&gt;ExtendedProperties[TABLE_TYPE]&lt;/code&gt; value. The &lt;code&gt;OleDbSchema&lt;/code&gt; class provides helper methods that deal with this so callers don&amp;#39;t have to. For example, the &lt;code&gt;GetTableType&lt;/code&gt; method returns a value that indicates whether a &lt;code&gt;DataTable&lt;/code&gt; in an &lt;code&gt;OleDbSchema&lt;/code&gt; represents a regular table, a view, or a stored procedure.&lt;/p&gt;
&lt;p&gt;The implementation of the &lt;code&gt;OleDbSchema&lt;/code&gt; class is based on the &lt;code&gt;OleDbConnection.GetOleDbSchemaTable&lt;/code&gt; method. This method allows you to retrieve tables, views, stored procedures, relations, and constraints defined for the connection. Once you have a table, the &lt;code&gt;OleDbDataAdapter.FillSchema&lt;/code&gt; method is used to retrieve the fields. If you are interested in the details, please refer to the source code.&lt;/p&gt;
&lt;p&gt;The example below shows how the &lt;code&gt;QueryDesignerDialog&lt;/code&gt; class populates a &lt;code&gt;TreeView&lt;/code&gt; control with the tables and views available in the database:&lt;/p&gt;&lt;pre&gt;// update table tree to reflect new connection string
void UpdateTableTree()
{
  // initialize table tree
  TreeNodeCollection nodes = _treeTables.Nodes;
  nodes.Clear();
  var ndTables = new TreeNode(Properties.Resources.Tables, 0, 0);
  var ndViews = new TreeNode(Properties.Resources.Views, 1, 1);

  // populate using current schema
  if (Schema != null)
  {
    // populate the tree
    _treeTables.BeginUpdate();
    foreach (DataTable dt in Schema.Tables)
    {
      // create new node, save table in tag property
      var node = new TreeNode(dt.TableName);
      node.Tag = dt;

      // add new node to appropriate parent
      switch (OleDbSchema.GetTableType(dt))
      {
        case TableType.Table:
          ndTables.Nodes.Add(node);
          node.ImageIndex = node.SelectedImageIndex = 0;
          AddDataColumns(node, dt);
          break;
        case TableType.View:
          ndViews.Nodes.Add(node);
          node.ImageIndex = node.SelectedImageIndex = 1;
          AddDataColumns(node, dt);
          break;
        }
      }

      // add non-empty nodes to tree
      foreach (TreeNode nd in new TreeNode[] { ndTables, ndViews })
      {
        if (nd.Nodes.Count &amp;gt; 0)
        {
          nd.Text = string.Format(&amp;quot;{0} ({1})&amp;quot;, nd.Text, nd.Nodes.Count);
          nodes.Add(nd);
        }
      }

      // expand tables node
      ndTables.Expand();

      // done
      _treeTables.EndUpdate();
  }
}
void AddDataColumns(TreeNode node, DataTable dt)
{
  foreach (DataColumn col in dt.Columns)
  {
    var field = node.Nodes.Add(col.ColumnName);
    field.Tag = col;
    field.ImageIndex = 2;
    field.SelectedImageIndex = 2;
  }
}&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;OleDbSchema&lt;/code&gt; class is used internally by the &lt;code&gt;QueryDesignerDialog&lt;/code&gt; class, but it is public and can also be used directly by applications that need access to schemas. The sample application included with the article uses it to populate a list with all the tables, views, and stored procedures in the database.&lt;/p&gt;
&lt;h3&gt;Building SQL Queries&lt;/h3&gt;
&lt;p&gt;Once the database schema is available, we can use it to build queries. This is done by the &lt;code&gt;QueryBuilder&lt;/code&gt; class, which maintains a list of &lt;code&gt;QueryField&lt;/code&gt; objects and generates SQL statements. The main properties of the &lt;code&gt;QueryBuilder&lt;/code&gt; class are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;ConnectionString&lt;/b&gt;: Used to retrieve the database schema. The schema defines the tables and fields used in the query, and also the relations between the tables, which are needed to build the SQL JOIN statements.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;QueryFields&lt;/b&gt;: A collection of &lt;code&gt;QueryField&lt;/code&gt; objects used to build the query. Each &lt;code&gt;QueryField&lt;/code&gt; object represents a database field or an expression, and has properties that define sorting, grouping, aliasing, and so on. This is a bindable collection suitable for display in a &lt;code&gt;DataGridView&lt;/code&gt; control for example.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Sql&lt;/b&gt;: The SQL select statement built to represent the current state of the &lt;code&gt;QueryFields&lt;/code&gt; collection.&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;The &lt;code&gt;Sql&lt;/code&gt; property is read-only. It builds the Sql query in parts, based on the &lt;code&gt;QueryFields&lt;/code&gt; collection.&lt;/p&gt;
&lt;p&gt;First, the SELECT clause is built by scanning the fields and outputting the proper table/view and column names. Next, the &lt;code&gt;QueryFields&lt;/code&gt; collection is analyzed to determine how the tables are connected. This allows the &lt;code&gt;QueryBuilder&lt;/code&gt; to create the FROM clause with the required JOIN statements, which is by far the most complicated and interesting part of the class. Finally, the ORDER BY and WHERE clauses are built based on the properties of the &lt;code&gt;QueryField&lt;/code&gt; objects.&lt;/p&gt;
&lt;p&gt;To build the FROM clause of the SQL statement, the &lt;code&gt;QueryBuilder&lt;/code&gt; class starts by building a list of tables so that each table on the list is related to the next one. This is accomplished by the &lt;code&gt;InsertRelatedTable&lt;/code&gt; method. Next, it scans the list to find the relation that connects each table with the following one. Each relation is then used to build the corresponding JOIN statement.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;QueryField&lt;/code&gt; class contains the information that defines each field in the query. It has the following properties:&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Column&lt;/b&gt;: String that specifies the name of a column within a table (e.g. &amp;quot;FirstName&amp;quot;) or an expression (e.g. &amp;quot;LEFT(FirstName, 2)&amp;quot;.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Alias&lt;/b&gt;: Optional string used to identify the field instead of the &lt;code&gt;Column&lt;/code&gt; value. If provided, this value is used as the column name on the query results table. For example, if the &lt;code&gt;Column&lt;/code&gt; property is set to &amp;quot;LEFT(FirstName, 1)&amp;quot; and the &lt;code&gt;Alias&lt;/code&gt; property is set to &amp;quot;FirstInitial&amp;quot;, the output table will contain a column named &amp;quot;FirstInitial&amp;quot;.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Table&lt;/b&gt;: String that identifies the table that contains the column. This property is read-only; it is provided only for binding purposes (so the table names appear on the field grid described later).&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Output&lt;/b&gt;: Boolean value that determines whether the field should be included in the output table. This is used to hide fields that are needed to define the query but should not appear on the output (e.g. fields used in calculations or to provide connections between related tables). &lt;/p&gt;
&lt;p&gt;&lt;b&gt;GroupBy&lt;/b&gt;: Aggregate to use when grouping the field. This column is used only if the &lt;code&gt;QueryBuilder.GroupBy&lt;/code&gt; property is set to true. In this case, the original data records are grouped and represented by an aggregate such as a sum or average. For example, to create a query showing the average product price per category, you would use two fields: &amp;quot;CategoryName&amp;quot;,&amp;nbsp; with &lt;code&gt;GroupBy&lt;/code&gt; set to &lt;b&gt;GroupBy&lt;/b&gt;; and &amp;quot;ProductPrice&amp;quot;, with &lt;code&gt;GroupBy&lt;/code&gt; set to &lt;b&gt;Average&lt;/b&gt;.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Sort&lt;/b&gt;: Specifies whether the field should be used to sort the output in ascending or descending order.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Filter&lt;/b&gt;: A filter expression used to select the records included in the output. If provided, the expression should be of the format [OPERATOR] [VALUE] or BETWEEN [VALUE1] AND [VALUE2]. To reduce the possibility of syntax errors when specifying the &lt;code&gt;Filter&lt;/code&gt; value, the &lt;code&gt;QueryDesignerDialog&lt;/code&gt; class provides an editor for this property instead of allowing users to type the expressions directly.&lt;/p&gt;
&lt;h3&gt;Designing SQL Queries&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;QueryDesignerDialog&lt;/code&gt; class provides the user interface for creating queries. It uses the &lt;code&gt;QueryBuilder&lt;/code&gt; class described above and adds the following UI elements:&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Table/View tree&lt;/b&gt;: This is a &lt;code&gt;TreeView&lt;/code&gt; control containing all the tables and views in the data source and all the fields within each table/view. Fields can be double-clicked or dragged onto the &lt;code&gt;QueryField&lt;/code&gt; grid to be added to the query. The &lt;code&gt;TreeView&lt;/code&gt; has a context menu that allows users to remove specific tables/views from the list or navigate to tables that are related to the one that is currently selected.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;QueryField grid&lt;/b&gt;: This is a &lt;code&gt;DataGridView&lt;/code&gt; control that shows the fields currently included in the query and their properties. Users may reorder the fields by dragging the grid rows, remove fields by deleting rows, and edit fields directly on the grid.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Sql view&lt;/b&gt;: This is a &lt;code&gt;TextBox&lt;/code&gt; control that shows the SQL statement as the user adds and edits individual fields on the grid. The &lt;code&gt;TextBox&lt;/code&gt; is read-only. Users can see the SQL statement and copy it to the clipboard, but they cannot change it by typing on the &lt;code&gt;TextBox&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;ToolStrip&lt;/b&gt;: The &lt;code&gt;ToolStrip&lt;/code&gt; on top of the dialog contains buttons for toggling the query&amp;#39;s &lt;code&gt;GroupBy&lt;/code&gt; property, editing query properties, checking the SQL syntax, previewing the query results, and for clearing the query.&lt;/p&gt;
&lt;p&gt;The Table/View tree is the element that deviates the most from the traditional query design tools available in tools such as SQL Server Management Studio and Microsoft Access. It shows all the tables by default, instead of asking users to add and remove them individually. Tables and views are shown as tree nodes, rather than as small floating lists. The tree-based UI is substantially simpler. The main drawback it has is that the connections between tables are not readily visible to the user. This is somewhat alleviated by the context menu that shows related tables on demand.&lt;/p&gt;
&lt;p&gt;The image below shows the &lt;code&gt;QueryDesignerDialog&lt;/code&gt; in action, creating a query against the popular AdventureWorks database:&lt;/p&gt;
&lt;p&gt;&lt;img style="WIDTH:600px;HEIGHT:381px;" title="Query Designer" alt="Query Designer" src="http://helpcentral.componentone.com/CS/blogs/windev/QueryDesigner/querydesigner.png" width="600" height="381" /&gt;&lt;/p&gt;
&lt;h3&gt;QueryField grid implementation&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;QueryField&lt;/code&gt; grid is a &lt;code&gt;DataGridView&lt;/code&gt; that displays a list of &lt;code&gt;QueryField&lt;/code&gt; objects. By default, the &lt;code&gt;DataGridView&lt;/code&gt; uses text boxes for editing non-boolean cells and check boxes for booleans. This is not ideal for editing values that are enumerations, such as the &lt;code&gt;Sort&lt;/code&gt; and &lt;code&gt;GroupBy&lt;/code&gt; properties of the &lt;code&gt;QueryField&lt;/code&gt; class. Those are much easier to edit with combo box controls instead. Also, we wanted to use a custom editor for the &lt;code&gt;Filter&lt;/code&gt; field, which is of type string but has formatting requirements that are better handled with a custom editor.&lt;/p&gt;
&lt;p&gt;These requirements seem very common, so I think showing the code here might be useful to some developers. The code below shows how you can replace regular &lt;code&gt;DataGridView&lt;/code&gt; columns with combo box ones for fields that are enumerations.&lt;/p&gt;&lt;pre&gt;// replace regular grid columns combo box columns for enum types
void FixGridColumns()
{
  for (int i = 0; i &amp;lt; _grid.Columns.Count; i++)
  {
    var col = _grid.Columns[i];
    if (col.ValueType.IsEnum)
    {
      // create combo column for enum types
      var cmb = new DataGridViewComboBoxColumn();
      cmb.ValueType = col.ValueType;
      cmb.Name = col.Name;
      cmb.DataPropertyName = col.DataPropertyName;
      cmb.HeaderText = col.HeaderText;
      cmb.DisplayStyleForCurrentCellOnly = true;
      cmb.DataSource = Enum.GetValues(col.ValueType);
      cmb.Width = col.Width;

      // replace original column with new combo column
      _grid.Columns.RemoveAt(i);
      _grid.Columns.Insert(i, cmb);
    }
  }
}
&lt;/pre&gt;
&lt;p&gt;The sample also replaces the &amp;quot;Filter&amp;quot; column with one that shows a button instead of a text box. Clicking the button brings up a filter editor dialog which can be used to edit the filter value. I would rather give users a choice, allowing them to type filter values directly into the cell &lt;b&gt;or&lt;/b&gt; click a button on the right of the cell to show the editor dialog. That is in the to-do list for a future version.&lt;/p&gt;
&lt;h2&gt;Sample Application&lt;/h2&gt;
&lt;p&gt;The sample application included with this article is a dialog that allows users to select a connection string, see all the tables, views, and stored procedures in the corresponding database, create queries against the database, and see the corresponding data. It could be used as a data source selection tool in applications such as report designers (which is actually why was written in the first place).&lt;/p&gt;
&lt;p&gt;The image below shows the sample application in action:&lt;/p&gt;
&lt;p&gt;&lt;img style="WIDTH:508px;HEIGHT:411px;" title="Sample Application" alt="Sample Application" src="http://helpcentral.componentone.com/CS/blogs/windev/QueryDesigner/querydesigner_sampleapp.png" width="508" height="411" /&gt;&lt;/p&gt;
&lt;p&gt;The application has a &lt;code&gt;ToolStrip&lt;/code&gt; along the top of the main dialog.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;ToolStrip&lt;/code&gt; contains a &lt;code&gt;ComboBox&lt;/code&gt; that provides a list of recently used connection strings and allows users to type or paste connection string values.&lt;/p&gt;
&lt;p&gt;The dropdown part of the &lt;code&gt;ComboBox&lt;/code&gt; is owner-drawn to show a trimmed version of the connections strings which are easier to read than the full version. The owner-draw code uses the &lt;code&gt;TrimConnectionString&lt;/code&gt; method in the &lt;code&gt;OleDbConnString&lt;/code&gt; class as shown below:&lt;/p&gt;&lt;pre&gt;// trim items in combo box (they&amp;#39;re very long)
void cmb_DrawItem(object sender, DrawItemEventArgs e)
{
  var fmt = new StringFormat();
  fmt.LineAlignment = StringAlignment.Center;
  fmt.Trimming = StringTrimming.EllipsisPath;

  var text = (string)_cmbConnString.Items[e.Index];
  &lt;b&gt;text = OleDbConnString.TrimConnectionString(text);&lt;/b&gt;

  var brush = (e.State &amp;amp; DrawItemState.Selected) != 0
    ? SystemBrushes.HighlightText
    : SystemBrushes.WindowText;

  e.DrawBackground();
  e.Graphics.DrawString(text, _cmbConnString.Font, brush, e.Bounds, fmt);
  e.DrawFocusRectangle();
}&lt;/pre&gt;
&lt;p&gt;The list of recent connection strings is saved as an application setting so it can be reused across sessions.&lt;/p&gt;
&lt;p&gt;The button next to the ComboBox allows users to create new connection strings using the familiar &amp;quot;DataLink&amp;quot; dialog. The button uses the &lt;code&gt;EditConnectionString&lt;/code&gt; method in the &lt;code&gt;OleDbConnString&lt;/code&gt; class as shown below:&lt;/p&gt;&lt;pre&gt;// pick a new connection
void _btnConnPicker_Click(object sender, EventArgs e)
{
  // release mouse capture to avoid wait cursor
  _toolStrip.Capture = false;

  // get starting connection string
  // (if empty or no provider, start with SQL source as default)
  string connString = _cmbConnString.Text;
  if (string.IsNullOrEmpty(connString) || 
      connString.IndexOf(&amp;quot;provider=&amp;quot;, StringComparison.OrdinalIgnoreCase) &amp;lt; 0)
  {
    connString = &amp;quot;Provider=SQLOLEDB.1;&amp;quot;;
  }

  // let user change it
  &lt;b&gt;ConnectionString = OleDbConnString.EditConnectionString(this, connString);&lt;/b&gt;
}&lt;/pre&gt;
&lt;p&gt;This code invokes the &amp;quot;DataLink&amp;quot; dialog seen below: &lt;/p&gt;
&lt;p&gt;&lt;img style="WIDTH:377px;HEIGHT:473px;" title="DataLinks" alt="DataLinks" src="http://helpcentral.componentone.com/CS/blogs/windev/QueryDesigner/querydesigner_datalink.png" width="377" height="473" /&gt;&lt;/p&gt;
&lt;p&gt;The next button (with a magic wand image) invokes the &lt;code&gt;QueryDesignerDialog&lt;/code&gt; which allows users to design SQL queries. Once the query is ready, it is shown in a &lt;code&gt;TextBox&lt;/code&gt; on the second tab of the main form. The code that invokes the &lt;code&gt;QueryDesignerDialog&lt;/code&gt; looks like this:&lt;/p&gt;&lt;pre&gt;// invoke SQL builder
void _btnSqlBuilder_Click(object sender, EventArgs e)
{
  &lt;b&gt;using (var dlg = new QueryDesignerDialog())&lt;/b&gt;
  {
    dlg.Font = this.Font;
    &lt;b&gt;dlg.ConnectionString = ConnectionString;&lt;/b&gt;
    if (dlg.ShowDialog(this) == DialogResult.OK)
    {
      &lt;b&gt;_txtSql.Text = dlg.SelectStatement;&lt;/b&gt;
      _tab.SelectedTab = _pgSql;
      UpdateUI();
    }
  }
}&lt;/pre&gt;
&lt;p&gt;The code creates a &lt;code&gt;QueryDesignerDialog&lt;/code&gt;, initializes its &lt;code&gt;ConnectionString&lt;/code&gt; property, then shows the dialog and retrieves the results by reading the dialog&amp;#39;s &lt;code&gt;SelectStatement&lt;/code&gt; property.&lt;/p&gt;
&lt;p&gt;The last button (with a preview image) loads the data from the currently selected source (table, view, stored procedure, or SQL statement) into a &lt;code&gt;DataTable&lt;/code&gt; and shows the table on a modal dialog. The implementation is given below:&lt;/p&gt;&lt;pre&gt;// preview data for currently selected node
void PreviewData()
{
  // create table to load with data and display
  var dt = new DataTable(&amp;quot;Query&amp;quot;);

  // if a table/view is selected, get table name and parameters
  if (_tab.SelectedTab == _pgTables)
  {
    // get table/view name
    var table = _treeTables.SelectedNode.Tag as DataTable;
    dt.TableName = table.TableName;

    // get view parameters if necessary
    var parms = OleDbSchema.GetTableParameters(table);
    if (parms != null &amp;amp;&amp;amp; parms.Count &amp;gt; 0)
    {
      var dlg = new ParametersDialog(parms);
      dlg.Font = Font;
      if (dlg.ShowDialog(this) != DialogResult.OK)
      {
        return;
      }
    }
  }

  // get data
  try
  {
    using (var da = new OleDbDataAdapter(SelectStatement, ConnectionString))
    {
      // get data
      da.Fill(0, MAX_PREVIEW_RECORDS, dt);

      // show the data
      using (var dlg = new DataPreviewDialog(dt, Font, Size))
      {
        dlg.ShowDialog(this);
      }
    }
  }
  catch (Exception x)
  {
    Warning(Properties.Resources.ErrGettingData, x.Message);
  }
}&lt;/pre&gt;
&lt;p&gt;The first part of the code handles the case where the &lt;code&gt;TreeView&lt;/code&gt; page is selected. It gets the name of the table that is currently selected and uses the &lt;code&gt;ParametersDialog&lt;/code&gt; helper class to prompt the user for any required parameters. The parameters entered by the user are stored as extended properties of the selected table.&lt;/p&gt;
&lt;p&gt;Next, the code builds an &lt;code&gt;OleDbDataAdapter&lt;/code&gt; to read the actual data. The parameters are given by the &lt;code&gt;SelectStatement&lt;/code&gt; and &lt;code&gt;ConnectionString&lt;/code&gt; properties. The &lt;code&gt;SelectStatement&lt;/code&gt; is a SQL string that reflects the current user selection. It could be either the node currently selected on the &lt;code&gt;TreeView&lt;/code&gt; or the custom SQL generated with the &lt;code&gt;QueryDesignerDialog&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This is the code that implements the &lt;code&gt;SelectStatement&lt;/code&gt; property:&lt;/p&gt;&lt;pre&gt;public string SelectStatement
{
  get
  {
    // table/view/sproc
    if (_tab.SelectedTab == _pgTables)
    {
      var nd = _treeTables.SelectedNode;
      return nd == null || nd.Tag == null || _schema == null
        ? string.Empty
        : &lt;b&gt;OleDbSchema.GetSelectStatement(nd.Tag as DataTable)&lt;/b&gt;;
    }
    else // explicit sql statement
    {
      return _txtSql.Text;
    }
  }
}        &lt;/pre&gt;
&lt;p&gt;The implementation uses the &lt;code&gt;GetSelectStatement&lt;/code&gt; method of the &lt;code&gt;OleDbSchema&lt;/code&gt; class. This method returns a string that depends on the type of table passed as a parameter. If the table is a regular table or view, the method returns a &lt;b&gt;select&lt;/b&gt; statement. If the table represents a stored procedure, the method returns an &lt;b&gt;exec&lt;/b&gt; statement including the name of the stored procedure and the parameter values stored as extended properties. In our case, the parameter values were set by the &lt;code&gt;ParametersDialog&lt;/code&gt; helper used earlier.&lt;/p&gt;
&lt;p&gt;Below the &lt;code&gt;ToolStrip&lt;/code&gt;, there is a &lt;code&gt;TabControl&lt;/code&gt; with two pages. The first contains a &lt;code&gt;TreeView&lt;/code&gt; that lists all the tables, views, and stored procedures found in the database defined by the current connection string. Users may preview the data by double-clicking the tree nodes or by selecting a node and clicking the preview button.&lt;/p&gt;
&lt;p&gt;The second tab page contains a &lt;code&gt;TextBox&lt;/code&gt; that contains the SQL statement generated by the &lt;code&gt;QueryDesignerDialog&lt;/code&gt;. This &lt;code&gt;TextBox&lt;/code&gt; is read/write, so users can cut, paste, or edit the SQL statement manually if they choose to do so.&lt;/p&gt;
&lt;h2&gt;Limitations&lt;/h2&gt;
&lt;p&gt;The main limitation in this initial version is the fact that it can generate new SQL statements, but it cannot edit existing ones. To overcome this limitation, the next version will need a SQL parser that will take an existing SQL string apart and generate the corresponding &lt;code&gt;QueryField&lt;/code&gt; objects. This may be easy to do in some cases, but SQL is a rich and flexible language, so the task is not trivial and that is why it is not included here.&lt;/p&gt;
&lt;p&gt;Another limitation is the fact that the whole implementation relies on &lt;code&gt;OleDb&lt;/code&gt; connection strings. This is not a serious limitation since &lt;code&gt;OleDb&lt;/code&gt; is a flexible data source, supporting Sql Server, Oracle, ODBC, Access (Jet), and many others. However, native implementations may be more efficient than the corresponding &lt;code&gt;OleDb&lt;/code&gt; version, and the SQL may require syntax adjustments. I have not looked into this very much at all, so if you have feedback in this area I am very interested.&lt;/p&gt;
&lt;p&gt;Finally, although the UI was loosely based on traditional tools like SQL Server Management Studio and Access, it deviates from that in the way tables and views are presented to the users. Personally, I like the approach used here, using a simple &lt;code&gt;TreeView&lt;/code&gt; that is complete and easy to navigate, and configure by expanding and collapsing nodes with the mouse or keyboard. But I am sure many people will prefer the more traditional approach showing a pane with tables represented by floating lists and lines showing the connections between the tables. I am interested in your feedback in this area as well.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Thanks for your interest. I hope you enjoy the &lt;code&gt;QueryDesignerDialog&lt;/code&gt; class and would love to get your feedback.&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://helpcentral.componentone.com/CS/aggbug.aspx?PostID=224312" width="1" height="1"&gt;</content><author><name>C1_GregL</name><uri>http://helpcentral.componentone.com/CS/members/C1_5F00_GregL/default.aspx</uri></author><category term="OleDb" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/OleDb/default.aspx" /><category term="Access" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/Access/default.aspx" /><category term="SQL Server" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/SQL+Server/default.aspx" /><category term="filtering" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/filtering/default.aspx" /><category term="sorting" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/sorting/default.aspx" /><category term="grouping" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/grouping/default.aspx" /><category term="querying" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/querying/default.aspx" /></entry><entry><title>Spell-check OleDb Databases</title><link rel="alternate" type="text/html" href="/CS/blogs/windev/archive/2009/10/14/SpellCheckDatabases.aspx" /><id>/CS/blogs/windev/archive/2009/10/14/SpellCheckDatabases.aspx</id><published>2009-10-14T15:37:00Z</published><updated>2009-10-14T15:37:00Z</updated><content type="html">&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;This article describes the implementation of &lt;strong&gt;C1DBSpellChecker&lt;/strong&gt;, a utility that performs spell-checking on OleDb databases including SqlServer and Access.&lt;/p&gt;
&lt;p&gt;After reading this article, you will be able to specify a connection string, and then select a table and one or more string fields on the table to spell-check. Typing errors will then be displayed in a grid where they can be corrected. When all corrections have been made, the changes will be saved back to the database.&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="C1DBSpellChecker" src="http://www.componentone.com/newimages/Products/Screenshots/Articles/C1DBSpellChecker.png" /&gt;&lt;/p&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;Spell-checking is an important tool used by most people when creating documents of all kinds, from formal reports to simple e-mail messages. Many popular applications provide built-in spell-checking that makes this task easy and almost automatic.&lt;/p&gt;
&lt;p&gt;Unfortunately, not all applications have built-in spell-checking. Visual Studio is a good example. Developers use Microsoft Visual Studio to create applications, web pages, and documentation. Unless they have a package such as &lt;strong&gt;ComponentOne IntelliSpell&lt;/strong&gt;, it is likely that spelling mistakes will creep into their work and eventually surface on a website or commercial application.&lt;/p&gt;
&lt;p&gt;Even developers that use &lt;strong&gt;ComponentOne IntelliSpell&lt;/strong&gt; often rely on content that is stored in databases and hasn&amp;#39;t been properly spell-checked. For example, the popular &lt;strong&gt;AdventureWorks&lt;/strong&gt; database contains typos such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;i&gt;alluminum, alumunim&lt;/i&gt; (Aluminum)&lt;/li&gt;
&lt;li&gt;&lt;i&gt;comparible&lt;/i&gt; (Comparable)&lt;/li&gt;
&lt;li&gt;&lt;i&gt;securly&lt;/i&gt; (Securely)&lt;/li&gt;
&lt;li&gt;&lt;i&gt;funtionality&lt;/i&gt; (functionality)&lt;/li&gt;
&lt;li&gt;&lt;i&gt;manuverability&lt;/i&gt; (maneuverability)&lt;/li&gt;
&lt;li&gt;&lt;i&gt;responsivness &lt;/i&gt;(responsiveness)&lt;/li&gt;
&lt;li&gt;&lt;i&gt;you&amp;#39;l find &lt;/i&gt;(you&amp;#39;ll find)&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;Spelling errors like this aren&amp;#39;t very professional on a catalog or website. Surprisingly, there are few or no tools for spell-checking databases. The &lt;strong&gt;C1DBSpellChecker&lt;/strong&gt; application was designed to fill this need.&lt;/p&gt;
&lt;h2&gt;Application Structure&lt;/h2&gt;
&lt;p&gt;The &lt;strong&gt;C1DBSpellChecker&lt;/strong&gt; application performs the following tasks:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Allow the user to select a connection string.&lt;/li&gt;
&lt;li&gt;Get the schema for the selected connection (list of tables and fields).&lt;/li&gt;
&lt;li&gt;Allow the user to pick a table and one or more string fields.&lt;/li&gt;
&lt;li&gt;Load the data and keep only rows that contain spelling errors.&lt;/li&gt;
&lt;li&gt;Show the spelling errors on a grid and allow the user to fix them.&lt;/li&gt;
&lt;li&gt;Save the changes back to the database.&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;These tasks are described in the following sections.&lt;/p&gt;
&lt;h3&gt;Select a connection string&lt;/h3&gt;
&lt;p&gt;The first step required to spell-check a database is selecting the database to use. This is done by specifying a connection string. We use the ADODB and MSDASC libraries to accomplish this. These libraries are provided by Microsoft and can be freely distributed. They provide a user interface for creating and editing OleDb connection strings.&lt;/p&gt;
&lt;p&gt;We decided to use the OleDb data provider because it provides great flexibility, allowing connections to Sql Server, Access, and many others.&lt;/p&gt;
&lt;p&gt;The code used to get and edit the connection strings is simple (the version below omits error checking code for clarity; please refer to the source for a more complete version):&lt;/p&gt;&lt;pre&gt;// prompt user for a connection string
string PromptConnectionString(string connString)
{
  // create objects we&amp;#39;ll need
  var dlinks = new MSDASC.DataLinksClass();
  var conn = new ADODB.ConnectionClass();

  // show connection picker dialog
  object obj = conn;
  dlinks.hWnd = (int)Handle;
  if (dlinks.PromptEdit(ref obj))
  {
    connString = conn.ConnectionString;
  }

  // done
  return connString;
}
&lt;/pre&gt;
&lt;p&gt;Connection strings created by the user are added to a &lt;code&gt;ComboBox&lt;/code&gt; and saved as part of the application settings, so they can be reused across sessions. The code that performs this task is listed below:&lt;/p&gt;&lt;pre&gt;// form loaded: load recently used connection strings
protected override void OnLoad(EventArgs e)
{
  var mru = Properties.Settings.Default.RecentConnections;
  if (mru != null)
  {
    foreach (string connString in mru)
    {
      _cmbConnString.Items.Add(connString);
    }
  }
  base.OnLoad(e);
}

// form closing: save recently used connection strings
protected override void OnFormClosing(FormClosingEventArgs e)
{
  var mru = new System.Collections.Specialized.StringCollection();
  foreach (string item in _cmbConnString.Items)
  {
    mru.Add(item);
  }
  Properties.Settings.Default.RecentConnections = mru;
  Properties.Settings.Default.Save();
  base.OnFormClosing(e);
}
&lt;/pre&gt;
&lt;p&gt;Because the connection strings are fairly long, the application uses the owner-draw feature of the &lt;code&gt;ComboBox&lt;/code&gt; control to trim the connection strings when they are displayed in the drop down. This makes it a lot easier for users to find the connections they are looking for.&lt;/p&gt;
&lt;p style="DIRECTION:ltr;"&gt;The owner-draw code for the &lt;code&gt;ComboBox&lt;/code&gt; is as follows:&lt;/p&gt;&lt;pre&gt;public Form1())
{
  InitializeComponent();

  // make combo owner-drawn
  var cmb = _cmbConnString.ComboBox;
  cmb.DrawMode = DrawMode.OwnerDrawFixed;
  cmb.DrawItem += cmb_DrawItem;
}

// trim items in combo using ellipsis (they&amp;#39;re very long)
void cmb_DrawItem(object sender, DrawItemEventArgs e)
{
  var fmt = new StringFormat();
  fmt.LineAlignment = StringAlignment.Center;
  fmt.Trimming = StringTrimming.EllipsisPath;

  var text = (string)_cmbConnString.Items[e.Index];
  text = TrimConnectionString(text);

  var brush = (e.State &amp;amp; DrawItemState.Selected) != 0
    ? SystemBrushes.HighlightText
    : SystemBrushes.WindowText;

  e.DrawBackground();
  e.Graphics.DrawString(text, _cmbConnString.Font, brush, e.Bounds, fmt);
  e.DrawFocusRectangle();
}

// trim connection string for display
string[] _keys = new string[] { &amp;quot;Provider&amp;quot;, &amp;quot;Initial Catalog&amp;quot;, &amp;quot;Data Source&amp;quot; };
string TrimConnectionString(string text)
{
  var sb = new StringBuilder();
  foreach (var item in text.Split(&amp;#39;;&amp;#39;))
  {
    foreach (var key in _keys)
    {
      if (item.IndexOf(key, StringComparison.InvariantCultureIgnoreCase) &amp;gt; -1)
      {
        if (sb.Length &amp;gt; 0)
        {
          sb.Append(&amp;quot;...&amp;quot;);
        }
        sb.Append(item.Split(&amp;#39;=&amp;#39;)[1].Trim());
      }
    }
  }
  return sb.ToString();
}
&lt;/pre&gt;
&lt;p&gt;The code works by splitting the connection string into key/value pairs and then keeping only the parts that help identify the connection. Configuration options are removed for clarity. For example, here is a typical OleDb connection string in all its glory:&lt;/p&gt;&lt;code&gt;Provider=SQLOLEDB.1;Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=AdventureWorks;Data Source=spock\sqlexpress&lt;/code&gt; 
&lt;p class="style1"&gt;And here is the trimmed version:&lt;/p&gt;&lt;code&gt;SQLOLEDB.1...AdventureWorks...spock\sqlexpress&lt;/code&gt; 
&lt;p&gt;The trimmed version shows only the information that is relevant for selecting an entry from a long list.&lt;/p&gt;
&lt;h3&gt;Get the database schema&lt;/h3&gt;
&lt;p&gt;Once the user picks a connection string, the next step is to use the connection to obtain a database schema. The schema is a list of the tables, fields, and relations in the database. It describes the structure of the database.&lt;/p&gt;
&lt;p&gt;We retrieve the database schema using an auxiliary class called &lt;code&gt;OleSDbSchema&lt;/code&gt;. This class extends the system &lt;code&gt;DataSet&lt;/code&gt; class with a &lt;strong&gt;ConnectionString&lt;/strong&gt; property. Setting this property populates the &lt;code&gt;OleSDbSchema&lt;/code&gt; with tables that have the same structure as the tables in the database (but no data).&lt;/p&gt;
&lt;p&gt;The code below shows how the &lt;code&gt;OleSDbSchema&lt;/code&gt; class obtains the database schema (this version is simplified for clarity; please refer to the source code for a complete version, including code that retrieves constraints, relations, and stored procedures):&lt;/p&gt;&lt;pre&gt;// Gets or sets the connection string used to fills the schema.
public string ConnectionString
{
  get { return _connString; }
  set
  {
    if (value != _connString)
	{
	  _connString = value;
	  GetSchema();
    }
  }
}
void GetSchema()
{
  // initialize this DataSet
  this.Reset();

  // go get the schema
  EnforceConstraints = false;
  using (var conn = new OleDbConnection(connString))
  {
    conn.Open();
	GetTables(conn);
	conn.Close();
  }
}
void GetTables(OleDbConnection conn)
{
  // add tables
  var dt = conn.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, null);
  foreach (DataRow dr in dt.Rows)
  {
    // get type (table/view)
    var type = (string)dr[TABLE_TYPE];
	if (type != TABLE &amp;amp;&amp;amp; type != VIEW &amp;amp;&amp;amp; type != LINK)
    {
	  continue;
    }

    // create table
    var name = (string)dr[TABLE_NAME];
    var table = new DataTable(name);
    table.ExtendedProperties[TABLE_TYPE] = type;

    // save definition in extended properties
    foreach (DataColumn col in dt.Columns)
    {
      table.ExtendedProperties[col.ColumnName] = dr[col];
    }

    // get table schema and add to collection
    try
    {
      var select = GetSelectStatement(table);
      var da = new OleDbDataAdapter(select, conn);
      da.FillSchema(table, SchemaType.Mapped);
      Tables.Add(table);
    }
    catch { }
  }
}
&lt;/pre&gt;
&lt;p&gt;The code shows how tables and columns are created. Notice that the &lt;strong&gt;ExtendedProperties&lt;/strong&gt; property is used to store additional information about each element. This allows us, for example, to distinguish between regular tables, views, and stored procedures since all these elements are represented by &lt;code&gt;DataTable&lt;/code&gt; objects within the &lt;code&gt;OleDbSchema&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;OleDbSchema&lt;/code&gt; class is described in detail in a separate article. We only use its basic features in this project.&lt;/p&gt;
&lt;h3&gt;Select table and fields to spell-check&lt;/h3&gt;
&lt;p&gt;Once the schema has been obtained, it is used to populate a &lt;code&gt;TreeView&lt;/code&gt; control. The &lt;code&gt;TreeView&lt;/code&gt; has nodes that represent the tables in the database, and each node has child nodes that represent the fields. Only string fields are included since they are the only ones eligible for spell-checking.&lt;/p&gt;
&lt;p&gt;This is the code that populates the &lt;code&gt;TreeView&lt;/code&gt; control:&lt;/p&gt;&lt;pre&gt;// update table tree to reflect new connection string
void UpdateTableTree()
{
  // initialize table tree
  TreeNodeCollection nodes = _treeTables.Nodes;
  nodes.Clear();

  // populate using current schema
  _treeTables.BeginUpdate();
  foreach (DataTable dt in _schema.Tables)
  {
    if (_schema.GetTableType(dt) == TableType.Table)
    {
      // create new node, save table in tag property
      var node = new TreeNode(dt.TableName);
      node.Tag = dt;

      // add string fields to node
      foreach (DataColumn col in dt.Columns)
      {
        if (col.DataType == typeof(string))
        {
          var ndCol = node.Nodes.Add(col.ColumnName);
          ndCol.Tag = col;
        }
      }

      // add new node to the tree
      if (node.Nodes.Count &amp;gt; 0)
      {
        nodes.Add(node);
      }
    }
  }

  // done
  _treeTables.Sort();
  _treeTables.EndUpdate();
}
&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;TreeView&lt;/code&gt; has check boxes next to each table and field. The check boxes require a fair amount of code to work in an intuitive, automatic manner. The code must ensure that only one table is selected, and is must handle the check boxes next to tables and fields. Specifically, checking a table automatically checks all its fields and un-checks all other tables. Un-checking a table will un-check all its fields. And checking a field automatically checks the parent table and un-checks all other tables.&lt;/p&gt;
&lt;p&gt;Here is the code that manages the check boxes:&lt;/p&gt;&lt;pre&gt;// handle check boxes
void _treeTables_AfterCheck(object sender, TreeViewEventArgs e)
{
  if (_updatingTree)
  {
    return;
  }

  // start updating...
  _updatingTree = true;

  // get node that was clicked
  var n = e.Node;

  // clicked on table node
  if (n.Tag is DataTable)
  {
    // apply check state to all child nodes (fields)
    SetCheck(n, n.Checked);

    // if this table is checked, uncheck all other tables
    if (n.Checked)
    {
      foreach (TreeNode c in n.TreeView.Nodes)
      {
        if (c != n)
        {
          SetCheck(c, false);
        }
      }
    }
  }

  // clicked on column node
  if (n.Tag is DataColumn)
  {
    // update parent node state
    bool check = false;
    foreach (TreeNode c in n.Parent.Nodes)
    {
      check |= c.Checked;
    }
    n.Parent.Checked = check;

    // if this node is checked, uncheck all other tables
    if (n.Checked)
    {
      foreach (TreeNode c in n.TreeView.Nodes)
      {
        if (c != n.Parent)
        {
          SetCheck(c, false);
        }
      }
    }
  }

  // done updating...
  _updatingTree = false;
}
void SetCheck(TreeNode n, bool check)
{
  n.Checked = check;
  foreach (TreeNode c in n.Nodes)
  {
    c.Checked = check;
  }
}
&lt;/pre&gt;
&lt;h3&gt;Load and spell-check the selected data&lt;/h3&gt;
&lt;p&gt;Once the user has selected one or more fields to spell-check, we need to read the data, spell-check each field, and keep only the records that have spelling errors in them. We use an &lt;code&gt;OleDbReader&lt;/code&gt; to read the records and a &lt;code&gt;C1SpellChecker&lt;/code&gt; to spell-check each one. Records that contain spelling errors are stored in a &lt;code&gt;DataTable&lt;/code&gt; to be edited by the user.&lt;/p&gt;
&lt;p&gt;Here is the code that reads the data and performs the spell-checking:&lt;/p&gt;&lt;pre&gt;// get data table with the rows that contain spelling errors
DataTable GetSpellingErrors(OleDbDataAdapter da, List&amp;lt;string&amp;gt; columns)
{
  // create table and adapter
  var dt = new DataTable();

  // get table schema
  da.FillSchema(dt, SchemaType.Mapped);

  // make columns not being spell-checked read-only
  foreach (DataColumn col in dt.Columns)
  {
    if (!columns.Contains(col.ColumnName))
    {
      col.ReadOnly = true;
    }
  }

  // read rows with DataReader
  var cmd = da.SelectCommand;
  cmd.Connection.Open();
  using (var reader = cmd.ExecuteReader())
  {
    while (reader.Read())
    {
      // read a row
      var dr = dt.NewRow();
      foreach (DataColumn col in dt.Columns)
      {
        var index = col.Ordinal;
        dr[index] = reader.GetValue(index);
      }

      // check for errors
      bool hasErrors = false;
      foreach (string col in columns)
      {
        var text = dr[col] as string;
        if (!string.IsNullOrEmpty(text))
        {
          var errors = _spell.CheckText(text);
          if (errors.Count &amp;gt; 0)
          {
            hasErrors = true;
          }
        }
      }

      // keep rows that have errors
      if (hasErrors)
      {
        dt.Rows.Add(dr);
      }
    }
  }

  // table has no changes
  dt.AcceptChanges();

  // done
  return dt;
}
&lt;/pre&gt;
&lt;p&gt;A simpler but less efficient alternative would be to read all the data into the &lt;code&gt;DataTable&lt;/code&gt; first, then spell-check it and remove the rows that don&amp;#39;t contain any spelling mistakes. The version listed above is better because rows without errors are discarded immediately and never added to the &lt;code&gt;DataTable&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Show errors and allow editing&lt;/h3&gt;
&lt;p&gt;Now that we have a &lt;code&gt;DataTable&lt;/code&gt; with all the errors, the next step is to show the table to the user so he can review and fix them. We use a &lt;code&gt;C1FlexGrid&lt;/code&gt; control to do that. We use the grid&amp;#39;s owner-draw feature to mark the typos with the familiar red wavy underlines, and attach a &lt;code&gt;C1SpellChecker&lt;/code&gt; to the grid editor so the user gets a nice context menu with spelling suggestions while editing the cells.&lt;/p&gt;
&lt;p&gt;Here is the code that binds the grid to the table containing the errors and implements the owner-draw logic:&lt;/p&gt;&lt;pre&gt;// bind the grid and enable owner-draw to show the errors
_flex.DataSource = _dtErrors;
_flex.DrawMode = C1.Win.C1FlexGrid.DrawModeEnum.OwnerDraw;
_flex.OwnerDrawCell += _flex_OwnerDrawCell;

// draw wavy read lines under spelling errors
void _flex_OwnerDrawCell(object sender, C1.Win.C1FlexGrid.OwnerDrawCellEventArgs e)
{
  // spell-check editable cells (unless we&amp;#39;re just measuring)
  if (!e.Measuring &amp;amp;&amp;amp; 
      _flex.Cols[e.Col].AllowEditing &amp;amp;&amp;amp; 
      e.Row &amp;gt;= _flex.Rows.Fixed)
  {
    var text = _flex.GetDataDisplay(e.Row, e.Col);
    var errors = _spell.CheckText(text);

    if (errors.Count &amp;gt; 0)
    {
      // draw cell as usual
      e.Style = _errorStyle;
      e.DrawCell();

      // build list with error ranges
      var ranges = new CharacterRange[errors.Count];
      for (int i = 0; i &amp;lt; errors.Count; i++)
      {
        ranges[i] = new CharacterRange(
          errors[i].Start, errors[i].Length);
      }

      // create StringFormat to locate the error ranges
      var sf = new StringFormat(e.Style.StringFormat);
      try
      {
        sf.SetMeasurableCharacterRanges(ranges);
      }
      catch { }
      
      // locate the error ranges
      var rc = e.Style.GetTextRectangle(e.Bounds, null);
      var rgns = e.Graphics.MeasureCharacterRanges(
        text, e.Style.Font, rc, sf);

      // draw wavy red underline for each range
      foreach (var rgn in rgns)
      {
        rc = Rectangle.Truncate(rgn.GetBounds(e.Graphics));
        for (Point pt = new Point(rc.X, rc.Bottom); 
          pt.X + 2 &amp;lt; rc.Right; 
          pt.X += 4)
        {
          e.Graphics.DrawLines(Pens.Red, new Point[] 
          { 
            new Point(pt.X, pt.Y), 
            new Point(pt.X + 2, pt.Y - 2), 
            new Point(pt.X + 4, pt.Y) 
          });
        }
      }
    }
  }
}
&lt;/pre&gt;
&lt;p&gt;And here is the code that connects a &lt;code&gt;C1SpellChecker&lt;/code&gt; with the grid editor so the user can see the underlines while editing the cell and get a nice context menu with spelling suggestions and commands:&lt;/p&gt;&lt;pre&gt;// connect event handler to customize the grid editor
_flex.SetupEditor += _flex_SetupEditor;

// enable spell checker in cell editor
void _flex_SetupEditor(object sender, RowColEventArgs e)
{
    var tb = _flex.Editor as TextBox;
    if (tb != null)
    {
        _spell.SetActiveSpellChecking(tb, true);
    }
}
&lt;/pre&gt;
&lt;p&gt;The form also contains a button that performs a modal (dialog-based) spell-check over the entire grid. That can be more convenient and comfortable than searching for the errors by inspecting each grid cell visually.&lt;/p&gt;
&lt;p&gt;Implementing the modal check option is easy:&lt;/p&gt;&lt;pre&gt;// perform modal check on the grid
private void _btnSpell_Click(object sender, EventArgs e)
{
  var cols = new List&amp;lt;string&amp;gt;();
  foreach (Column col in _flex.Cols)
  {
    if (col.AllowEditing)
    {
      cols.Add(col.Name);
    }
  }
  var speller = new FlexGridSpellChecker(_flex, cols.ToArray());
  _spell.CheckControl(speller);
}
&lt;/pre&gt;
&lt;p&gt;The code starts by building a list with the columns that are to be spell-checked. Then is uses the list to build a &lt;code&gt;FlexGridSpellChecker&lt;/code&gt; object that implements the spell-checking interface required by the &lt;code&gt;C1SpellChecker&lt;/code&gt; control on behalf of the grid. The &lt;code&gt;FlexGridSpellChecker&lt;/code&gt; implementation is not especially interesting, so we won&amp;#39;t reproduce it here. Please refer to the source code if you are interested in the details.&lt;/p&gt;
&lt;h3&gt;Save changes back to the database&lt;/h3&gt;
&lt;p&gt;Once the user has reviewed and corrected the errors, the only task remaining is saving the changes back into the database. To do this, we need an &lt;code&gt;OleDbDataAdapter&lt;/code&gt; object with select and update commands. The select command is used to retrieve the data, and the update command is used to write changes back into the database.&lt;/p&gt;
&lt;p&gt;Here is the code that creates the &lt;code&gt;OleDbDataAdapter&lt;/code&gt; and writes the changes back into the database:&lt;/p&gt;&lt;pre&gt;// save changes in the data table
void SaveChanges()
{
  try
  {
    // build DataAdapter with select and update commands
    var sql = _schema.GetSelectStatement(table);
    var da = new OleDbDataAdapter(sql, ConnectionString);
    var cmdBuilder = new OleDbCommandBuilder(da);
    da.UpdateCommand = cmdBuilder.GetUpdateCommand();
    
    // save the changes
    da.Update(_dtErrors);

    // mark table as clean
    _dtErrors.AcceptChanges();

    // disable save changes button until there are more changes
    _btnSaveChanges.Enabled = false;
  }
  catch (Exception x)
  {
    // something went wrong? inform the user
    var dr = MessageBox.Show(
      this,
      string.Format(Properties.Resources.FailedToSave, x.Message),
      Application.ProductName,
      MessageBoxButtons.OK,
      MessageBoxIcon.Error);
  }
}
&lt;/pre&gt;
&lt;p&gt;This concludes the last part of the application. We walked over the steps required to select the database, spell-check its content, display the errors, allow users to fix them, and save the corrections back to the database. It was a long and fun journey!&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;We developed the &lt;strong&gt;C1DBSpellChecker&lt;/strong&gt; application with two objectives in mind:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;To create a useful tool that fills a real need. Many sites and applications are driven by databases that contain spelling errors, and fixing them without a proper tool is difficult.&lt;/li&gt;
&lt;li&gt;To show some useful techniques required to implement real applications in .NET. The techniques covered include database management, owner-draw controls, application settings and resources, and more. &lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;We hope both objectives have been achieved and you find them useful. If you have requests or suggestions for improving this document or the application, please post on our site. Thanks in advance.&lt;/p&gt;
&lt;p&gt;Download the full project with source code: &lt;a href="http://helpcentral.componentone.com/CS/blogs/windev/C1DBSpellChecker.zip"&gt;C1DBSpellChecker.zip&lt;/a&gt;&lt;/a&gt;&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://helpcentral.componentone.com/CS/aggbug.aspx?PostID=223521" width="1" height="1"&gt;</content><author><name>C1_GregL</name><uri>http://helpcentral.componentone.com/CS/members/C1_5F00_GregL/default.aspx</uri></author><category term="OleDb" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/OleDb/default.aspx" /><category term="WinForms" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/WinForms/default.aspx" /><category term="IntelliSpell" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/IntelliSpell/default.aspx" /><category term="Access" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/Access/default.aspx" /><category term="spell-checking" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/spell-checking/default.aspx" /><category term="AdventureWorks" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/AdventureWorks/default.aspx" /><category term="SQL Server" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/SQL+Server/default.aspx" /><category term="C1SpellChecker" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/C1SpellChecker/default.aspx" /></entry><entry><title>Enhanced PrintPreviewDialog Class with PDF Output</title><link rel="alternate" type="text/html" href="/CS/blogs/windev/archive/2009/10/06/PDFPrintPreview.aspx" /><id>/CS/blogs/windev/archive/2009/10/06/PDFPrintPreview.aspx</id><published>2009-10-06T18:53:00Z</published><updated>2009-10-06T18:53:00Z</updated><content type="html">&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;This article describes the implementation of an enhanced &lt;code&gt;PrintPreviewDialog&lt;/code&gt; class that provides PDF output in addition to the standard print and preview capabilities.&lt;/p&gt;
&lt;p align="center"&gt;&lt;img style="WIDTH:539px;HEIGHT:380px;" title="C1PrintPreview" alt="C1PrintPreview" src="http://helpcentral.componentone.com/CS/blogs/windev/C1PrintPreview/PrintPreview.png" width="539" height="380" /&gt;&lt;/p&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;PrintPreviewDialog&lt;/code&gt; is convenient and easy to use. All you need to do is create an instance of the dialog class, assign your &lt;code&gt;PrintDocument&lt;/code&gt; object to the &lt;code&gt;Document&lt;/code&gt; property, and call the &lt;code&gt;ShowDialog&lt;/code&gt; method.&lt;/p&gt;
&lt;p&gt;However, &lt;code&gt;PrintPreviewDialog&lt;/code&gt; has some shortcomings, including the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The entire document must be rendered before the preview appears. This is annoying for long documents.&lt;/li&gt;
&lt;li&gt;There are no options for choosing the printer, adjusting the page layout, or selecting specific pages to print.&lt;/li&gt;
&lt;li&gt;The dialog looks outdated. It hasn&amp;#39;t changed since .NET 1.0, and it wasn&amp;#39;t exactly cutting-edge even back then.&lt;/li&gt;
&lt;li&gt;The dialog allows little or no customization.&lt;/li&gt;
&lt;li&gt;There is no option to export the document to other formats such as PDF.&lt;/li&gt;
&lt;li&gt;Page images are cached in the control, which limits the size of the documents that can be previewed.&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;The &lt;code&gt;CoolPrintPreviewDialog&lt;/code&gt; class presented here addresses these shortcomings. It is just as easy to use as the standard &lt;code&gt;PrintPreviewDialog&lt;/code&gt;, but has the following enhancements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Pages can be previewed as soon as they are rendered. The first page is shown almost instantly and subsequent pages become available while the user browses the first ones.&lt;/li&gt;
&lt;li&gt;The &amp;quot;Print&amp;quot; button shows a dialog that allows users to select the printer and page ranges to print. A &amp;quot;Page Layout&amp;quot; button is also available so users can change page size, orientation, and margins.&lt;/li&gt;
&lt;li&gt;The dialog uses a &lt;code&gt;ToolStrip&lt;/code&gt; control instead of the old toolbar.&lt;/li&gt;
&lt;li&gt;You have the source and can customize everything from appearance to behavior.&lt;/li&gt;
&lt;li&gt;The control creates a list of images which can be exported to other formats including PDF.&lt;/li&gt;&lt;/ul&gt;
&lt;h2&gt;Using the Code&lt;/h2&gt;
&lt;p&gt;Using the &lt;code&gt;CoolPrintPreviewDialog&lt;/code&gt; is as easy as using the traditional &lt;code&gt;PrintPreviewDialog&lt;/code&gt;. You instantiate the control, set the &lt;code&gt;Document&lt;/code&gt; property to the &lt;code&gt;PrintDocument&lt;/code&gt; you want to preview, then call the dialog&amp;#39;s &lt;code&gt;Show&lt;/code&gt; method.&lt;/p&gt;
&lt;p&gt;If you have code that uses the &lt;code&gt;PrintPreviewDialog&lt;/code&gt; class, switching to the &lt;code&gt;CoolPrintPreviewDialog&lt;/code&gt; only requires changing one line of code. For example:&lt;/p&gt;&lt;pre&gt;// using a PrintPreviewDialog
&lt;strong&gt;using (var dlg = new PrintPreviewDialog())&lt;/strong&gt;
{
  dlg.Document = this.printDocument1;
  dlg.ShowDialog(this);
}

// using a CoolPrintPreviewDialog
&lt;strong&gt;using (var dlg = new CoolPrintPreview.CoolPrintPreviewDialog())&lt;/strong&gt;
{
  dlg.Document = this.printDocument1;
  dlg.ShowDialog(this);
}
&lt;/pre&gt;
&lt;h2&gt;Generating the Preview Images&lt;/h2&gt;
&lt;p&gt;The core of the &lt;code&gt;CoolPrintPreviewDialog&lt;/code&gt; class is a &lt;code&gt;CoolPreviewControl&lt;/code&gt; that generates and shows the page previews.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;PrintDocument&lt;/code&gt; object has a &lt;code&gt;PrintController&lt;/code&gt; property that specifies an object responsible for creating the &lt;code&gt;Graphics&lt;/code&gt; objects where the document is rendered. The default print controller creates &lt;code&gt;Graphics&lt;/code&gt; objects for the default printer and is not interesting in this case. But .NET also defines a &lt;code&gt;PreviewPrintController&lt;/code&gt; class that creates metafiles instead. These remain available to the caller to be shown in the preview area.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;CoolPreviewControl&lt;/code&gt; works by temporarily replacing the document&amp;#39;s original print controller with a &lt;code&gt;PreviewPrintController&lt;/code&gt;, calling the document&amp;#39;s &lt;code&gt;Print&lt;/code&gt; method, and getting the page images while the document is rendered. The images represent pages in the document, and are scaled and displayed in the control just like any regular &lt;code&gt;Image&lt;/code&gt; object.&lt;/p&gt;
&lt;p&gt;The code that creates the page previews looks like this (this code is simplified for clarity, refer to the source for a better version):&lt;/p&gt;&lt;pre&gt;// list of page images
List&amp;lt;Image&amp;gt; _imgList = new List&amp;lt;Image&amp;gt;();

// generate page images
public void GeneratePreview(PrintDocument doc)
{
  // save original print controller
  PrintController savePC = doc.PrintController;

  // replace it with a preview print controller  
  doc.PrintController = new PreviewPrintController();
  
  // hook up event handlers
  doc.PrintPage += _doc_PrintPage;
  doc.EndPrint += _doc_EndPrint;

  // render the document
  _imgList.Clear();
  doc.Print();

  // disconnect event handlers
  doc.PrintPage -= _doc_PrintPage;
  doc.EndPrint -= _doc_EndPrint;
  
  // restore original print controller
  doc.PrintController = savePC;
}&lt;/pre&gt;
&lt;p&gt;The code installs the controller and hooks up the event handlers, then calls the &lt;code&gt;Print&lt;/code&gt; method to generate the pages, and cleans up when it&amp;#39;s done.&lt;/p&gt;
&lt;p&gt;When the &lt;code&gt;Print&lt;/code&gt; method is invoked, the document starts firing events. The &lt;code&gt;PrintPage&lt;/code&gt; and &lt;code&gt;EndPrint&lt;/code&gt; event handlers capture the pages as soon as they are rendered and add them to an internal image list.&lt;/p&gt;
&lt;p&gt;The event handlers also call the &lt;code&gt;Application.DoEvents&lt;/code&gt; method to keep the dialog responsive to user actions while the document renders. This allows users to switch pages, adjust the zoom factor, or cancel the document generation process. Without this call, the dialog would stop operating until the whole document finishes rendering.&lt;/p&gt;
&lt;p&gt;This is the code that does all this:&lt;/p&gt;&lt;pre&gt;void _doc_PrintPage(object sender, PrintPageEventArgs e)
{
  SyncPageImages();
  if (_cancel)
  {
    e.Cancel = true;
  }
}
void _doc_EndPrint(object sender, PrintEventArgs e)
{
  SyncPageImages();
}
void SyncPageImages()
{
  // get page previews from print controller
  var pv = (PreviewPrintController)_doc.PrintController;
  var pageInfo = pv.GetPreviewPageInfo();
  
  // add whatever images are missing from our internal list
  for (int i = _img.Count; i &amp;lt; pageInfo.Length; i++)
  {
    // add to internal list
    _img.Add(pageInfo[i].Image);

    // fire event to indicate we have more pages
    OnPageCountChanged(EventArgs.Empty);
    
    // if the page being previewed changed, refresh to show it
    if (StartPage &amp;lt; 0) StartPage = 0;
    if (i == StartPage || i == StartPage + 1)
    {
      Refresh();
    }
    
    // keep application responsive
    Application.DoEvents();
  }
}&lt;/pre&gt;
&lt;p&gt;This is the core of the preview code. The rest is concerned with housekeeping tasks such as scaling the preview images, updating the scrollbars, handling navigation buttons, mouse, keyboard, and so on. Please refer to the source code for the implementation details.&lt;/p&gt;
&lt;h2&gt;Updating the Page Layout&lt;/h2&gt;
&lt;p&gt;The preview dialog allows users to update the print layout. This is very easy to implement, thanks to the .NET &lt;code&gt;PageSetupDialog&lt;/code&gt; class. Here is the code that gets called when users click the &amp;quot;Page Layout&amp;quot; button:&lt;/p&gt;&lt;pre&gt;void _btnPageSetup_Click(object sender, EventArgs e)
{
  using (var dlg = new PageSetupDialog())
  {
    dlg.Document = Document;
    if (dlg.ShowDialog(this) == DialogResult.OK)
    {
      // user changed the page layout, refresh preview images
      _preview.RefreshPreview();
    }
  }
}&lt;/pre&gt;
&lt;p&gt;The code shows a &lt;code&gt;PageSetupDialog&lt;/code&gt; that allows the user to change the paper size, orientation, and margins. Changes made by the user are reflected in the document&amp;#39;s &lt;code&gt;DefaultPageSettings&lt;/code&gt; property.&lt;/p&gt;
&lt;p&gt;If the user clicks OK, then we assume that the page layout has been modified, and call the &lt;code&gt;RefreshPreview&lt;/code&gt; method on the preview control. This method regenerates all preview images using the new settings, so the user can see the changes applied to margins, page orientation, and so on.&lt;/p&gt;
&lt;h2&gt;Printing the Document&lt;/h2&gt;
&lt;p&gt;When the user clicks the &amp;quot;Print&amp;quot; button, the dialog shows a &lt;code&gt;PrintDialog&lt;/code&gt; so the user can select the printer, page range, or change his mind and cancel the printing.&lt;/p&gt;
&lt;p&gt;Unfortunately, page range selections are not honored if you simply call the &lt;code&gt;Print&lt;/code&gt; method directly on the document. To remedy this, the dialog calls the &lt;code&gt;Print&lt;/code&gt; method on the enhanced preview control instead. That implementation uses the page images already stored in the control, and honors page ranges defined in the document&amp;#39;s &lt;code&gt;PrinterSettings&lt;/code&gt; properties.&lt;/p&gt;
&lt;p&gt;This is the code that gets called when the user clicks the &amp;quot;Print&amp;quot; button:&lt;/p&gt;&lt;pre&gt;void _btnPrint_Click(object sender, EventArgs e)
{
  using (var dlg = new PrintDialog())
  {
    // configure dialog
    dlg.AllowSomePages = true;
    dlg.AllowSelection = true;
    dlg.Document = Document;

    // show allowed page range
    var ps = dlg.PrinterSettings;
    ps.MinimumPage = ps.FromPage = 1;
    ps.MaximumPage = ps.ToPage = _preview.PageCount;

    // show dialog
    if (dlg.ShowDialog(this) == DialogResult.OK)
    {
      // print selected page range
      _preview.Print();
    }
  }
}&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;Print&lt;/code&gt; method in the preview control starts by determining the range of pages that should be rendered. This may be the full document, a specific range, or the current selection (page being previewed). Once the page range has been determined, the code creates a &lt;code&gt;DocumentPrinter&lt;/code&gt; helper class to perform the actual printing:&lt;/p&gt;&lt;pre&gt;public void Print()
{
  // select pages to print
  var ps = _doc.PrinterSettings;
  int first = ps.MinimumPage - 1;
  int last = ps.MaximumPage - 1;
  switch (ps.PrintRange)
  {
    case PrintRange.CurrentPage:
      first = last = StartPage;
      break;
    case PrintRange.Selection:
      first = last = StartPage;
      if (ZoomMode == ZoomMode.TwoPages)
        last = Math.Min(first + 1, PageCount - 1);
      break;
    case PrintRange.SomePages:
      first = ps.FromPage - 1;
      last = ps.ToPage - 1;
      break;
    }

    // print using helper class
    var dp = new DocumentPrinter(this, first, last);
    dp.Print();
  }
}&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;DocumentPrinter&lt;/code&gt; class is simple. It inherits from &lt;code&gt;PrintDocument&lt;/code&gt; and overrides the &lt;code&gt;OnPrintPage&lt;/code&gt; method to print only the pages selected by the user:&lt;/p&gt;&lt;pre&gt;internal class DocumentPrinter : PrintDocument
{
  int _first, _last, _index;
  List&amp;lt;Image&amp;gt; _imgList;

  public DocumentPrinter(CoolPrintPreviewControl preview, int first, int last)
  {
    // save page range and image list
    _first = first;
    _last = last;
    _imgList = preview.PageImages;
    
    // copy page and printer settings from original document
    DefaultPageSettings = preview.Document.DefaultPageSettings;
    PrinterSettings = preview.Document.PrinterSettings;
  }

  protected override void OnBeginPrint(PrintEventArgs e)
  {
    // start from the first page
    _index = _first;
  }
  protected override void OnPrintPage(PrintPageEventArgs e)
  {
    // render the current page and increment the index
    e.Graphics.PageUnit = GraphicsUnit.Display;
    e.Graphics.DrawImage(_imgList[_index++], e.PageBounds);
    
    // stop when we reach the last page in the range
    e.HasMorePages = _index &amp;lt;= _last;
  }
}&lt;/pre&gt;
&lt;p&gt;This implementation renders the page images assuming all pages have the same size and orientation, which is the case for most documents. If the document contains pages of different sizes, or with different orientation, this simple implementation will not work correctly. To fix this, we would have to check that the current paper size and orientation match the preview image size before printing each page and adjust the printer settings if necessary. That is left as an exercise for the reader.&lt;/p&gt;
&lt;h2&gt;Exporting to PDF&lt;/h2&gt;
&lt;p&gt;The PDF format is extremely popular because it is compact and portable. PDF files can be posted on the web, distributed by e-mail, and viewed or printed almost anywhere. &lt;/p&gt;
&lt;p&gt;Once a &lt;code&gt;PrintDocument&lt;/code&gt; has been rendered into a series of images, we can use the &lt;code&gt;C1PdfDocument&lt;/code&gt; component to render the images into a PDF document. Using this approach, any application that uses the &lt;code&gt;PrintDocument&lt;/code&gt; class to provide printing and previewing can add a PDF export feature with minimal effort.&lt;/p&gt;
&lt;p&gt;To implement the PDF export, we start by implementing the event handler that gets invoked when the user presses the export to PDF button on the &lt;code&gt;PrintPreviewDialog:&lt;/code&gt;&lt;/p&gt;&lt;pre&gt;void _btnPdf_Click(object sender, EventArgs e)
{
  using (var dlg = new SaveFileDialog())
  {
    dlg.Filter = &amp;quot;Portable Document File (*.pdf)|*.pdf&amp;quot;;
    dlg.DefaultExt = &amp;quot;.pdf&amp;quot;;
    if (dlg.ShowDialog() == DialogResult.OK)
    {
      &lt;strong&gt;pdfGenerator = new PrintDocumentPdfExporter(_pdf);&lt;/strong&gt;
      if (pdfGenerator.RenderDocument(Document, true))
      {
        _pdf.Save(dlg.FileName);
      }
    }
  }
}&lt;/pre&gt;
&lt;p&gt;The code uses a &lt;code&gt;SaveFileDialog&lt;/code&gt; to prompt the user for the name of the PDF file to save and a &lt;code&gt;PrintDocumentPdfExporter&lt;/code&gt; to generate the PDF file from the preview images.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;PrintDocumentPdfExporter&lt;/code&gt; class can be used independently of the &lt;code&gt;C1PrintPreviewDialog&lt;/code&gt; class. For example, you may want to provide a PDF export button on the main application, that does not cause a preview dialog to appear at all.&lt;/p&gt;
&lt;p&gt;The implementation of the &lt;code&gt;PrintDocumentPdfExporter&lt;/code&gt; class is shown below:&lt;/p&gt;&lt;pre&gt;class PrintDocumentPdfExporter
{
  // ** fields
  PrintDocument _doc;
  C1PdfDocument _pdf;
  PreviewPrintController _previewController;
  int _pageCount;
  bool _cancel;

  // ** ctor
  public PrintDocumentPdfExporter(C1PdfDocument pdf)
  {
    // save reference to pdf component
    _pdf = pdf;
  }
&lt;/pre&gt;
&lt;p&gt;The constructor simply saves a reference to the &lt;code&gt;C1PdfDocument&lt;/code&gt; that will be used to generate the PDF file.&lt;/p&gt;
&lt;p&gt;The main public method in the class is called &lt;code&gt;RenderDocument&lt;/code&gt;. This method starts by assigning a &lt;code&gt;PreviewPrintController&lt;/code&gt; to the document, which causes pages to be rendered into metafile images. The controller used can be a plain &lt;code&gt;PreviewPrintController&lt;/code&gt; or a &lt;code&gt;PrintControllerWithStatusDialog&lt;/code&gt; wrapper, depending on the value of the &lt;code&gt;showProgressDialog&lt;/code&gt; parameter. Both of these classes are part of the .NET framework. &lt;/p&gt;
&lt;p&gt;Once the controller has been installed, the code connects event handlers for the &lt;code&gt;PrintPage&lt;/code&gt; and &lt;code&gt;EndPrint&lt;/code&gt; events. These event handlers are responsible for rendering the page images into the PDF document. Once the event handlers are installed, the code invokes the &lt;code&gt;Print&lt;/code&gt; method to render the document.&lt;/p&gt;
&lt;p&gt;The method returns a boolean value that indicates whether the document was fully generated (and should be saved) or whether the user canceled the process by pressing the &amp;quot;Cancel&amp;quot; button on the optional progress dialog.&lt;/p&gt;&lt;pre&gt;  // ** object model
  public bool RenderDocument(PrintDocument doc, bool showProgressDialog)
  {
    // save reference to document
    _doc = doc;

    // initialize pdf document
    _pdf.Clear();
    _pdf.Landscape = false;

    // prepare to render
    var savePC = _doc.PrintController;
    _previewController = new PreviewPrintController();
    _doc.PrintController = showProgressDialog
      ? new PrintControllerWithStatusDialog(_previewController)
      : (PrintController)_previewController;
    _pageCount = 0;
    _cancel = false;

    // render
    try
    {
      _doc.PrintPage += _doc_PrintPage;
      _doc.EndPrint += _doc_EndPrint;
      _doc.Print();
    }
    finally
    {
      _doc.PrintPage -= _doc_PrintPage;
      _doc.EndPrint -= _doc_EndPrint;
      _doc.PrintController = savePC;
    }

    // done
    return !_cancel;
  }
  &lt;/pre&gt;
&lt;p&gt;The document event handlers are listed below. As each page is rendered, the handlers call the &lt;code&gt;DrawPage&lt;/code&gt; method to render the page images (metafiles) into the PDF document. This is done using the &lt;code&gt;C1PdfDocument.DrawImage&lt;/code&gt; method.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;C1PdfDocument.DrawImage&lt;/code&gt; method enumerates the GDI commands in the metafile and converts each one to the corresponding PDF vector command. The metafile is never converted into a raster image, which means the content remains scalable, searchable, and compact.&lt;/p&gt;&lt;pre&gt;  // ** PrintDocument event handlers
  void _doc_PrintPage(object sender, PrintPageEventArgs e)
  {
    var pages = _previewController.GetPreviewPageInfo();
    while (_pageCount &amp;lt; pages.Length - 1)
    {
      DrawPage(pages, _pageCount++);
    }
    if (e.Cancel)
    {
      _cancel = true;
    }
  }
  void _doc_EndPrint(object sender, PrintEventArgs e)
  {
    var pages = _previewController.GetPreviewPageInfo();
    DrawPage(pages, _pageCount);
    if (e.Cancel)
    {
      _cancel = true;
    }
  }
  void DrawPage(PreviewPageInfo[] pages, int index)
  {
    // skip to next page
    if (index &amp;gt; 0)
    {
      _pdf.NewPage();
    }

    // get preview page info
    var pi = pages[index];

    // adjust page size
    var ps = pi.PhysicalSize;
    _pdf.PageSize = new SizeF(ps.Width / 100f * 72, ps.Height / 100f * 72);

    // draw image
    var img = pi.Image;
    _pdf.DrawImage(img, _pdf.PageRectangle);
  }
}
&lt;/pre&gt;
&lt;h2&gt;Previewing Really Long Documents&lt;/h2&gt;
&lt;p&gt;After I posted the first version of this project, I got some great feedback from other CodeProject users. One of them mentioned a problem I also had a while ago. If the document contains several thousand pages, caching all those images may cause problems. Windows has a limit of 10,000 GDI objects, and each page image represents at least one. If you use too many GDI objects, your application may crash, or cause other apps to crash. Not nice...&lt;/p&gt;
&lt;p&gt;One easy way to solve this problem is to convert the page images into streams. You can then store the streams and create images on demand, only when they are needed for previewing or printing.&lt;/p&gt;
&lt;p&gt;The code below shows a &lt;code&gt;PageImageList&lt;/code&gt; class that does the job. You can use it much like a regular &lt;code&gt;List&lt;/code&gt;, except when you get or set an image, it is automatically converted to and from a byte array. This way, the images stored in the list are not GDI objects and don&amp;#39;t use up the system resources.&lt;/p&gt;&lt;pre&gt;// This version of the PageImageList stores images as byte arrays. It is a little
// more complex and slower than a simple list, but doesn&amp;#39;t consume GDI resources.
// This is important when the list contains lots of images (Windows only supports
// 10,000 simultaneous GDI objects!)
class PageImageList
{
  // ** fields
  var _list = new List&amp;lt;byte[]&amp;gt;();

  // ** object model
  public void Clear()
  {
    _list.Clear();
  }
  public int Count
  {
    get { return _list.Count; }
  }
  public void Add(Image img)
  {
    _list.Add(GetBytes(img));

    // stored image data, now dispose of original
    img.Dispose();
  }
  public Image this[int index]
  {
    get { return GetImage(_list[index]); }
    set { _list[index] = GetBytes(value); }
  }

  // implementation
  byte[] GetBytes(Image img)
  {
    // clone image since GetEnhMetaFileBits is destructive
    var clone = img.Clone() as Metafile;

    // use interop to get the metafile bits
    var enhMetafileHandle = clone.GetHenhmetafile().ToInt32();
    var bufferSize = GetEnhMetaFileBits(enhMetafileHandle, 0, null);
    var buffer = new byte[bufferSize];
    GetEnhMetaFileBits(enhMetafileHandle, bufferSize, buffer);

    // done with clone (already destroyed by call to GetEnhMetaFileBits)
    clone.Dispose();

    // return bits
    return buffer;
  }
  Image GetImage(byte[] data)
  {
    MemoryStream ms = new MemoryStream(data);
    return Image.FromStream(ms);
  }

  [System.Runtime.InteropServices.DllImport(&amp;quot;gdi32&amp;quot;)]
  static extern int GetEnhMetaFileBits(int hemf, int cbBuffer, byte[] lpbBuffer);
}&lt;/pre&gt;
&lt;p&gt;Note that the &lt;code&gt;Add&lt;/code&gt; method disposes of the image after storing it. Normally we would not do it this way since the caller owns the image and should be responsible for disposing of it. But in this project, this arrangement allows us to swap the &lt;code&gt;PageImageList&lt;/code&gt; implementation with a regular &lt;code&gt;List&lt;/code&gt;, which is convenient for testing and benchmarking.&lt;/p&gt;
&lt;p&gt;Note also that the &lt;code&gt;GetBytes&lt;/code&gt; uses the &lt;code&gt;GetHenhMetaFileBits&lt;/code&gt; API. This API makes the metafile invalid, so the image cannot be used after this method is called. To avoid destroying the original image, the method creates a clone first, then gets the bits from the clone.&lt;/p&gt;
&lt;p&gt;In case you are concerned about performance, the extra conversion work causes a performance hit of about 10% while generating the document. I think this is a small price to pay for the benefit if your documents have a few hundred or a few thousand pages.&lt;/p&gt;
&lt;p&gt;And in case you are concerned about memory usage, consider compressing the byte arrays when you store them. It is easy to do using &lt;code&gt;C1Zip&lt;/code&gt;, and metafiles tend to compress really well. Of course, there would be a small additional performance penalty involved.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;We implemented an enhanced &lt;code&gt;PrintPreviewDialog&lt;/code&gt; class that provides PDF output in addition to the standard print and preview capabilities. If you have requests or suggestions for improving this document or the application, please post on our site.&lt;/p&gt;
&lt;p&gt;Download the full source code: &lt;a href="http://helpcentral.componentone.com/CS/blogs/windev/C1PrintPreview/C1PrintPreview.zip"&gt;C1PrintPreview.zip&lt;/a&gt;&lt;br /&gt;&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://helpcentral.componentone.com/CS/aggbug.aspx?PostID=223162" width="1" height="1"&gt;</content><author><name>C1_GregL</name><uri>http://helpcentral.componentone.com/CS/members/C1_5F00_GregL/default.aspx</uri></author><category term="WinForms" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/WinForms/default.aspx" /><category term="PDF" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/PDF/default.aspx" /><category term=".NET" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/.NET/default.aspx" /><category term="Print Preview" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/Print+Preview/default.aspx" /><category term="exporting" scheme="http://helpcentral.componentone.com/CS/blogs/windev/archive/tags/exporting/default.aspx" /></entry><entry><title>Welcome to WinDev!</title><link rel="alternate" type="text/html" href="/CS/blogs/windev/archive/2009/09/29/welcome-to-windev.aspx" /><id>/CS/blogs/windev/archive/2009/09/29/welcome-to-windev.aspx</id><published>2009-09-29T17:48:00Z</published><updated>2009-09-29T17:48:00Z</updated><content type="html">&lt;p&gt;Welcome to the Win Dev Blog.&amp;nbsp;In this blog you&amp;#39;ll&amp;nbsp;find&amp;nbsp;interesting code examples and articles&amp;nbsp;for desktop application development - mostly Windows Forms and some WPF. &lt;/p&gt;
&lt;p&gt;Regarding myself, I am currently the desktop dev-tools product manager for ComponentOne.&amp;nbsp;I&amp;#39;ve done a lot of work with all .NET technologies.&amp;nbsp;If you&amp;#39;re a .NET developer like&amp;nbsp;me then you&amp;#39;re probably familiar with WinForms and WPF development. I&amp;#39;ll be&amp;nbsp;sharing many code samples that I&amp;#39;ve written, samples from other .NET developers, and even cool tips and tricks that I&amp;#39;ve stumbled upon. So stay tuned!&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://helpcentral.componentone.com/CS/aggbug.aspx?PostID=222906" width="1" height="1"&gt;</content><author><name>C1_GregL</name><uri>http://helpcentral.componentone.com/CS/members/C1_5F00_GregL/default.aspx</uri></author></entry></feed>