Splitting and Merging Pdf Files in C# Using iTextSharp

Posted on March 9 2013 11:44 AM by John Atten in CodeProject, C#   ||   Comments (3)

I recently posted about using PdfBox.net to manipulate Pdf documents in your C# application. This time, I take a quick look at iTextSharp, another library for working with Pdf documents from within the .NET framework.

Some Navigation Aids:

What is iTextSharp?

iTextSharp is a direct .NET port of the open source iText Java library for PDF generation and manipulation. As the project’s summary page on SourceForge states, iText “  . . . can be used to create PDF Documents from scratch, to convert XML to PDF . . . to fill out interactive PDF forms, to stamp new content on existing PDF documents, to split and merge existing PDF documents, and much more.”

iTextSharp presents a formidable set of tools for developers who need to create and/or manipulate Pdf files. This does come with a cost, however. The Pdf file format itself is complex; therefore, programming libraries which seek to provide a flexible interface for working with Pdf files become complex by default. iText is no exception.

I noted in my previous post on PdfBox that PdfBox was a little easier for me to get up and running with, at least for rather basic tasks such as splitting and merging existing Pdf files. I also noted that iText looked to be a little more complex, and I was correct. However, iTextSharp does not suffer some of the performance drawbacks inherent to PdfBox, at least on the .net platform.

Superior Performance vs. PdfBox

Aston-Martin-V8-Sports-Car-For-EveryAs I observed in my previous post, PdfBox.net is NOT a direct port of the PdfBox Java library, but instead is a Java library running within .net using IKVM. While I found it very cool to be able to run Java code in a .NET context, there was a serious performance hit, most notably the first time the PdfBox library was called, and the massive IKVM library spun up what amounts to a .Net implementation of the Java Virtual Machine, within which the Java code of the PdfBox library is then executed.

Needless to say, iTextSharp does not suffer this limitation. the library itself it relatively lightweight, and fast.

Extracting and Merging Pages from an Existing Pdf File

One of the most common tasks we need to do is extract pages from one Pdf into a new file. We’ll take a look at some relatively basic sample code which does just that, and get a feel for using the iTextSharp programming model.

In the following code sample, the primary iTextSharp classes we will be using are the PdfReader, Document, PdfCopy, and PdfImportedPage classes.

My simplified understanding of how this works is as follows: The PdfReader instance contains the content of the source PDF file. The Document class, once initialized with the PdfReader instance and a new output FileStream, essentially becomes a container into which pages extracted from the source file represented in the PdfReader class will be copied. Note that the Document class represents the Pdf content as HTML, which will be used to construct a properly formatted Pdf file. The result is then output to the Filestream, and saved to disk at the location specified by the destination file name.

You can download the iTextSharp source code and binaries as a single package from Files page at the iTextSharp project site. Just click on the “Download itextsharp-all-5.4.0.zip” link. Extract the files from the .zip archive, and stash them somewhere convenient. Next, set a reference in your project to the itextsharp.dll. You will need to browse to the folder where you stashed the extracted contents of the iTextSharp download.

NOTE: The complete example code for this post is available at my Github Repo.

I went ahead and created a project named iTextTools, with a class file named PdfExtractorUtility. Add the following using statements at the top of the file:

Set up references and Using Statements to use iTextSharp

using iTextSharp.text;
using iTextSharp.text.pdf;
using System;
// CLASS DEPENDS ON iTextSharp: http://sourceforge.net/projects/itextsharp/
namespace iTextTools
{
    public class PdfExtractorUtility
    {
    }
}

 

First, I’ll add a simple method to extract a single page from an existing PDF file and save to a new file:

Extract Single Page from Existing PDF to a new File:

public void ExtractPage(string sourcePdfPath, string outputPdfPath, 
    int pageNumber, string password = "")
{
    PdfReader reader = null;
    Document document = null;
    PdfCopy pdfCopyProvider = null;
    PdfImportedPage importedPage = null;
    try
    {
        // Intialize a new PdfReader instance with the contents of the source Pdf file:
        reader = new PdfReader(sourcePdfPath);
 
        // Capture the correct size and orientation for the page:
        document = new Document(reader.GetPageSizeWithRotation(pageNumber));
 
        // Initialize an instance of the PdfCopyClass with the source 
        // document and an output file stream:
        pdfCopyProvider = new PdfCopy(document, 
            new System.IO.FileStream(outputPdfPath, System.IO.FileMode.Create));
        document.Open();
 
        // Extract the desired page number:
        importedPage = pdfCopyProvider.GetImportedPage(reader, pageNumber);
        pdfCopyProvider.AddPage(importedPage);
        document.Close();
        reader.Close();
    }
    catch (Exception ex)
    {
        throw ex;
    }
}

 

As you can see, simply pass in the path to the source document, the page number to be extracted, and an output file path, and you’re done.

If we want to be able to a range of contiguous pages, we might add another method defining a start and end point:

Extract a Range of Pages from Existing PDF to a new File:

public void ExtractPages(string sourcePdfPath, string outputPdfPath, 
    int startPage, int endPage)
{
    PdfReader reader = null;
    Document sourceDocument = null;
    PdfCopy pdfCopyProvider = null;
    PdfImportedPage importedPage = null;
    try
    {
        // Intialize a new PdfReader instance with the contents of the source Pdf file:
        reader = new PdfReader(sourcePdfPath);
 
        // For simplicity, I am assuming all the pages share the same size
        // and rotation as the first page:
        sourceDocument = new Document(reader.GetPageSizeWithRotation(startPage));
 
        // Initialize an instance of the PdfCopyClass with the source 
        // document and an output file stream:
        pdfCopyProvider = new PdfCopy(sourceDocument, 
            new System.IO.FileStream(outputPdfPath, System.IO.FileMode.Create));
 
            sourceDocument.Open();
 
        // Walk the specified range and add the page copies to the output file:
        for (int i = startPage; i <= endPage; i++)
        {
            importedPage = pdfCopyProvider.GetImportedPage(reader, i);
            pdfCopyProvider.AddPage(importedPage);
        }
        sourceDocument.Close();
        reader.Close();
    }
    catch (Exception ex)
    {
        throw ex;
    }
}

 

What if we want non-contiguous pages from the source document? Well, we might override the above method with one which accepts an array of ints representing the desired pages:

Extract multiple non-contiguous pages from Existing PDF to a new File:

public void ExtractPages(string sourcePdfPath, 
    string outputPdfPath, int[] extractThesePages)
{
    PdfReader reader = null;
    Document sourceDocument = null;
    PdfCopy pdfCopyProvider = null;
    PdfImportedPage importedPage = null;
    try
    {
        // Intialize a new PdfReader instance with the 
        // contents of the source Pdf file:
        reader = new PdfReader(sourcePdfPath);
 
        // For simplicity, I am assuming all the pages share the same size
        // and rotation as the first page:
        sourceDocument = new Document(reader.GetPageSizeWithRotation(extractThesePages[0]));
 
        // Initialize an instance of the PdfCopyClass with the source 
        // document and an output file stream:
        pdfCopyProvider = new PdfCopy(sourceDocument,
            new System.IO.FileStream(outputPdfPath, System.IO.FileMode.Create));
        sourceDocument.Open();
 
        // Walk the array and add the page copies to the output file:
        foreach (int pageNumber in extractThesePages)
        {
            importedPage = pdfCopyProvider.GetImportedPage(reader, pageNumber);
            pdfCopyProvider.AddPage(importedPage);
        }
        sourceDocument.Close();
        reader.Close();
    }
    catch (Exception ex)
    {
        throw ex;
    }
}

 

Scratching the Surface

Obviously, the example(s) above are a simplistic first exploration of what appears to be a powerful library. What I notice about iText in general is that, unlike some API’s, the path to achieving your desired result is often not intuitive. I believe this is as much to do with the nature of the PDF file format, and possibly the structure of lower-level libraries upon which iTextSharp is built.

That said, there is without a doubt much to be discerned by exploring the iTextSharp source code. Additionally, there are a number of resources to assist the erstwhile developer in using this library:

