Keeps track of temporary images that have been created in a temporary folder.
This is meant for use by classes that create a temporary folder containing temporary images for use by another class.

If a temporary folder is created, store its path in the property. If one or more temporary images are created in the folder, store their file names in the dictionary returned by FileNames, and store their size in .

Inheritance: Object
        CreateAndSaveSubgraphImages
        (
            IGraph oSubgraph,
            String sVertexName,
            CreateSubgraphImagesAsyncArgs oCreateSubgraphImagesAsyncArgs,
            TemporaryImages oThumbnailImages
        )
        {
            Debug.Assert(oSubgraph != null);
            Debug.Assert(!String.IsNullOrEmpty(sVertexName));
            Debug.Assert(oCreateSubgraphImagesAsyncArgs != null);
            Debug.Assert(oThumbnailImages != null);
            AssertValid();

            if (oCreateSubgraphImagesAsyncArgs.SaveToFolder)
            {
                CreateAndSaveSubgraphImageInFolder(oSubgraph, sVertexName,
                                                   oCreateSubgraphImagesAsyncArgs);
            }

            if (oCreateSubgraphImagesAsyncArgs.CreateThumbnails)
            {
                CreateAndSaveThumbnailImage(oSubgraph, sVertexName,
                                            oCreateSubgraphImagesAsyncArgs, oThumbnailImages);
            }
        }
        CreateAndSaveThumbnailImage
        (
            IGraph oSubgraph,
            String sVertexName,
            CreateSubgraphImagesAsyncArgs oCreateSubgraphImagesAsyncArgs,
            TemporaryImages oThumbnailImages
        )
        {
            Debug.Assert(oSubgraph != null);
            Debug.Assert(!String.IsNullOrEmpty(sVertexName));
            Debug.Assert(oCreateSubgraphImagesAsyncArgs != null);
            Debug.Assert(oCreateSubgraphImagesAsyncArgs.CreateThumbnails);
            Debug.Assert(oThumbnailImages != null);
            AssertValid();

            if (oThumbnailImages.Folder == null)
            {
                // Create a temporary folder where the thumbnail images will be
                // stored.

                String sTemporaryFolder = Path.Combine(
                    Path.GetTempPath(),
                    Path.GetRandomFileName()
                    );

                Directory.CreateDirectory(sTemporaryFolder);

                oThumbnailImages.Folder = sTemporaryFolder;
            }

            // Save the graph to a bitmap.

            Bitmap oBitmap = CreateSubgraphImage(oSubgraph,
                                                 oCreateSubgraphImagesAsyncArgs,
                                                 oCreateSubgraphImagesAsyncArgs.ThumbnailSizePx);

            try
            {
                // Save the bitmap in the temporary folder.

                String sTemporaryFileName = SaveSubgraphImage(oBitmap,
                                                              oThumbnailImages.Folder, sVertexName,
                                                              oCreateSubgraphImagesAsyncArgs);

                // Add the file name to the dictionary.  They key is the vertex
                // name and the value is the file name, without a path.

                oThumbnailImages.FileNames[sVertexName] = sTemporaryFileName;
            }
            finally
            {
                GraphicsUtil.DisposeBitmap(ref oBitmap);
            }
        }
        PopulateSubgraphImageColumn
        (
            Workbook workbook,
            TemporaryImages temporarySubgraphImages
        )
        {
            Debug.Assert(workbook != null);
            Debug.Assert(temporarySubgraphImages != null);

            Boolean bOriginalScreenUpdating = workbook.Application.ScreenUpdating;

            workbook.Application.ScreenUpdating = false;

            try
            {
                PopulateSubgraphImageColumnInternal(workbook,
                                                    temporarySubgraphImages);
            }
            finally
            {
                workbook.Application.ScreenUpdating = bOriginalScreenUpdating;
            }
        }
        CreateSubgraphImagesInternal
        (
            CreateSubgraphImagesAsyncArgs oCreateSubgraphImagesAsyncArgs,
            BackgroundWorker oBackgroundWorker,
            DoWorkEventArgs oDoWorkEventArgs
        )
        {
            Debug.Assert(oCreateSubgraphImagesAsyncArgs != null);
            Debug.Assert(oBackgroundWorker != null);
            Debug.Assert(oDoWorkEventArgs != null);
            AssertValid();

            // Create an object to keep track of the thumbnail images this method
            // creates and stores in a temporary folder.

            TemporaryImages oThumbnailImages = new TemporaryImages();

            oThumbnailImages.ImageSizePx =
                oCreateSubgraphImagesAsyncArgs.ThumbnailSizePx;

            oDoWorkEventArgs.Result = oThumbnailImages;

            ICollection <IVertex> oVertices;

            if (oCreateSubgraphImagesAsyncArgs.SelectedVerticesOnly)
            {
                oVertices = oCreateSubgraphImagesAsyncArgs.SelectedVertices;
            }
            else
            {
                oVertices = oCreateSubgraphImagesAsyncArgs.Graph.Vertices;
            }

            Int32 iSubgraphsCreated = 0;

            Boolean bSaveToFolder = oCreateSubgraphImagesAsyncArgs.SaveToFolder;

            Boolean bCreateThumbnails =
                oCreateSubgraphImagesAsyncArgs.CreateThumbnails;

            if (bSaveToFolder || bCreateThumbnails)
            {
                foreach (IVertex oVertex in oVertices)
                {
                    if (oBackgroundWorker.CancellationPending)
                    {
                        if (oThumbnailImages.Folder != null)
                        {
                            // Delete the entire temporary folder.

                            Directory.Delete(oThumbnailImages.Folder, true);

                            oThumbnailImages.Folder = null;
                        }

                        oDoWorkEventArgs.Cancel = true;
                        break;
                    }

                    String sVertexName = oVertex.Name;

                    oBackgroundWorker.ReportProgress(0,
                                                     String.Format(
                                                         "Creating subgraph image for \"{0}\"."
                                                         ,
                                                         sVertexName
                                                         ));

                    // Create a subgraph for the vertex.

                    IGraph oSubgraph = CreateSubgraph(oVertex,
                                                      oCreateSubgraphImagesAsyncArgs);

                    // Create and save images for the subgraph.

                    CreateAndSaveSubgraphImages(oSubgraph, sVertexName,
                                                oCreateSubgraphImagesAsyncArgs, oThumbnailImages);

                    iSubgraphsCreated++;
                }
            }

            oBackgroundWorker.ReportProgress(0,
                                             String.Format(
                                                 "Done.  Created {0} subgraph {1}."
                                                 ,
                                                 iSubgraphsCreated.ToString(ExcelTemplateForm.Int32Format),
                                                 StringUtil.MakePlural("image", iSubgraphsCreated)
                                                 ));
        }
    PopulateColumnWithImages
    (
        Workbook workbook,
        String worksheetName,
        String tableName,
        String imageColumnName,
        String keyColumnName,
        TemporaryImages temporaryImages
    )
    {
        Debug.Assert(workbook != null);
        Debug.Assert( !String.IsNullOrEmpty(worksheetName) );
        Debug.Assert( !String.IsNullOrEmpty(tableName) );
        Debug.Assert( !String.IsNullOrEmpty(imageColumnName) );
        Debug.Assert( !String.IsNullOrEmpty(keyColumnName) );
        Debug.Assert(temporaryImages != null);

        ListObject oTable;
        Range oKeyColumnData;

        // Get the table and the key column data.

        if (!ExcelTableUtil.TryGetTable(workbook, worksheetName, tableName,
                out oTable) 
            ||
            !ExcelTableUtil.TryGetTableColumnData(oTable, keyColumnName,
                out oKeyColumnData)
            )
        {
            // Nothing can be done without the table or key column.

            return;
        }

        Range oImageColumnData;

        // Add the image column if it doesn't already exist.

        if ( !TryGetImageColumnData(oTable, imageColumnName,
            out oImageColumnData) )
        {
            // The image column doesn't exist and couldn't be added.

            return;
        }

        String sFolder = temporaryImages.Folder;

        if (sFolder == null)
        {
            // No temporary images were created, so nothing more needs to be
            // done.

            return;
        }

        // Reduce the key and image column data to visible areas only.

        Range oVisibleKeyColumnData, oVisibleImageColumnData;

        if (
            !ExcelUtil.TryGetVisibleRange(oKeyColumnData,
                out oVisibleKeyColumnData)
            ||
            !ExcelUtil.TryGetVisibleRange(oImageColumnData,
                out oVisibleImageColumnData)
            )
        {
            return;
        }

        Int32 iAreas = oVisibleKeyColumnData.Areas.Count;

        if (iAreas != oVisibleImageColumnData.Areas.Count)
        {
            return;
        }

        // Get the size of each image, in points.

        SizeF oImageSizePt =
            GetImageSizePt(temporaryImages.ImageSizePx, workbook);

        // Get any old images in the image column as a dictionary.  This
        // significantly speeds up the deletion of the old images, because
        // Excel doesn't have to do a linear search on Shape.Name as each image
        // is deleted by PopulateAreaWithImages().

        Debug.Assert(oTable.Parent is Worksheet);

        Dictionary<String, Microsoft.Office.Interop.Excel.Shape>
            oOldImagesInColumn = GetImagesInColumn( (Worksheet)oTable.Parent,
                imageColumnName );

        // Populate each area of the image column with images.

        workbook.Application.ScreenUpdating = false;

        try
        {
            for (Int32 iArea = 1; iArea <= iAreas; iArea++)
            {
                PopulateAreaWithImages(oVisibleKeyColumnData.Areas[iArea],
                    oVisibleImageColumnData.Areas[iArea], imageColumnName,
                    oImageSizePt, oOldImagesInColumn, temporaryImages);
            }
        }
        finally
        {
            workbook.Application.ScreenUpdating = true;
        }

        // Delete the entire temporary folder.

        try
        {
            Directory.Delete(sFolder, true);
        }
        catch (IOException)
        {
            // A user reported the following exception thrown from the above
            // Directory.Delete() call:
            //
            // "System.IO.IOException: The directory is not empty.:
            //
            // Others have reported this happenning at random times.  For
            // example:
            //
            // http://forums.asp.net/p/1114215/1722498.aspx
            //
            // I have also seen it happen from the command line outside of
            // .NET.  When it occurs, the directory IS empty but cannot be
            // accessed in any way.  The directory disappears when the machine
            // is rebooted.
            //
            // I can't figure out the cause or the fix.  Ignore the problem,
            // which seems to be benign.
        }
    }
    PopulateAreaWithImages
    (
        Range oKeyColumnArea,
        Range oImageColumnArea,
        String sImageColumnName,
        SizeF oImageSizePt,
        Dictionary<String, Microsoft.Office.Interop.Excel.Shape>
            oOldImagesInColumn,
        TemporaryImages oTemporaryImages
    )
    {
        Debug.Assert(oKeyColumnArea != null);
        Debug.Assert(oImageColumnArea != null);
        Debug.Assert( !String.IsNullOrEmpty(sImageColumnName) );
        Debug.Assert(oOldImagesInColumn != null);
        Debug.Assert(oTemporaryImages != null);

        // Gather some required information.

        Int32 iRows = oKeyColumnArea.Rows.Count;

        Debug.Assert(iRows == oImageColumnArea.Rows.Count);

        Debug.Assert(oKeyColumnArea.Parent is Worksheet);

        Worksheet oWorksheet = (Worksheet)oKeyColumnArea.Parent;

        Microsoft.Office.Interop.Excel.Shapes oShapes = oWorksheet.Shapes;

        Object [,] aoKeyValues = ExcelUtil.GetRangeValues(oKeyColumnArea);

        Dictionary<String, String> oFileNames = oTemporaryImages.FileNames;

        // Set the row heights to fit the images.

        oKeyColumnArea.RowHeight = oImageSizePt.Height + 2 * ImageMarginPt;

        // Get the first cell in the image column.

        Range oImageCell = (Range)oImageColumnArea.Cells[1, 1];

        // Loop through the area's rows.

        for (Int32 iRow = 1; iRow <= iRows; iRow++)
        {
            String sKey, sFileName;

            // Check whether the row's key cell has a corresponding file name
            // in the dictionary.

            if (
                ExcelUtil.TryGetNonEmptyStringFromCell(aoKeyValues, iRow, 1,
                    out sKey)
                &&
                oFileNames.TryGetValue(sKey, out sFileName)
                )
            {
                // Give the picture a name that can be recognized by
                // GetImagesInColumn().

                String sPictureName = sImageColumnName + "-" + sKey;

                Microsoft.Office.Interop.Excel.Shape oPicture;

                // If an old version of the picture remains from a previous
                // call to this method, delete it.

                if ( oOldImagesInColumn.TryGetValue(sPictureName,
                    out oPicture) )
                {
                    oPicture.Delete();
                }

                String sFileNameWithPath = Path.Combine(
                    oTemporaryImages.Folder, sFileName);

                oPicture = oShapes.AddPicture(sFileNameWithPath,
                    MsoTriState.msoFalse, MsoTriState.msoCTrue,
                    (Single)(Double)oImageCell.Left + ImageMarginPt,
                    (Single)(Double)oImageCell.Top + ImageMarginPt,
                    oImageSizePt.Width,
                    oImageSizePt.Height
                    );

                oPicture.Name = sPictureName;
            }

            // Move down one cell in the image column.

            oImageCell = oImageCell.get_Offset(1, 0);
        }
    }
        PopulateAreaWithSubgraphImages
        (
            Range oNameColumnArea,
            Range oSubgraphImageColumnArea,
            SizeF oSubgraphImageSizePt,

            Dictionary <String, Microsoft.Office.Interop.Excel.Shape>
            oOldSubgraphImages,

            TemporaryImages oTemporarySubgraphImages
        )
        {
            Debug.Assert(oNameColumnArea != null);
            Debug.Assert(oSubgraphImageColumnArea != null);
            Debug.Assert(oOldSubgraphImages != null);
            Debug.Assert(oTemporarySubgraphImages != null);

            // Gather some required information.

            Int32 iRows = oNameColumnArea.Rows.Count;

            Debug.Assert(iRows == oSubgraphImageColumnArea.Rows.Count);

            Debug.Assert(oNameColumnArea.Parent is Worksheet);

            Worksheet oWorksheet = (Worksheet)oNameColumnArea.Parent;

            Microsoft.Office.Interop.Excel.Shapes oShapes = oWorksheet.Shapes;

            Object [,] aoNameValues = ExcelUtil.GetRangeValues(oNameColumnArea);

            Dictionary <String, String> oFileNames =
                oTemporarySubgraphImages.FileNames;

            // Set the row heights to fit the images.

            oNameColumnArea.RowHeight =
                oSubgraphImageSizePt.Height + 2 * SubgraphImageMarginPt;

            // Get the first cell in the subgraph image column.

            Range oSubgraphImageCell = (Range)oSubgraphImageColumnArea.Cells[1, 1];

            // Loop through the area's rows.

            for (Int32 iRow = 1; iRow <= iRows; iRow++)
            {
                String sName, sFileName;

                // Check whether the row's name cell has a corresponding file name
                // in the dictionary.

                if (
                    ExcelUtil.TryGetNonEmptyStringFromCell(aoNameValues, iRow, 1,
                                                           out sName)
                    &&
                    oFileNames.TryGetValue(sName, out sFileName)
                    )
                {
                    // Give the picture a name that can be recognized by
                    // GetSubgraphImageDictionary().

                    String sPictureName =
                        VertexTableColumnNames.SubgraphImage + "-" + sName;

                    Microsoft.Office.Interop.Excel.Shape oPicture;

                    // If an old version of the picture remains from a previous
                    // call to this method, delete it.

                    if (oOldSubgraphImages.TryGetValue(sPictureName,
                                                       out oPicture))
                    {
                        oPicture.Delete();
                    }

                    String sFileNameWithPath = Path.Combine(
                        oTemporarySubgraphImages.Folder, sFileName);

                    oPicture = oShapes.AddPicture(sFileNameWithPath,
                                                  MsoTriState.msoFalse, MsoTriState.msoCTrue,

                                                  (Single)(Double)oSubgraphImageCell.Left
                                                  + SubgraphImageMarginPt,

                                                  (Single)(Double)oSubgraphImageCell.Top
                                                  + SubgraphImageMarginPt,

                                                  oSubgraphImageSizePt.Width,
                                                  oSubgraphImageSizePt.Height
                                                  );

                    oPicture.Name = sPictureName;
                }

                // Move down one cell in the image column.

                oSubgraphImageCell = oSubgraphImageCell.get_Offset(1, 0);
            }
        }
        PopulateSubgraphImageColumnInternal
        (
            Workbook oWorkbook,
            TemporaryImages oTemporarySubgraphImages
        )
        {
            Debug.Assert(oWorkbook != null);
            Debug.Assert(oTemporarySubgraphImages != null);

            ListObject oVertexTable;
            Range      oVisibleNameColumnData, oVisibleSubgraphImageColumnData;
            String     sTemporaryImageFolder = oTemporarySubgraphImages.Folder;

            if (
                // If the vertex table, the name column data, or the image column
                // data aren't available, nothing can be done.

                !TryGetVertexTableAndVisibleColumnData(oWorkbook, out oVertexTable,
                                                       out oVisibleNameColumnData,
                                                       out oVisibleSubgraphImageColumnData)

                // If no temporary images were created, nothing more needs to be
                // done.

                ||
                sTemporaryImageFolder == null
                )

            {
                return;
            }

            Int32 iAreas = oVisibleNameColumnData.Areas.Count;

            if (iAreas != oVisibleSubgraphImageColumnData.Areas.Count)
            {
                return;
            }

            SizeF oSubgraphImageSizePt = GetSubgraphImageSizePt(
                oTemporarySubgraphImages.ImageSizePx, oWorkbook);

            // Get any old images in the image column as a dictionary.  This
            // significantly speeds up the deletion of the old images, because
            // Excel doesn't have to do a linear search on Shape.Name as each image
            // is deleted by PopulateAreaWithSubgraphImages().

            Debug.Assert(oVertexTable.Parent is Worksheet);

            Dictionary <String, Microsoft.Office.Interop.Excel.Shape>
            oOldSubgraphImages = GetSubgraphImageDictionary(
                (Worksheet)oVertexTable.Parent);

            for (Int32 iArea = 1; iArea <= iAreas; iArea++)
            {
                PopulateAreaWithSubgraphImages(
                    oVisibleNameColumnData.Areas[iArea],
                    oVisibleSubgraphImageColumnData.Areas[iArea],
                    oSubgraphImageSizePt, oOldSubgraphImages,
                    oTemporarySubgraphImages);
            }

            DeleteTemporaryImageFolder(sTemporaryImageFolder);
        }
        OnImageCreationCompleted
        (
            RunWorkerCompletedEventArgs e
        )
        {
            AssertValid();
            Debug.Assert(m_oWorkbook != null);
            Debug.Assert(m_oSelectedVertexNames != null);

            if (e.Cancelled)
            {
                this.State = DialogState.Idle;

                lblStatus.Text = "Image creation stopped.";
            }
            else if (e.Error != null)
            {
                this.State = DialogState.Idle;

                Exception oException = e.Error;

                if (oException is System.IO.IOException)
                {
                    lblStatus.Text = "Image creation error.";

                    this.ShowWarning(oException.Message);
                }
                else
                {
                    ErrorUtil.OnException(oException);
                }
            }
            else
            {
                // Success.  Were temporary images created that need to be inserted
                // into the vertex worksheet?

                Debug.Assert(e.Result is TemporaryImages);

                TemporaryImages oTemporaryImages = (TemporaryImages)e.Result;

                if (oTemporaryImages.Folder != null)
                {
                    // Yes.  Insert them, then delete the temporary images.

                    this.State = DialogState.PopulatingImageColumn;

                    String sLastStatusFromSubgraphImageCreator = lblStatus.Text;

                    lblStatus.Text =
                        "Inserting subgraph thumbnails into the worksheet.  Please"
                        + " wait...";

                    SubgraphImageColumnPopulator.PopulateSubgraphImageColumn(
                        m_oWorkbook, oTemporaryImages);

                    lblStatus.Text = sLastStatusFromSubgraphImageCreator;
                }

                this.State = DialogState.Idle;

                if (m_eMode == DialogMode.Automate)
                {
                    this.Close();
                }
            }
        }
    CreateAndSaveThumbnailImage
    (
        IGraph oSubgraph,
        String sVertexName,
        CreateSubgraphImagesAsyncArgs oCreateSubgraphImagesAsyncArgs,
        TemporaryImages oThumbnailImages
    )
    {
        Debug.Assert(oSubgraph != null);
        Debug.Assert( !String.IsNullOrEmpty(sVertexName) );
        Debug.Assert(oCreateSubgraphImagesAsyncArgs != null);
        Debug.Assert(oCreateSubgraphImagesAsyncArgs.CreateThumbnails);
        Debug.Assert(oThumbnailImages != null);
        AssertValid();

        if (oThumbnailImages.Folder == null)
        {
            // Create a temporary folder where the thumbnail images will be
            // stored.

            String sTemporaryFolder = Path.Combine(
                Path.GetTempPath(),
                Path.GetRandomFileName()
                );

            Directory.CreateDirectory(sTemporaryFolder);

            oThumbnailImages.Folder = sTemporaryFolder;
        }

        // Save the graph to a bitmap.

        Bitmap oBitmap = CreateSubgraphImage(oSubgraph,
            oCreateSubgraphImagesAsyncArgs,
            oCreateSubgraphImagesAsyncArgs.ThumbnailSizePx);

        try
        {
            // Save the bitmap in the temporary folder.

            String sTemporaryFileName = SaveSubgraphImage(oBitmap,
                oThumbnailImages.Folder, sVertexName,
                oCreateSubgraphImagesAsyncArgs);

            // Add the file name to the dictionary.  They key is the vertex
            // name and the value is the file name, without a path.

            oThumbnailImages.FileNames[sVertexName] = sTemporaryFileName;
        }
        finally
        {
            GraphicsUtil.DisposeBitmap(ref oBitmap);
        }
    }
    CreateAndSaveSubgraphImages
    (
        IGraph oSubgraph,
        String sVertexName,
        CreateSubgraphImagesAsyncArgs oCreateSubgraphImagesAsyncArgs,
        TemporaryImages oThumbnailImages
    )
    {
        Debug.Assert(oSubgraph != null);
        Debug.Assert( !String.IsNullOrEmpty(sVertexName) );
        Debug.Assert(oCreateSubgraphImagesAsyncArgs != null);
        Debug.Assert(oThumbnailImages != null);
        AssertValid();

        if (oCreateSubgraphImagesAsyncArgs.SaveToFolder)
        {
            CreateAndSaveSubgraphImageInFolder(oSubgraph, sVertexName,
                oCreateSubgraphImagesAsyncArgs);
        }

        if (oCreateSubgraphImagesAsyncArgs.CreateThumbnails)
        {
            CreateAndSaveThumbnailImage(oSubgraph, sVertexName,
                oCreateSubgraphImagesAsyncArgs, oThumbnailImages);
        }
    }
    CreateSubgraphImagesInternal
    (
        CreateSubgraphImagesAsyncArgs oCreateSubgraphImagesAsyncArgs,
        BackgroundWorker oBackgroundWorker,
        DoWorkEventArgs oDoWorkEventArgs
    )
    {
        Debug.Assert(oCreateSubgraphImagesAsyncArgs != null);
        Debug.Assert(oBackgroundWorker != null);
        Debug.Assert(oDoWorkEventArgs != null);
        AssertValid();

        // Create an object to keep track of the thumbnail images this method
        // creates and stores in a temporary folder.

        TemporaryImages oThumbnailImages = new TemporaryImages();

        oThumbnailImages.ImageSizePx =
            oCreateSubgraphImagesAsyncArgs.ThumbnailSizePx;

        oDoWorkEventArgs.Result = oThumbnailImages;

        ICollection<IVertex> oVertices;

        if (oCreateSubgraphImagesAsyncArgs.SelectedVerticesOnly)
        {
            oVertices = oCreateSubgraphImagesAsyncArgs.SelectedVertices;
        }
        else
        {
            oVertices = oCreateSubgraphImagesAsyncArgs.Graph.Vertices;
        }

        Int32 iSubgraphsCreated = 0;

        Boolean bSaveToFolder = oCreateSubgraphImagesAsyncArgs.SaveToFolder;

        Boolean bCreateThumbnails =
            oCreateSubgraphImagesAsyncArgs.CreateThumbnails;

        if (bSaveToFolder || bCreateThumbnails)
        {
            foreach (IVertex oVertex in oVertices)
            {
                if (oBackgroundWorker.CancellationPending)
                {
                    if (oThumbnailImages.Folder != null)
                    {
                        // Delete the entire temporary folder.

                        Directory.Delete(oThumbnailImages.Folder, true);

                        oThumbnailImages.Folder = null;
                    }

                    oDoWorkEventArgs.Cancel = true;
                    break;
                }

                String sVertexName = oVertex.Name;

                oBackgroundWorker.ReportProgress(0,
                    String.Format(
                        "Creating subgraph image for \"{0}\"."
                        ,
                        sVertexName
                    ) );

                // Create a subgraph for the vertex.

                IGraph oSubgraph = CreateSubgraph(oVertex,
                    oCreateSubgraphImagesAsyncArgs);

                // Create and save images for the subgraph.

                CreateAndSaveSubgraphImages(oSubgraph, sVertexName,
                    oCreateSubgraphImagesAsyncArgs, oThumbnailImages);

                iSubgraphsCreated++;
            }
        }

        oBackgroundWorker.ReportProgress(0,
            String.Format(
                "Done.  Created {0} subgraph {1}."
                ,
                iSubgraphsCreated.ToString(ExcelTemplateForm.Int32Format),
                StringUtil.MakePlural("image", iSubgraphsCreated)
                ) );
    }