Additional Resources for iTextSharp

Lastly, there is a book authored by one of the primary contributors to the iText project, Bruno Lowagie:

 

Posted on March 9 2013 11:44 AM by John Atten     

Comments (3)

Working with Pdf Files in C# Using PdfBox and IKVM

Posted on January 30 2013 07:09 PM by John Atten in Java, C#, Hacks, CodeProject   ||   Comments (2)

I have found two primary libraries for programmatically manipulating PDF files;  PdfBox and iText. These are both Java libraries, but I needed something I could use with C Sharp. Well, as it turns out there is an implementation of each of these libraries for .NET, each with its own strengths and weaknesses:

Some Navigation Links:

PdfBox - .Net version

The .NET implementation of PdfBox is not a direct port - rather, it uses IKVM to run the Java version inter-operably with .NET. IKVM features an actual .net implementation of a Java Virtual Machine, and a .net implementation of Java Class Libraries along with tools which enable Java and .Net interoperability. 

PdfIcon_PngPdfBox’s dependency on IKVK incurs a lot of baggage in performance terms. When the IKVM libraries load, and (I am assuming) the “’Virtual’ Java Virtual Machine” spins up, things slow way down until the load is complete. On the other hand, for some of the more common things one might want to do with a PDF programmatically, the API is (relatively) straightforward, and well documented.

When you run a project which uses PdfBox, you WILL notice a lag the first time PdfBox and IKVM are loaded. After that, things seem to perform sufficiently, at least for what I needed to do.

Side Note: iTextSharp

iTextSharp is a direct port of the Java library to .Net.

iTextSharp looks to be the more robust library in terms of fine-grained control, and is extensively documented in a book by one of the authors of the library, iText in Action (Second Edition). However, the learning curve was a little steeper for iText, and I needed to get a project out the door. I will examine iTextSharp in another post, because it looks really cool, and supposedly does not suffer the performance limitations of PdfBox.

Getting started with PdfBoxVS-References-After_adding-PdfBox-and-IKVM

Before you can use PdfBox, you need to either build the project from source, or download the ready-to-use binaries. I just downloaded the binaries for version 1.2.1 from this helpful gentleman’s site, which, since they depend on IKVM, also includes the IKVM binaries. However, there are detailed instruction for building from source on the PdfBox site. Personally, I would start with the downloaded binaries to see if PdfBox is what you want to use first.

Important to note here: apparently, the PdfBox binaries are dependent upon the exact dependent DLL’s used to build them. See the notes on the PdfBox .Net Version page.

Once you have built or downloaded the binaries, you will need to set references to PdfBox and ALL the included IKVM binaries in your Visual Studio Project. Create a new Visual Studio project named “PdfBoxExamples” and add references to ALL the PdfBox and IKVM binaries. There are a LOT. Deal with it. Your project references folder will look like the picture to the right when you are done.

The PdfBox API is quite dense, but there is a handy reference at the Apache Pdfbox site.  The PDF file format is complex, to say the least, so when you first take a gander at the available classes and methods presented by the PDF box API, it can be difficult to know where to begin. Also, there is the small issue that what you are looking at is a Java API, so some of the naming conventions are a little different. Also, the PdfBox API often returns what appear to be Java classes. This comes back to that .Net implementation of the Java Class libraries I mentioned earlier.

Things to Do with PdfBox

It seems like there are three common things I often want to do with PDF files: Extract text into a string or text file, split the document into one or more parts, or merge pages or documents together. To get started with using PdfBox we will look at extracting text first, since the set up for this is pretty straightforward, and there isn’t any real Java/.Net weirdness here.

Extracting Text from a PDF File

To do this, we will call upon two PdfBox namespaces (“Packages” in Java, loosely), and two Classes:

The namespace org.apache.pdfbox.pdmodel gives us access to the PDDocument class and the namespace  org.apache.pdfbox.util   gives us the PDFTextStripper class.

In your new PdfBoxExamples project, add a new class, name it “PdfTextExtractor," and add the following code:

The PdfTextExtractor Class
using System;
using org.apache.pdfbox.pdmodel;
using org.apache.pdfbox.util;
namespace PdfBoxExamples
{
    public class pdfTextExtractor
    {
        public static String PDFText(String PDFFilePath)
        {
            PDDocument doc = PDDocument.load(PDFFilePath);
            PDFTextStripper stripper = new PDFTextStripper();
            return stripper.getText(doc);
        }
    }
}

 

As you can see, we use the PDDocument class (from the org.apache.pdfbox.pdmodel namespace) and initialize is using the static .load method defined as a class member on PDDocument. As long as we pass it a valid file path, the .load method will return an instance of PDDocument, ready for us to work with.

Once we have the PDDocument instance, we need an instance of the PDFTextStripper class, from the namespace org.apache.pdfbox.util. We pass our instance of PDDocument in as a parameter, and get back a string representing the text contained in the original PDF file.

Be prepared. PDF documents can employ some strange layouts, especially when there are tables and/or form fields involved. The text you get back will tend not to retain the formatting from the document, and in some cases can be bizarre.

However, the ability to strip text in this manner can be very useful, For example, I recently needed to download an individual PDF file for each county in the state of Missouri, and strip some tabular data our of each one. I hacked together an iterator/downloader to pull down the files, and the, using a modified version of the text stripping tool illustrated above and some rather painful Regex, I was able to get what I needed.

Splitting the Pages of a PDF File

At the simplest level, suppose you had a PDF file and you wanted to split it into individual pages. We can use the Splitter Class, again from the org.apache.pdf.util namespace. Add another class to you project, named PDFFileSplitter, and copy the following code into the editor:

The PdfFileSplitter Class
using org.apache.pdfbox.pdmodel;
using org.apache.pdfbox.util;
namespace PdfBoxExamples
{
    public class PDFFileSplitter
    {
        public static java.util.List SplitPDFFile(string SourcePath, 
            int splitPageQty = 1)
        {
            var doc = PDDocument.load(SourcePath);
            var splitter = new Splitter();
            splitter.setSplitAtPage(splitPageQty);
            return (java.util.List)splitter.split(doc);
        }
    }
}

 

Notice anything strange in the code above? That’s right. We have declared a static method with a return type of java.util.List. WHAT? This is where working with PdfBox and more importantly, IKVM becomes weird/cool. Cool, because I am using a direct Java class implementation in Visual Studio, in my C# code. Weird, because my method returns a bizarre type (from a C# perspective, anyway) that I was unsure what to do with.

I would probably add to the above class so that the splitter persisted the split documents to disk, or change the return type of my method to object[], and use the .ToArray() method, like so:

The PdfFileSplitter Class (improved?)
public static object[] SplitPDFFile(string SourcePath, 
    int splitPageQty = 1)
{
    var doc = PDDocument.load(SourcePath);
    var splitter = new Splitter();
    splitter.setSplitAtPage(splitPageQty);
    return (object[])splitter.split(doc).toArray();
}

 

In any case, the code in either example loads up the specified PDF file into a PDDocument instance, which is then passed to the org.apache.pdfbox.Splitter, along with an int parameter. The output in the example above is a Java ArrayList containing a single page from your original document in each element. Your original document is not altered by this process, by the way.

The int parameter is telling the Splitter how many pages should be in each split section. In other words, if you start with a six-page PDF file, the output will be three two-page files. If you started with a 5-page file, the output would be two two-page files and one single-page file. You get the idea.

Extract Multiple Pages from a PDF Into a New File

Something slightly more useful might be a method which accepts an array of integers as a parameter, with each integer representing a page number within a group to be extracted into a new, composite document. For example, say I needed pages 1, 6, and 7 from a 44 page PDF pulled out and merged into a new document (in reality, I needed to do this for pages 1, 6, and 7 for each of about 200  individual documents). We might add a method to our PdfFileSplitter Class as follows:

The ExtractToSingleFile Method
public static void ExtractToSingleFile(int[] PageNumbers, 
    string sourceFilePath, string outputFilePath)
{
    var originalDocument = PDDocument.load(sourceFilePath);
    var originalCatalog = originalDocument.getDocumentCatalog();
    java.util.List sourceDocumentPages = originalCatalog.getAllPages();
    var newDocument = new PDDocument();
    foreach (var pageNumber in PageNumbers)
    {
        // Page numbers are 1-based, but PDPages are contained in a zero-based array:
        int pageIndex = pageNumber - 1;
        newDocument.addPage((PDPage)sourceDocumentPages.get(pageIndex));
    }
    newDocument.save(outputFilePath);
}

 

Below is a simple example to illustrate how we might call this method from a client:

Calling the ExtractToSingleFile Method:
public void ExtractAndMergePages()
{
    string sourcePath = @"C:\SomeDirectory\YourFile.pdf";
    string outputPath = @"C:\SomeDirectory\YourNewFile.pdf";
    int[] pageNumbers = { 1, 6, 7 };
    PDFFileSplitter.ExtractToSingleFile(pageNumbers, sourcePath, outputPath);
}

 

Limit Class Dependency on PdfBox

It is always good to limit dependencies within a project. In this case, especially, I would want to keep those odd Java class references constrained to the highest degree possible. In other words, where possible, I would attempt to either return standard .net types from my classes which consume the PdfBox API, or otherwise complete execution so that client code calling upon this class doesn’t need to be aware of IKVM, or funky C#/Java hybrid types.

Or, I would build out my own “PdfUtilities” library project, within which objects are free to depend upon and intermix this Java hybrid. However, I would make sure public methods defined within the library itself accepted and returned only standard C# types.

In fact, that is precisely what I am doing, and I’ll look at that in a following post.

Links to resources:

 

Posted on January 30 2013 07:09 PM by John Atten     

Comments (2)

Extending C# Listview with Collapsible Groups (Part II)

Posted on May 11 2012 02:49 PM by John Atten in C#, CodeProject, Hacks   ||   Comments (5)

The GroupedList Control Container

This post is part two of a short series on extending the Winforms Listview control. If you missed the previous post, you can review it HERE. Also, the Source Code for this project can be found in my GitHub repo.

In our previous post, we examined the first component of what I am calling the “GroupedList Control” – essentially, a list of contained and extended Listview controls which act as independent groups. Individual ListGroups (which is how I refer to them) may contain independent column headers, and are expandable/collapsible, much like what I believe is called a “slider” control.

A brief note – I am posting somewhat abbreviated code here. I have omitted many common overloads and other features we might discuss in a future post. For now, the code posted here contains only the very core functionality under discussion. The Source, however, contains all my work so far on this control.

Also note – the GroupedListControl arose out of my need for a quick-and-dirty combination of the functionality of the Winforms Listview and a Treeview. A group of columnar lists which could be independently expanded or collapsed.

A Quick Look at a Very Plain Demo:

Gl Demo 4 Widen Column 

 

 

 

 

 

 

 

 

 

 

 

 

 

In the last post, we had assembled our basic ListGroup component, which is essentially an extension of the Winforms Listview control, modified to handle some events related to column and item addition and removal. Where we left off, it was time to assemble our container, the GroupedListControl.

I figured the quickest way to accomplish what I needed (remember – under the gun, here) would be to extend the FlowLayoutPanel such that I could use this ready-made container to manage a collection of ListGroup controls, stack them vertically, and such. There were a few issues with this approach that we will discuss in a bit. First, let’s look at the basic code required to bring the control to life:

The GroupedList Control – Basic Code:

    public class GroupListControl : FlowLayoutPanel
    {

        public GroupListControl()
        {
            // Default configuration. Adapt to suit your needs:
            this.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
            this.AutoScroll = true;
            this.WrapContents = false;

            // Add a local handler for the ControlAdded Event.
            this.ControlAdded += new ControlEventHandler(GroupListControl_ControlAdded);
        }


        /// <summary>
        /// Handles the ControlAdded Event for the current instance. 
        /// </summary>
        void GroupListControl_ControlAdded(object sender, ControlEventArgs e)
        {
            ListGroup lg = (ListGroup)e.Control;
            lg.Width = this.Width;
            lg.GroupCollapsed += new ListGroup.GroupExpansionHandler(lg_GroupCollapsed);
            lg.GroupExpanded += new ListGroup.GroupExpansionHandler(lg_GroupExpanded);
        }


        /// <summary>
        /// Gets or Sets a boolean value indicating whether multiple ListGroups
        /// may be in the expanded state at the same time. When set to true, the current expanded 
        /// ListGroup is collapsed when a new ListGroup is expanded. 
        /// </summary>
        public bool SingleItemOnlyExpansion { get; set; }


        /// <summary>
        /// Handles the Expanded event for the current instance.
        /// </summary>
        void lg_GroupExpanded(object sender, EventArgs e)
        {
            // Grab a reference to the ListGroup which sent the message:
            ListGroup expanded = (ListGroup)sender;

            // If Single item only expansion, collapse all ListGroups in except
            // the one currently exanding:
            if (this.SingleItemOnlyExpansion)
            {
                this.SuspendLayout();
                foreach (ListGroup lg in this.Controls)
                {
                    if (!lg.Equals(expanded))
                        lg.Collapse();
                }
                this.ResumeLayout(true);
            }

        }


        /// <summary>
        /// Handles the Collapsed event for the current instance.
        /// </summary>
        void lg_GroupCollapsed(object sender, EventArgs e)
        {
            // No need.
        }


        /// <summary>
        /// Expands all listgroups contained in the current instance. 
        /// </summary>
        public void ExpandAll()
        {
            foreach (ListGroup lg in this.Controls)
            {
                lg.Expand();
            }
        }


        /// <summary>
        /// Collapses all ListGroups contained in the current instance.
        /// </summary>
        public void CollapseAll()
        {
            foreach (ListGroup lg in this.Controls)
            {
                lg.Collapse();
            }
        }

    }

 

Of particular note here is the GroupListControl_ControlAdded Event Handler. Sadly, when one adds controls to the FlowLayoutPanel Controls collection, they are just that. The Controls property of the FlowLayout panel represents a ControlCollection object, which accepts a parameter of type (wait for it . . . ) Control.

I wanted MY GroupedListControl to contain a collection of ListGroup objects. However, I have not yet figured out a way to do this while retaining the functionality of the FlowLayout panel. As far as I can tell, we can’t narrow the type requirement of the native ControlCollection. One option I considered would be to add a new method to the class, named AddListGroup, which could then accept a parameter of type ListGroup, and pass THAT to the Controls.Add(Control) method. However, that seems a bit mindless, as the Controls.Add(0 method would remain publicly exposed, thus creating opportunity for confusion.

For now, I decided that those using this control will have to realize that passing anything other than a ListGroup object as the parameter will likely be disappointed in the performance of the control! It is less than elegant, but I didn’t have time to figure out a more elegant solution, and for the moment it works. I would love to hear suggestions for improvement.

The next thing to notice about the GroupListControl_ControlAdded method is that for each ListGroup we add, we are subscribing to the GroupExpanded and GroupCollapsed events sourced by each individual ListGroup. This is mainly because there are use cases in which we might want to limit group expansion to a single group at a time, such that expanding one group collapses any other expanded group. This is accomplished by providing the boolean SingleItemOnlyExpansion property. The GroupListControl_ControlAdded method checks the state of this property, and if true, collapses any expanded groups which are not equal to (as in, referencing the same object instance as) the current group (the “sender” in the method’s signature).

The last thing to note is the manner in which we set the width of each ListGroup in the GroupListControl_ControlAdded method. I tried setting the Dock property instead, and ran into difficulties with that.

Given the code above, you would think all that was pretty simple, no? Yeah. Right. A problem arose in the form of ugly scrollbars. The code above will run, and do everything represented. However, for the GroupedListControl to look like anything other than ass, we need to do something about the horizontal scrollbar which appears at the bottom of the GroupedListControl , due to the width of each ListGroup being essentially the same as the container control. This, I must say was initially giving me pains. The FlowLayoutPanel does not, apparently, afford us the ability to control the appearance of the horizontal and vertical scrollbars individually.

Some research on the interwebs yielded, after no small amount of digging, the following solution. Sadly, it involves Windows messages and API calls, neither of which I am particularly well-versed in. More sadly, I seem to have misplaced the link to where I found the solution. If YOU know where the concept below came from, please forward me a link, so I can link back, and attribute properly.

Add the following code to the end of the GroupedListControl class:

Handling Scrollbars by Intercepting Windows Messages:

        /// <summary>
        /// Consumed by the Win API calls below:
        /// </summary>
        private enum ScrollBarDirection
        {
            SB_HORZ = 0,
            SB_VERT = 1,
            SB_CTL = 2,
            SB_BOTH = 3
        }


        /// <summary>
        /// Disables the horizontal scrollbar in the primary container control.
        /// Individual ListGroups within the GroupList have their own scrollbars
        /// if needed. 
        /// </summary>
        protected override void WndProc(ref System.Windows.Forms.Message m)
        {
            // Call to unmanaged WinAPI:
            ShowScrollBar(this.Handle, (int)ScrollBarDirection.SB_HORZ, false);
            base.WndProc(ref m);
        }


        /// <summary>
        /// Imported from WinAPI: Method to control Scrollbar visibility.
        /// </summary>
        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool ShowScrollBar(IntPtr hWnd, int wBar, bool bShow);

 

The above code essentially listens to windows messages, and when it “hears” one related to showing scrollbars in the FlowLayoutPanel base class, performs the appropriate action. in this case, some sort of WinAPI magic related to NOT showing the horizontal scrollbar.

Note that we WANT the vertical scrollbar to show up, anytime the height of the collected ListGroups exceeds the height of the GroupedList client area. But I decided I would prefer to have the horizontal scrolling option available within each individual ListGroup where needed, without the extra screen clutter of another horizontal scrollbar a the bottom of the container control.

Scroll Bars in the Grouped List Control (note horizontal scroll in individual ListGroup, and vertical scroll for container control . . .)

 Gl Demo 5 Vert and Horiz Scroll

 

Summary

What we have done to this point is examine the core essentials of creating a composite control which provides some very basic behaviors I needed for a project at work. Some things to remember:

  • The code in this and the previous post is somewhat abbreviated. For example, there are a number of overloads for the Add() method on both the ListViewItemCollection and the ListViewColumnCollection which we did not address here. They are, however, mostly addressed in the Source Code on Github. I will say not all the overloads have been properly tested.
  • Another requirement I had for my control was the ability to detect Right-Mouse-Clicks on the column headers in each individual GroupedList. This capability is not built into the Listview control, and in fact it was a bit of an exercise to make it happen. More adventures with external calls to the WinApi. I will likely examine this in my next post.
  • Populating the GroupedList control takes only a little more thought and planning that doing the same with a regular Listview. In many ways, it is akin to populating a two-tiered Treeview control. The Example project in the source code repo demonstrates this in a very, very basic way. I know thus far it has met my own needs rather nicely. I needed to make a large amount of data available to the user with a minimal number of clicks, and with minimal return trips to the database.
  • I would love to hear about improvements, and especially where I have done something dumb. I am here to learn, so bring it. Feel free to fork the source, and please do put in a pull request for any changes or improvements you make.

I will try to follow up with a post about adding Right-Click detection for the ListGroup column Headers in a day or two. This enables us to deploy a different ContextMenuStrip when the user right-clicks on a columnheader vs. the standard context menu for the ListView Control.

Thanks for reading do far . . .

 

Posted on May 11 2012 02:49 PM by John Atten     

Comments (5)

Extending C# Listview with Collapsible Groups (Part I)

Posted on May 9 2012 05:11 AM by John Atten in C#, CodeProject, Hacks   ||   Comments (0)

NOTE: This post is kinda long. However, most of the length is a result of code postings (even after removing some extra stuff). Bear with me!

I’ve been deep in a project for work for the past two months. Sadly, it is nothing sexy, no exciting bleeding-edge technology, just another enterprise database, using the very mature and slightly dull Winforms library in the .NET platform.

However, I did stumble across an interesting project requirement for what is essentially an expandable group of the venerable Listview control, what one might get if one combined a Listview with a Treeview, or if the “groups” built into the Listview control could be expanded/collapsed, right-clicked, etc.

Fig. 1 – Single Group Expanded:

Gl-Demo-1_thumb6

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Fig. 2 – Multiple Groups Expanded (Note Scrollbar on Container Control):

Gl-Demo-3-Three-Expanded_thumb2

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Fig. 3 – On Widen Column (Note Scrollbar on Specific ListGroup):

Gl-Demo-4-Widen-Column_thumb3

 

 

 

 

 

 

 

 

 

 

 

 

  

 

For those who are about to point out that such a control exists in the ObjectListView, I am aware. However, I needed to do this using standard .NET Libraries. I am also aware that the standard Listview Group can be forced to expand/Collapse, but I needed this to be a faster solution. Also, causing the standard Listview Group to expand/collapse looked to rely on a whole lot of Windows API calls, and I am not so fluent in that arcane area.

My solution was to extend the Listview control, and then assemble multiple Listview controls within a FlowLayoutPanel control. The ColumnHeaders of each Listview double as the “Group.” Clicking on the left-most column toggles group expansion/collapse. The expanded/collapsed state is indicated by a solid arrow image at the left end of the column. In cases where the group is empty (containing no ListViewItems, and looking for all the world like a collapsed group) the arrow image is empty.

For the purpose of clarity, I refer to the aggregate control as a “GroupedListControl",” and each contained ListView as a “ListGroup".” There is a wide potential for improvement in this naming scheme, I am sure. For the purpose of this narrative, assume the following:

  1. A GroupedListControl Contains one or more ListGroups, which contain ListViewItems.
  2. The GroupedListControl is a container which inherits from FlowLoyoutPanel.
  3. The ListGroup is a container which inherits from the Winforms ListView.

Extending Native Listview Behaviors with Inner Classes

First, I needed to extend some of the basic behaviors of the stock .net Listview control. For example, in order to treat the leftmost column (Column [0]) differently, and to monitor the addition and removal of columns for the purpose of controlling and adjusting for the appearance of scrollbars, I needed an event to be fired when columns are added and removed. I did a little digging on the interwebs, and found my solution in a post on the Code Project site. I was able to take the core concept there and achieve what I needed:

A basic list of desired behaviors for each ListGroup include:

  1. Clicking on the leftmost ColumnHeader of a ListGroup should toggle the expansion/collapse of the group.
  2. When the first column is added, the Expanded/Collapsed indicator arrows should be added to the leftmost ColumnHeader.
  3. If the total width of the columns in any given ListGroup exceed the width of the client area of the containing GroupedListControl, the ListGroup should show a horizontal scrollbar.
  4. The height of each ListGroup should be adjusted such that all Listview items contained should be displayed in the expanded state, up to an optional maximum height determined either at design-time or runtime. If the number of ListViewItems contained exceeds this maximum, the Individual ListGroup Vertical Scrollbar will appear.
  5. Any time the Horizontal Scrollbar is displayed for a specific ListGroup, the client area for that ListGroup should be adjusted such that the Horizontal scrollbar does not partially obscure the last displayed ListViewItem.
  6. Detect Mouse Right-Clicks on specific ListView ColumnHeaders and allow for a context menu specific to right-clicking on ColumnHeaders vs. ListView Items.

This covers some minimums for the control to function properly. Let’s look at what a basic code skeleton would look like here. We will fill in some of the empty code stubs a little later in the post.

Complete source code for this project is available at GroupedListControl Project Source Code On GitHub. The source includes additional code not covered here. We will discuss some of it in upcoming posts.

First, I defined some Custom Event Argument Classes which will be utilized within the control. While they are functionally similar to some existing ListView Event Argument classes, I wanted to maintain clear naming to the degree possible. These are required by subsequent code.

Note that all examples require the following references at the head of your code file:

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Windows.Forms.Layout;
using System.Runtime.InteropServices;
using System.ComponentModel;

Custom Event Arguments:

    public class ListGroupColumnEventArgs : EventArgs
    {
        public ListGroupColumnEventArgs(int ColumnIndex)
        {
            this.ColumnIndex = ColumnIndex;
        }
        
        public ListGroupColumnEventArgs(int[] ColumnIndexes)
        {
            this.ColumnIndexes = ColumnIndexes;
        }

        public int ColumnIndex { get; set; }
        public int[] ColumnIndexes { get; set; }
    }



    public class ListGroupItemEventArgs : EventArgs
    {
        public ListGroupItemEventArgs(ListViewItem Item)
        {
            this.Item = Item;
        }

        public ListGroupItemEventArgs(ListViewItem[] Items)
        {
            this.Items = Items;
        }

        public ListViewItem Item { get; set; }
        public ListViewItem[] Items { get; set; }
    }
 

Now, the ListGroup Class itself. I have cut out some additional methods for the sake of brevity here (even at that, it’s a long chunk of code . . .), and left some code stubs to be filled in shortly. This is just to give an idea of the most basic class structure, and core functionality requirements. As it is right here, this code is not functional.

The ListGroup Class – Essentials and Code Stubs

    public class ListGroup : ListView
    {

        // DELEGATES AND ASSOCIATED EVENTS:

        // NOTE: The events and delegates related to Column and Item Addition/Removal are
        // called by the inner classes ListGroupColumnCollection and LIstGroupItemCollection. 
        // The proper function of the control depends upon these. 

        // Delegates to handle Column addition and removal Events:
        public delegate void ColumnAddedHandler(object sender, ListGroupColumnEventArgs e);
        public delegate void ColumnRemovedHandler(object sender, ListGroupColumnEventArgs e);

        // Events related to Column Addition and removal:
        public event ColumnAddedHandler ColumnAdded;
        public event ColumnRemovedHandler ColumnRemoved;

        // Delegates to handle Item Addition and Removal events:
        public delegate void ItemAddedHandler(object sender, ListGroupItemEventArgs e);
        public delegate void ItemRemovedHandler(object sender, ListGroupItemEventArgs e);

        // Events related to Item Addition and Removal:
        public event ItemAddedHandler ItemAdded;
        public event ItemRemovedHandler ItemRemoved;

        // Delegate and related events to process Group Expansion and Collapse:
        public delegate void GroupExpansionHandler(object sender, EventArgs e);
        public event GroupExpansionHandler GroupExpanded;
        public event GroupExpansionHandler GroupCollapsed;

        // Delegate and related Events to handle Listview Header Right Clicks:
        public delegate void ColumnRightClickHandler(object sender, ColumnClickEventArgs e);
        public event ColumnRightClickHandler ColumnRightClick;


        // PRIVATE INSTANCES OF INNER CLASSES

        // Instances of our inner classes, declared as private members so 
        // that our public Property accessors can be Read-only, yet allow 
        // direct manipulation from within the control instance
        private ListGroupItemCollection _Items;
        private ListGroupColumnCollection _Columns;


        /// <summary>
        /// Constructor Stub
        /// </summary>
        public ListGroup() : base()
        {
            // Implementation Code . . .
        }


        // INNER CLASS INSTANCE ACCESSORS:

        /// <summary>
        /// Hides the ListViewItemCollection internal to the base class, 
        /// and uses the new implementation defined as an inner class, 
        /// which sources an "ItemAdded" Event:
        /// </summary>
        public new ListGroupItemCollection Items
        {
            get { return _Items; }
        }


        /// <summary>
        /// Hides the ColumnHeaderCollection internal to the base class, 
        /// and uses the new implementation defined as an inner class, 
        /// which sources a "ColumnAdded" Event:
        /// </summary>
        public new ListGroupColumnCollection Columns
        {
            get { return _Columns; }
        }


        // INNER CLASS DEFINITIONS:

        /// <summary>
        /// Inner class used to hide the ListViewColumnHeaderCollection 
        /// built in to the ListView Control and provide required extended behaviors.
        /// </summary>
        public class ListGroupColumnCollection : ListView.ColumnHeaderCollection
        {
            // Implementation Code Here . . .
        }


        /// <summary>
        /// Inner class defined for ListGroup to contain List items. 
        /// Derived from ListViewItemCollection and modified to source events 
        /// indicating item addition and removal. 
        /// </summary>
        public class ListGroupItemCollection : ListView.ListViewItemCollection
        {
            // Implementation Code Here . . .
        }


        // ITEM ADDITION AND REMOVAL:


        /// <summary>
        /// Raises the ItemAdded Event when a new item is
        /// added to the items collection.
        /// </summary>
        private void OnItemAdded(ListViewItem Item)
        {
            // Code to set the size of the control to display all the items 
            // so far . . .

            // Raise the ItemAddded event to any subscribers:
            if (ItemAdded != null)
                this.ItemAdded(this, new ListGroupItemEventArgs(Item));
        }


        /// <summary>
        /// Raises the ItemRemoved Event when an item is
        /// removed from the items collection.
        /// </summary>
        private void OnItemRemoved(ListViewItem Item)
        {
            // Code to set the size of the control to display all the items 
            // remaining after the current one is removed . . .

            // Raise the ItemRemoved event to any subscribers:
            if (ItemRemoved != null)
                this.ItemRemoved(this, new ListGroupItemEventArgs(Item));
        }


        // COLUMN ADDITION AND REMOVAL:

        /// <summary>
        /// Raises the ColumnAdded Event when a new column is
        /// added to the ColumnHeaders collection.
        /// </summary>
        private void OnColumnAdded(int ColumnIndex)
        {
            // Code to manage column additions. The first column added
            // needs to have the Expand/Collapse/Empty image added . . .

            // Raise the ColumnAdded event to any subscribers:
            if (this.ColumnAdded != null)
                this.ColumnAdded(this, new ListGroupColumnEventArgs(ColumnIndex));
        }


        /// <summary>
        /// Raises the ColumnRemoved Event when a column is
        /// remmoved from the ColumnHeaders collection.
        /// </summary>
        private void OnColumnRemoved(int ColumnIndex)
        {
            // Code to manage column removals . . . 

            // Raise the ColumnRemoved event to any subscribers:
            if (this.ColumnRemoved != null)
                this.ColumnRemoved(this, new ListGroupColumnEventArgs(ColumnIndex));
        }


        // USER ACTIONS:

        /// <summary>
        /// Handles the ListGroup ColumnClick Event sourced by the base.
        /// </summary>
        void ListGroup_ColumnClick(object sender, ColumnClickEventArgs e)
        {
            // Code to manage ColumnHeader Clicks. If the Event is sourced from 
            // the first Column (Column[0], toggle expansion/collapse of the list . . .
        }


        // CONTROL BEHAVIORS:

        /// <summary>
        /// Causes the list of items to expand, showing all items in the 
        /// Items collection.
        /// </summary>
        public void Expand()
        {
            // Do stuff to make the control expand . . .

            // Raise the Expanded event to notify client code that the ListGroup has expanded:
            if (this.GroupExpanded != null)
                this.GroupExpanded(this, new EventArgs());
        }


        /// <summary>
        /// Causes the Displayed list of items to collapse, hiding all items and 
        /// displaying only the columnheaders. 
        /// </summary>
        public void Collapse()
        {
            // Do stuff to make the control collapse

            // Raise the Collapsed event to notify client code that the ListGroup has expanded:
            if (this.GroupCollapsed != null)
                this.GroupCollapsed(this, new EventArgs());
        }
    }
 

Now, pay close attention to the code stubs where we define our Inner Classes, ListGroupColumnCollection and ListGroupItemCollection. Note that each derives from its respective counterpart in the Winforms ListView Control. This is where we achieve a number of our custom event sourcing behaviors. Once again, I have simplified the class definitions here, leaving out various overloads of the core methods required (for example, there are multiple ways to “Add” an item to either collection – here I only cover the most basic. The rest are defined in the GroupedListControl Project Source Code, obtainable from my GitHub Repo).

Notice how the code within each inner class causes events to be raised within the containing ListGroup class? This provides our event sourcing for the addition/removal of Columns and ListViewItems, since these events are not defined in the base Winforms ListView Class. We need them in order to affect proper control expansion and collapse in response to additions and removals

NOTE: Core Concepts for the use of Inner Classes in this manner was adapted from THIS ARTICLE by Simon Segal on Code Project.

The following code replaces the code stub for the ListGroupColumnCollection Class in our ListGroup class definition:

The ListGroupColumnCollection Class

    /// <summary>
    /// Inner class defined for ListGroup to contain ColumnHeaders. 
    /// Derived from ListView.ColumnHeaderCollection and modified to 
    /// source events indicating column addition and removal. 
    /// </summary>
    public class ListGroupColumnCollection : ListView.ColumnHeaderCollection
    {
        // Reference to the containing ListGroup Control
        private ListGroup _Owner;

        public ListGroupColumnCollection(ListGroup Owner) : base(Owner)
        {
            _Owner = Owner;
        }


        /// <summary>
        /// Gets the total width of all columns currently defined in the control.
        /// </summary>
        public int TotalColumnWidths
        {
            get
            {
                int totalColumnWidths = 0;
                foreach(ColumnHeader clm in this)
                    totalColumnWidths = totalColumnWidths + clm.Width;
                return totalColumnWidths;
            }
        }


        /// <summary>
        /// Adds a column to the current collection and raises 
        /// the OnColumnAddedEvent on the parent control.
        /// </summary>
        public new ColumnHeader Add(string text, int width, HorizontalAlignment textAlign)
        {
            ColumnHeader clm = base.Add(text, width, textAlign);
            _Owner.OnColumnAdded(clm.Index);
            return clm;
        }


        /// <summary>
        /// Removes a column from the current collection and 
        /// raises the OnColumnRemoved Event on the parent control.
        /// </summary>
        public new void Remove(ColumnHeader column)
        {
            int index = column.Index;
            base.Remove(column);
            _Owner.OnColumnRemoved(index);
        }


        public new void Clear()
        {
            base.Clear();
        }

    } // ListGroupColumnCollection
 

The following code replaces the empty stub for the ListGroupItemCollection class in our original ListGroup class definition:

The ListGroupItemCollection Class

    /// <summary>
    /// Inner class defined for ListGroup to contain List items. Derived from ListViewItemCollection
    /// and modified to source events indicating item addition and removal. 
    /// </summary>
    public class ListGroupItemCollection : System.Windows.Forms.ListView.ListViewItemCollection
    {
        private ListGroup _Owner;
        public ListGroupItemCollection(ListGroup Owner) : base(Owner)
        {
            _Owner = Owner;
        }


        /// <summary>
        /// New implementation of Add method hides Add method defined on base class
        /// and causes an event to be sourced informing the parent about item additions.
        /// </summary>
        public new ListViewItem Add(string text)
        {
            ListViewItem item = base.Add(text);
            _Owner.OnItemAdded(item);
            return item;
        }


        /// <summary>
        /// New implementation of Remove method hides Remove method defined on base class
        /// and causes an event to be sourced informing the parent about item Removals.
        /// </summary>
        public new void Remove(ListViewItem Item)
        {
            base.Remove(Item);
            _Owner.OnItemRemoved(Item);
        }

    } // ListGroupItemCollection
 

 

Filling it all in

The most basic extension of the existing capability of the standard ListView control we are seeking is the ability for each ListGroup to “expand” and/or “collapse” in response to certain user inputs. We will want to define a singular method call which causes the desired action to be performed, so we’ll add a method to our ListGroup class named SetControlHeight. This method evaluates the current state of the control (collapsed or expanded), and calls the appropriate method to toggle that state to the opposite.

The minimum collapsed height of each ListGroup control should be just enough to display the Column Headers. The Expanded height may be unlimited, or may be constrained by setting the MaximumHeight property. In either case, however, the control height should include enough space to display the Column Headers, and an even number of ListViewItems such that the last item is fully visible in the display.

The collapsed state is recognizable if the height of the control is equal to the height of the Column Headers and the item count is greater than 0. Otherwise, the control must be in an expanded state. I had to arbitrarily set the header height as a constant which matches the default header height for the standard ListView Control (25). However, this can be modified to suit.

In order to accomplish all of the above, we will need to define a few more private members in our ListGroup Class. Add the following code in the declaration area of your class (I am still a little old-school, in that I place most of my declarations at the top of the class, just after the class declaration itself). In the Source file, the following appear just before the Constructor:

Additional Member Declarations:

        // Text strings used as Image keys for the expanded/Collapsed image in the 
        // left-most columnHeader:
        static string COLLAPSED_IMAGE_KEY = "CollapsedImage";
        static string EXPANDED_IMAGE_KEY = "ExpandedImageKey";
        static string EMPTY_IMAGE_KEY = "EmptyImageKey";

        // "Magic number" approximates the height of the List View Column Header:
        static int HEADER_HEIGHT = 25;

 

We also need a Constructor at this point. Note that the Constructor initializes the ListView.SmallImageList with some images stored in the project resources (Properties.Resources). The images are included with the . Replace the Constructor code stub with the following:

The Constructor:

    public ListGroup() : base()
    {
        this.Columns = new ListGroupColumnCollection(this);
        this.Items = new ListGroupItemCollection(this);

        // The Imagelist is used to hold images for the expanded and contracted icons in the
        // Left-most columnheader:
        this.SmallImageList = new ImageList();

        // The tilting arrow images are available in the app resources:
        this.SmallImageList.Images.Add
            (COLLAPSED_IMAGE_KEY, Properties.Resources.CollapsedGroupSmall_png_1616);
        this.SmallImageList.Images.Add
            (EXPANDED_IMAGE_KEY, Properties.Resources.ExpandedGroupSmall_png_1616);
        this.SmallImageList.Images.Add
            (EMPTY_IMAGE_KEY, Properties.Resources.EmptyGroupSmall_png_1616);

        // Default configuration (for this sample. Obviously, configure to fit your needs:
        this.View = System.Windows.Forms.View.Details;
        this.FullRowSelect = true;
        this.GridLines = true;
        this.LabelEdit = false;
        this.Margin = new Padding(0);
        this.SetAutoSizeMode(AutoSizeMode.GrowAndShrink);
        this.MaximumSize = new System.Drawing.Size(1000, 2000);
            
        // Subscribe to local Events:
        this.ColumnClick += new ColumnClickEventHandler(ListGroup_ColumnClick);
        this.ItemAdded += new ItemAddedHandler(ListGroup_ItemAdded);
    }

Now we can add code to manage the re-sizing of the control in response to user actions. While we have existing stubs for the Expand() and Collapse() methods because these formed obvious behaviors for our control, the next two will have to be added. The SetControlHeight() method is our one-stop call to adjust the height of the control:

The SetControlHeight Method:

        /// <summary>
        /// Adjusts the item display area of the control in response to changes in the 
        /// expanded or collapsed state of the control. 
        /// </summary>
        public void SetControlHeight()
        {
            if (this.Height == HEADER_HEIGHT && this.Items.Count != 0)
                this.Expand();
            else
                this.Collapse();
        }

Add the above, along with the next three methods to our ListGroup Class. The next three methods actually perform the heavy lifting in terms of adjusting the control expanded/collapsed state. The first is a function which returns the proper control height after evaluating several factors (explained in the comments). We don’t have a stub for this in our existing structure, so add it right under the SetControlHeight method:

PreferredControlHeight Function:

    private int PreferredControlHeight()
    {
        int output = HEADER_HEIGHT;
        int rowHeight = 0;

        // determine the height of an individual list item:
        if(this.Items.Count > 0)
            rowHeight = this.Items[0].Bounds.Height;

        // In case the horizontal scrollbar makes an appearance, we will
        // need to modify the height of the expanded list so that it does not
        // obscure the last item (default is 10 px to leave a little space 
        // no matter what):
        int horizScrollBarOffset = 10;

        // if the Width of the columns is greater than the width of the control, 
        // the vertical scroll bar will be shown. Increase that offset height by the 
        // height of the scrollbar (approximately the same as the height of a row):
        if (this.Columns.TotalColumnWidths > this.Width)
            horizScrollBarOffset = rowHeight + 10;

        // Increase the height of the control to accomodate the Columnheader, 
        // all of the current items, and the value of the 
        // horizontal scroll bar (if present):
        output = HEADER_HEIGHT + (this.Items.Count) * rowHeight 
        + horizScrollBarOffset + this.Groups.Count * HEADER_HEIGHT;

        return output;
    }

Then replace the Expand() and Collapse() code stubs with the following. While the PreferredControlHeight function provides the optimal height for a ListGroup, the Expand and Collapse methods perform the requested action and also cause the Expanded/Collapsed/Empty images to display properly in the left-most column:

The Expand and Collapse Methods:

    /// <summary>
    /// Causes the list of items to expand, showing all items in the 
    /// Items collection.
    /// </summary>
    public void Expand()
    {
        if (this.Columns.Count > 0)
        {
            this.Height = this.PreferredControlHeight();

            if (this.Items.Count > 0)
                // Set the image in the first column to indicate an expanded state:
                this.Columns[0].ImageKey = EXPANDED_IMAGE_KEY;
            else
                // Set the image in the first column to indicate an empty state:
                this.Columns[0].ImageKey = EMPTY_IMAGE_KEY;

            this.Scrollable = true;

            // Raise the Expanded event to notify client code 
            // that the ListGroup has expanded:
            if (this.GroupExpanded != null)
                this.GroupExpanded(this, new EventArgs());
        }
    }


    /// <summary>
    /// Causes the Displayed list of items to collapse, hiding all items and 
    /// displaying only the columnheaders. 
    /// </summary>
    public void Collapse()
    {
        if (this.Columns.Count > 0)
        {
            this.Scrollable = false;

            // Collapse the ListGroup to show only the header:
            this.Height = HEADER_HEIGHT;

            if (this.Items.Count > 0)
                // Set the image in the first column to indicate a collapsed state:
                this.Columns[0].ImageKey = COLLAPSED_IMAGE_KEY;
            else
                // Set the image in the first column to indicate an empty state:
                this.Columns[0].ImageKey = EMPTY_IMAGE_KEY;

            // Raise the Collapsed event to notify client code that 
            // the ListGroup has expanded:
            if (this.GroupCollapsed != null)
                this.GroupCollapsed(this, new EventArgs());
        }
    }

Wiring it all up

Now we need to wire up behaviors (Expand/Collapse) to the appropriate events. Some of these are obvious. When the user clicks on the left-most column (with the Expanded/Collapsed state image), the control should toggle this state. However, we also need the control to adjust its displayed area (and possible toggle the state image) when items are added/removed, and when columns are added/removed (because when columns are added, there may be a need to add the state image to the first column).

First, we will address Column addition/removal. When a column is added to the control, if it is the FIRST column, it will need the initial state image added, and the control will need to size itself. Since it is the first column, it is reasonably safe (but not 100%) to assume that there have been no items added yet, so the ListGroup is empty, and the state image should reflect this.

Add the highlighted items to the OnColumnAdded() Method:

    private void OnColumnAdded(int ColumnIndex)
    {
        

if (ColumnIndex == 0){this.Columns[0].ImageKey = EMPTY_IMAGE_KEY;this.SetControlHeight();}

        if(this.ColumnAdded != null)
            this.ColumnAdded(this, new ListGroupColumnEventArgs(ColumnIndex));
    }

What happens if the ListGroup is populated with items, and the last column is removed? I don’t have a good answer for this, other than to clear the ListItems. It seems to me that the control loses its identity and purpose. If you have thoughts about this, please do discuss in the comments, or fork the code on Github. In any case, when removing columns, we need to test and see if the column removed is the last column in the control. If so, call the Clear() Method:

    private void OnColumnRemoved(int ColumnIndex)
    {
        

if (this.Columns.Count == 0){this.Items.Clear(); }

        // Raise the ColumnRemoved event to any subscribers:
        if (this.ColumnRemoved != null)
            this.ColumnRemoved(this, new ListGroupColumnEventArgs(ColumnIndex));
    }

Now, the final piece in our simplified ListGroup is the whole thing where the user clicks on the left-most column (Column[0]), and is rewarded by the control expanding or collapsing. The state image provides a sort of visual cue/affordance to the user which implies the current state. All we have to do is handle the ColumnClick event (sourced by the base class ListView) and we’re done with this part of our (abbreviated) control

Replace the ListGroup_ColumnClick code stub with the following:

Handling the ColumnClick Event:

    void ListGroup_ColumnClick(object sender, ColumnClickEventArgs e)
    {
        int columnClicked = e.Column;
            
        // The first column (Column[0]) is what activates the expansion/collapse of the 
        // List view item group:
        if (columnClicked == 0)
        {
            this.SuspendLayout();
            this.SetControlHeight();
            this.ResumeLayout();
        }
    }

Summing Up Part I

Sadly, I need to break this project up into two parts. Even this one is too long (although much of the length is simply code samples.

In this first post, we have examined extending the Winforms ListView class so that it can serve as a component within a container control. We have also examined extending the events sourced by the ListView control through the use of inner classes, used to extend the ListViewColumnCollection and ListViewItemCollection classes.

In the next post, we will fold our ListGroup class into the container GroupedListControl. IN doing so , we will need to make a few calls to the Windows API. I hate it when that happens, but so it is (I hate it because I am not well-schooled in the Win32 API, so that kind of thing is HARD for me!). After that, we will examine some special methods to source a custom Context Menu specific to right-clicks on the List Group column headers, and a few other interesting tidbits which were necessary to make the overall control work properly.

Report Bugs. Submit Improvements. Do Good. Help Me Get Better!

I will try to get the next post up is a day or two. In the meantime, if you find over bugs in the code, or see areas for improvement in the overall implementation, please report bugs (in the comments here, or on Github), and feel free to fork the source and submit pull requests for improvements. I have said many times, I need all the help I can get!

Referenced in This Article:

 

Posted on May 9 2012 05:11 AM by John Atten     

Comments (0)

Things to love about Java: Exception Handling

Posted on November 5 2011 08:26 AM by John Atten in C#, Java, Quality Code   ||   Comments (6)

In my quest to learn and become a better developer, I undertook an excursion into Java-land. Partly this was driven by the fact that Java is the language used for Android app development, and partly because it was time to branch out and explore my first non-Microsoft-driven development platform. Until sometime in 2010, I had only worked within various Microsoft languages, mostly VB and C# .NET and VBA/VB6.

As with any significant change, there was some initial frustration. However, as I became accustomed to the Java way of doing things, I discovered a few implementation gems within the language which I really liked. Chief among these was the exception handling model.

Java identifies two categories of exception: the Checked Exception, which well designed code should anticipate and handle, and Unchecked Exceptions, which arise from errors external to the system, or within the runtime execution of the program, and which are difficult to anticipate and/or handle in any practical sense.

Java REQUIRES that any method which might potentially encounter or throw a Checked Exception adhere to a Check or Specify policy. What this means is that any method which throws such an exception must specify such as part of the method signature, and that client code consuming the method must either handle the exception, or again specify that it will throw the same exception.

A Trivial Example for Comparison Part I - The C# Way:

By way of illustrating the difference between exception handling in C# and that of Java, we will create a simple library class called RentalAgreement (We are really just focusing on exception handling here, and this is a REALLY trivial example, so I don't wanna hear about problems with the business logic, or clunkiness of the examples!) A rental agreement has a start date and an end date (and some additional information, but for the purpose of brevity, we will leave our class at that for the moment).

C# Example 1 – Basic C# Code:

    public class RentalAgreement
    {
        private DateTime _startDate;
        private DateTime _endDate;
    
        publicRentalAgreement(DateTime StartDate, DateTime EndDate)
        {
            _startDate = StartDate;
            _endDate = EndDate;
        }

The constructor for this class accepts two arguments, a start date and and end date. Since our business model dictates that the end date must occur AFTER the start date, we might want to set up some exception handling to ensure a valid range between the start and end dates. Our code can then propagate an exception to any client code if such an event occurs. We'll modify our class slightly, adding a local function to compare two dates for precedence, and a if/else throw block in the constructor:

C# Example 2 – Improved C# Code:

    public class RentalAgreement
    {
        private DateTime _startDate;
        private DateTime _endDate;

        public RentalAgreement(DateTime StartDate, DateTime EndDate)
        {
            //Use local function NoPrecedence to compare the start and end dates:
            if (this.NoPrecedence(StartDate, EndDate))
            {
                _startDate = StartDate;
                _endDate = EndDate;
            }
            else
            {
                // If the end date occurs before the start date, let client 
               // code know about it:
                throw (new Exception("The end date cannot occur before the start date"));
            }
        }      

        private bool NoPrecedence(DateTime StartDate, DateTime EndDate)
        {
            if(EndDate < StartDate)
            {
                return true;
            }
            return false;
        }
    } 

 

The exception thrown in the constructor will propagate up the call stack to the client code, which can then implement some well-thought-out handling. Or not. It could be that our erstwhile developer might have overlooked the need to validate user input, or otherwise missed the potential exception case. In any case, the following test code mimics what might happen if a user were to enter a start date of 1/1/2011, and an end date of 12/31/2010:

C# Example 3 – Bad, BAD Client Code:

    private void button1_Click(object sender, EventArgs e)
    {
        DateTime startDate = new DateTime(2011, 1, 1);
        DateTime endDate = new DateTime(2010, 12, 31);

        RentalAgreement rentalAgreement = new RentalAgreement(startDate, endDate);
        MessageBox.Show("Start Date = " 
            + startDate.ToShortDateString() 
            + " : End Date = " + endDate.ToShortDateString());
    }

 

However it happened, our hapless user, on entering the above incorrect date combination, would be faced with THIS ugliness:

Exception-Message-to-User-C-Sharp-Ex[2]

 

 

 

 

 

 

Of course, all of this might be averted if our developer implements some exception handling in his client code:

C# Example 4 – Much Better Client Code (kind of):

    private void button1_Click(object sender, EventArgs e)
    {
        DateTime startDate = new DateTime(2011, 1, 1);
        DateTime endDate = new DateTime(2010, 12, 31);

        try
        {
            RentalAgreement rentalAgreement = new RentalAgreement(startDate, endDate);
            MessageBox.Show("Start Date = "
                + startDate.ToShortDateString()
                + " : End Date = " + endDate.ToShortDateString());
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);            
            // Now do some stuff to reset the GUI so that the user can see exactly
            // where they screwed up . . . 
        }
    }

 

A Trivial Example for Comparison Part II – The Java Way

The Java version of our class differs only slightly from the C# code. If we add the throw statement to our code in the else block before we add the throws declaration to the method signature, the compiler (I am using Eclipse) warns us that our method presents an unhandled exception, and will not compile. This is the Check or Specify policy informing us that we either need to handle the exception condition within the current method, or specify in the method signature that there is potential for the exception to occur, and that client code must provide the handling mechanism. Once we add the throws declaration in the method signature, everything is fine again:

Java Example #1 – with throws keyword

    public class RentalAgreement 
    {
        private Calendar startDate;
        private Calendar endDate;
        
        // Note the throws clause of the method signature:
        public RentalAgreement(Calendar StartDate, Calendar EndDate) throws Exception
        {
            if (this.NoPrecedence(StartDate, EndDate))
            {
                startDate = StartDate;
                endDate = EndDate;            
            }
            else
            {
                // Because this method throws a checked exception, we are REQUIRED
                // to either handle the exception condition or specify in the method 
                // signature that the exception might be thrown.
                throw (new Exception(""));
            }
        }
        

        private boolean NoPrecedence(Calendar StartDate, Calendar EndDate)
        {
            if(EndDate.getTimeInMillis() < StartDate.getTimeInMillis())
            {
                return true;
            }
            return false;
        }
    }  

Now, we have defined a library class containing a method which throws a checked exception. Next, lets create another silly piece of code which consumes the class, mimicking some faulty user input:

Java Example #2 – Consuming the Method

    public static void main(String[] args)
    {
        //Mimic some user input:
        Calendar startDate = Calendar.getInstance();
        startDate.set(Calendar.YEAR, 2011);
        startDate.set(Calendar.MONTH, Calendar.JANUARY);
        startDate.set(Calendar.DAY_OF_MONTH, 1);
        
        Calendar endDate = Calendar.getInstance();
        endDate.set(Calendar.YEAR, 2010);
        endDate.set(Calendar.MONTH, Calendar.DECEMBER);
        endDate.set(Calendar.DAY_OF_MONTH, 31);
        
        //Attempt to create an instance of the RentalAgreement class:
        RentalAgreement newRentalAgreement = new RentalAgreement(startDate, endDate);
        
    }

The compiler flags our code at the point where we attempt to create an instance of the Rental Agreement class, and in fact will not compile as written. Why? Because the constructor of the RentalAgreement class posits that it might throw an exception, and we have not provided a handling or propagation mechanism for this. Our client code is REQUIRED by Java to either Check the exception (most often with a try . . .catch block) or Specify, again, that the exception may be raised by the current method, and declared as part of the method signature. Since the current code represents the application entry point, we will need to provide some graceful handling (with a try . . . catch block) of the exception before our application will even compile:

Java Example #3 – Client Code with Exception Handling:

    public static void main(String[] args)
    {
        //Mimic some user input:
        Calendar startDate = Calendar.getInstance();
        startDate.set(Calendar.YEAR, 2011);
        startDate.set(Calendar.MONTH, Calendar.JANUARY);
        startDate.set(Calendar.DAY_OF_MONTH, 1);
        
        Calendar endDate = Calendar.getInstance();
        endDate.set(Calendar.YEAR, 2010);
        endDate.set(Calendar.MONTH, Calendar.DECEMBER);
        endDate.set(Calendar.DAY_OF_MONTH, 31);
        
        //Attempt to create an instance of the RentalAgreement class:
        try
        {
            RentalAgreement newRentalAgreement = new RentalAgreement(startDate, endDate);
            SimpleDateFormat formatter = new SimpleDateFormat("MM/dd/yyyy");
            System.out.print("State date = " + 
                    formatter.format(startDate.getTime()) + 
                    " : End Date = " + 
                    formatter.format(endDate.getTime()));
        }
        catch (Exception ex)
        {
            // Inform the user about the error of their ways:
            System.out.print(ex.getMessage());
        }        
    }

 

The code in Java Example #3 compiles and runs properly.

Note that not ALL exceptions receive this special treatment within Java. Unchecked Exceptions which derive from the java.lang.RuntimeException or java.lang.Error do NOT require adherence to the Check or Specify policy. In fact, because the requirement to build the Check or Specify mechanism into code is often viewed as a pain the ass, some Java developers tend to write code which throws RuntimeExceptions where in fact a checked exception is warranted, or derive their own Exception classes from RuntimeException in order to avoid writing a bunch of handling code and/or adding the throws clause to method signatures.

In my humble opinion, these folks are depriving themselves (and more importantly, consumers of their code) of one of the more useful benefits of the Java language architecture. Yes, it IS a pain in the ass to follow up and Check/Specify all those Checked Exceptions. But this requires us to construct better code, in which many of the exceptional cases which should either be pinned down with proper handling, or eliminated through design improvements and structural code changes. This ALSO provides an informative mechanism for developers who may use our libraries in their own applications, through which they will know straight away what type of exception to expect when calling one of our methods.

I am a strong fan of C# and .NET in general. But one area where the designers of the Java language got things right was in requiring such handling of exceptions, and the Check/Specify policy.

 

Posted on November 5 2011 08:26 AM by John Atten     

Comments (6)

About the author

My name is John Atten, and my username on many of my online accounts is xivSolutions. I am Fascinated by all things technology and software development. I work mostly with C#, Java, SQL Server 2012, learning ASP.NET MVC, html 5/CSS/Javascript. I am always looking for new information, and value your feedback (especially where I got something wrong!). You can email me at:

jatten@typecastexception.com

Web Hosting by