Enhancing Windows Presentation Foundation (WPF) XamDataGrid from Infragistics

Introduction

I had to extend the Infragistics Controls with added functionality and ease of use. And one of the primary goals I had to achieve was to abstract out the "Infragistics" touch from the controls. So as to look like an independent control. One of the most widely used controls of Infragistics, the "XamDataGrid" was missing some important features according to me. Remember, you are offered state-of-art control library, but with missing common stuff.

One of the most widely used controls of Infragistics , the "XamDataGrid" was missing some important features according to me. Remember, you are offered state-of-art control library, but with missing common stuff.

So this article will talk about extending the "XamDataGrid" control. This article would show only part of the features that I have added. In subsequent updates to this article, I will put the rest of the stuff in place.

The Infragistics library that this article uses is WPF 2008 Vol 2.

  • Infragistics3.Wpf.v8.2
  • Infragistics3.Wpf.Ribbon.v8.2
  • Infragistics3.Wpf.Editors.v8.2
  • Infragistics3.Wpf.Reporting.v8.2
  • Infragistics3.Wpf.DataPresenter.v8.2.dll

And the Infragistics2.Excel.v8.1 for exporting XamDataGrid to excel.

Let us first create a user control comprising the native DataPresenter's XamDataGrid control.

The settings that are normally reused across all instances of XamDataGrid control in a project, are the:

  • FieldSettings Collection
  • FieldLayoutSettings Collection
  • Context Menus
  • Styles on the Grid

Let's look at such a markup.

<igDP:XamDataGrid x:Class="ReusableControls.XamDataGrid"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:igDP="http://infragistics.com/DataPresenter"
    xmlns:igRibbon="http://infragistics.com/Ribbon"
    xmlns:igWindows="http://infragistics.com/Windows"
    xmlns:igRep="http://infragistics.com/Reporting"
    xmlns:igEditors="http://infragistics.com/Editors"	
    x:Name="rxdg"  
    ContextMenuOpening="xdg_ContextMenuOpening"
>
    <igDP:XamDataGrid.FieldSettings >
       <igDP:FieldSettings  
          AllowSummaries="true" 
          SummaryUIType="MultiSelect"
          CellClickAction="EnterEditModeIfAllowed" 
          LabelTextAlignment="Center"
          LabelTextTrimming="WordEllipsis" 
          LabelTextWrapping="Wrap">
</igDP:FieldSettings>
   </igDP:XamDataGrid.FieldSettings>

   <igDP:XamDataGrid.FieldLayoutSettings>
        <igDP:FieldLayoutSettings 
                 AllowFieldMoving="WithinLogicalRow"
                 HighlightAlternateRecords="True" 
                 AllowAddNew="False" 
                 AllowDelete="True"
                 SelectionTypeCell="Default" 
                 MaxSelectedRecords="1" 
                 SelectionTypeRecord="Single"/>
        </igDP:FieldLayoutSettings>
    </igDP:XamDataGrid.FieldLayoutSettings>
    
    <igDP:XamDataGrid.Resources>
        <ContextMenu x:Key="ugContextMenu" MenuItem.Click="ugContextMenu_Click"  ButtonBase.Click="ugContextMenu_Click">
            <igRibbon:ButtonTool x:Name="ButtonManageColumns" 
                                Loaded="ButtonManageColumns_Loaded"
                                >manage columns
            </igRibbon:ButtonTool>
            <igRibbon:ButtonTool x:Name="ButtonClearGridPreferences" 
                                Loaded="ButtonClearGridPreferences_Loaded"
                                >clear grid preferences
            </igRibbon:ButtonTool>
            <igRibbon:ButtonTool x:Name="ButtonExcelExporter" 
                                Loaded="ButtonExcelExporter_Loaded"
                                >xport 2 xcel
                </igRibbon:ButtonTool>
                <igRibbon:ButtonTool x:Name="ButtonCSVExporter" 
                                Loaded="ButtonCSVExporter_Loaded"
                                >xport to csv
                </igRibbon:ButtonTool>
<igRibbon:ButtonTool x:Name="ButtonXPSExporter" 
                                VerticalAlignment="Center"
                                Loaded="ButtonXPSExporter_Loaded"
                                Click="ButtonXPSExporter_Click"
                                >Export to XPS
                </igRibbon:ButtonTool>
                <igRibbon:ButtonTool x:Name="ButtonPrintPreview" 
                                VerticalAlignment="Center"
                                Loaded="ButtonPrintPreview_Loaded"
                                Click="ButtonPrintPreview_Click"
                                >Print Preview
                </igRibbon:ButtonTool>
                <igRibbon:ButtonTool x:Name="ButtonPrint" 
                                VerticalAlignment="Center"
                                Loaded="ButtonPrint_Loaded"
                                Click="ButtonPrint_Click"
                                >Print
                </igRibbon:ButtonTool>
               
        </ContextMenu>
        <Style TargetType="{x:Type igDP:HeaderLabelArea}">
            <Setter Property="ContextMenu" Value="{StaticResource ugContextMenu}" />
        </Style>
    </igDP:XamDataGrid.Resources>
</igDP:XamDataGrid>

The key properties are the

FieldSettings:
   LabelTextAlignment, LabelTextTrimming, LabelTextWrapping
FieldLayoutSettings:
   AllowFieldMoving, HighlightAlternateRecords,AllowAddNew,
   AllowDelete, MaxSelectedRecords

And adding Context menu to the grid. The context menu is loaded with buttons like Export to CSV, Export to Excel, and Export to XPS, Print, Print Preview and clear Grid Preferences.

Note: This code blocks for the Context menu functionalities are taken from Infragistics samples.



Enhancing Windows Presentation Foundation (WPF) XamDataGrid from Infragistics

The code for the buttons in the context menu is very rudimentary.

/// <summary>
/// Handles the Click event of the ButtonPrint control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
private void ButtonPrint_Click(object sender, RoutedEventArgs e)
{
       XamDataGridPrinter.PrintXamDataGrid(xdg);
}
 
/// <summary>
/// Handles the Click event of the ButtonPrintPreview control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
private void ButtonPrintPreview_Click(object sender, RoutedEventArgs e)
{
        XamDataGridPrinter.PrintPreviewXamDataGrid(xdg);
} 

The XamDataGridPrinter is a custom class that has two functions. It takes in the XamDataGrid control as input and uses the Report object to exhibit the

Print

and the PrintPreview functions. The code below is from one of the Infragistics Samples. It is just added to a helper class.

public sealed class XamDataGridPrinter
    {
        /// <summary>
        /// Prints the xam data grid.
        /// </summary>
        /// <param name="xamDataGrid">The xam data grid.</param>
        public static void PrintXamDataGrid(XamDataGrid xdg)
        {
            //Create Report object
            Report reportObj = new Report();
            reportObj.ReportSettings.Margin = new Thickness(50, 20, 50, 20);

            //Create EmbeddedVisualReportSection sections for chart and grid. 
//Put the grid you want to print as a parameter of section's //constructor
EmbeddedVisualReportSection sectionGrid = new EmbeddedVisualReportSection(xdg);

            //Add created sections to report's section collection
            reportObj.Sections.Add(sectionGrid);

            // Call print method
            reportObj.Print(true, false);
        }


        /// <summary>
        /// Prints the preview xam data grid.
        /// </summary>
        /// <param name="xamDataGrid">The xam data grid.</param>
        public static void PrintPreviewXamDataGrid(XamDataGrid xdg)
        {
            // Create report and add visual to print
            Report reportObj = new Report();

            // Create section with chart
EmbeddedVisualReportSection section = new EmbeddedVisualReportSection(xdg);
            reportObj.Sections.Add(section);

            // Put report to preview control
XamReportPreview myXamReportPreview = new XamReportPreview();
myXamReportPreview.GeneratePreview(reportObj, false, true);
            myXamReportPreview.ShowDialog();
        }
    }

We would use a routing technique (Tunnelling) to route the Context Menu button click event to the ContextMenu control.

<ContextMenu x:Key="ugContextMenu" MenuItem.Click="ugContextMenu_Click"  ButtonBase.Click="ugContextMenu_Click"> 

In the click event we would capture the sender type and decide on the actions. The code block below shows the calls made to appropriate functions on each button click in the context menu.

/// <summary>
/// Handles the Click event of the exContextMenu control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
private void exContextMenu_Click(object sender, RoutedEventArgs e)
{
            System.Type senderType          = e.OriginalSource.GetType();
            System.Object originalSender    = e.OriginalSource;

            
            //Sender is Button Tool.
            if (senderType == typeof(ButtonTool))
            {
if ((originalSender as ButtonTool).ToString() == "Export to E_xcel"))
            	{
                    ExportToExcel();
                 	}
                //CSV
    else if ((originalSender as ButtonTool).ToString() == "Export      to _CSV")	
                {
                    ExportToCSV();
                }
            }
} 

Just to complete the missing part in the code blocks, the ExportToExcel functions sample is below:

/// <summary>
/// Exports to excel.
/// </summary>
public void ExportToExcel()
{
       //EXCEL
string excelDataFile = MyOpenDialog.GetFileNameForSave(FileExtenion.XLS, DefaultFileName);

      if (string.IsNullOrEmpty(excelDataFile))
      return;

XamDataGridExcelExporter excelExporter = new XamDataGridExcelExporter();

      if (excelExporter.Export(xdg, excelDataFile, SheetName))
 MessageBox.Show(string.Format("Exported to {0} Successfully", excelDataFile),     "Export", MessageBoxButton.OK, MessageBoxImage.Information);
}

The Export to CSV is a custom function written to take the datasource from the XamDataGrid and emit a CSV (Comma Separated Values) file.

/// <summary>
/// Exports to CSV.
/// </summary>
public void ExportToCSV()
{
string csvDataFile = ExtendedDialogs.GetFileNameForSave(FileExtenion.CSV, DefaultFileName);

            if (string.IsNullOrEmpty(csvDataFile))
                return;

            DataSet exportDataSet = new DataSet();

            if (xdg.DataSource is DataSet)
            {
                exportDataSet = (xdg.DataSource as DataSet);
            }

            if (xdg.DataSource is DataView)
            {
exportDataSet.Tables.Add((xdg.DataSource as DataView).Table.Copy());
            }

//To remove the columns that are hidden and excluded to be exported.
            DataSet exportDatasetCopy = exportDataSet.Copy();
            List<string> removeColumns = new List<string>();
            foreach (DataColumn dc in exportDatasetCopy.Tables[0].Columns)
            {
                //To set column caption
                if (this.FieldLayouts[0].Fields.IndexOf(dc.ColumnName) != -1)
                {
dc.Caption = this.FieldLayouts[0].Fields[dc.ColumnName].Label.ToString();
                }
            }

foreach (Infragistics.Windows.DataPresenter.Field field in this.FieldLayouts[0].Fields)
            {
if (field.Visibility != Visibility.Visible && !removeColumns.Contains(field.Name))
                {
                    removeColumns.Add(field.Name);
                }
            }

for (int columnIndex = 0; columnIndex < removeColumns.Count; columnIndex++)
            {
                exportDatasetCopy.Tables[0].Columns.Remove(removeColumns[columnIndex]);
            }
               
            if (CSVExporter.SerializeToCsv(exportDatasetCopy, csvDataFile))
MessageBox.Show(string.Format("Exported to {0} Successfully", csvDataFile),
            "Export", MessageBoxButton.OK, MessageBoxImage.Information);
        }

Note: We would ideally export what we see on screen. That gives us an additional task of hiding the columns that we don't see on the screen. Because the dataSource of the XamDataGrid gives us the the complete datasource, we would have to remove them ourselves.

And that is accomplished using the following piece of code.

foreach (Infragistics.Windows.DataPresenter.Field field in this.FieldLayouts[0].Fields)
{
if (field.Visibility != Visibility.Visible && !removeColumns.Contains(field.Name))
      {
            removeColumns.Add(field.Name);
      }
}

The ExportToCSV function takes in a dataset and saves it to a specified filename. The code is pretty straight-forward and is illustrated below:

/// <summary>
/// Serializes to CSV.
/// </summary>
/// <param name="ds">The ds.</param>
/// <param name="filename">The filename.</param>
/// <returns></returns>
public static bool SerializeToCsv(DataSet ds, string fileName)
{
try
      {
using (StreamWriter sw = new StreamWriter(fileName, false,     Encoding.Default))
            {
                    //write column headers
                    foreach (DataColumn dc in ds.Tables[0].Columns)
                        sw.Write(dc.Caption.ToString() + ",");

                    sw.Write(sw.NewLine);

                    foreach (DataTable dt in ds.Tables)
                    {
                    
                    foreach (DataRow dr in dt.Rows)
                    {
//To skip the deleted rows from grid (This will happen only when the user export the data before saving)
                        
if (dr.RowState == DataRowState.Deleted) continue;
                        for (int i = 0; i < dt.Columns.Count; i++)
                        {
                            if (!Convert.IsDBNull(dr[i]))
                                sw.Write(string.Format("\"{0}\",", dr[i]));
                            else
                                sw.Write("\"\",");
                        }
                        sw.Write(sw.NewLine);
                    }
                    }

                    sw.Flush();
                    sw.Close();
                }
                
                return true;
            }
            catch (System.IO.IOException ioEx)
            {
string errorMessage = string.Format("The process cannot access the file '{0}' because it is being used by another process", fileName);

                	if (ioEx.Message.IndexOf(errorMessage) != -1)
                	{
                    MessageBox.Show(string.Format(
"Error on exporting {0}"), fileName, Environment.NewLine), "Export", MessageBoxButton.OK, MessageBoxImage.Error);
                	}
               	return false;
            }
        }

Screenshots:

The buttons are visible in the Context Menu of the Grid.

[figure1.jpg]
Figure 1

You can also have a textbox in the Grid Header where the user can type a search text. And on the Value changed Event of the textbox, we can highlight the corresponding match in the Grid.

/// <summary>
/// Handles the ValueChanged event of the TextUserName control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.Windows.RoutedPropertyChangedEventArgs&lt;System.Object&gt;"/> instance containing the event data.</param>
private void TextSearch_ValueChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
            //Search
            
SearchXamDataGrid(xdg, TextSearch.Text, "FirstName")
}

/// <summary>
/// Searches the xam data grid.
/// </summary>
/// <param name="searchText">The search text.</param>
private void SearchXamDataGrid(XamDataGrid xamdataGrid, string searchText, string columnName)
{
//Ignore the constant string..
      if (string.IsNullOrEmpty(searchText))
      	return;

      //Single case for comparison.
      searchText = searchText.ToUpper();

      //Clear all current selections.
      xamdataGrid.ClearAllSelected();

      //Take the datasource - dataview.
      DataView dvGrid = (xamdataGrid.DataSource as DataView);
      if (dvGrid ==  null || dvGrid.Count == 0)
      	return;

      //take a datatable copy.
      DataTable dtGrid = dvGrid.ToTable().Copy();
            
      //prepare a row filter for starts with
string rowFilter = string.Concat(columnName, @" LIKE '" , searchText , @"%'");

      //loop thru the grid.
      foreach (DataRecord dataRecord in xamdataGrid.Records)
      {
string cellValue = dataRecord.Cells[columnName].Value.ToString().ToUpper();

            if (cellValue.StartsWith(searchText))
            {
                    //Exact match. Activate.
                    if (cellValue == searchText)
                    {
                        dataRecord.DataPresenter.BringRecordIntoView(dataRecord);
                        dataRecord.ActivateRecord();
                        break;
                    }
                    //Starts With Match. Select
                    else
                    {
                        //apply the rowfilter
                        dtGrid.DefaultView.RowFilter = rowFilter;

                        //if only one record matches, then activate it.
                        if (dtGrid.DefaultView.Count == 1) 
                        {
                            dataRecord.DataPresenter.BringRecordIntoView(dataRecord);
                            dataRecord.ActivateRecord();
                            break;
                        }
                        else
                        {
                            //select the record.
dataRecord.DataPresenter.BringRecordIntoView(dataRecord);
                            dataRecord.IsSelected = true;
                            break;
                        }
                    }
                }
            }
        }

Conclusion

There are few more additional things that we can do over this basic XamDataGrid control. In future articles, I will show you some more functionalities and the usage of ExtensionMethods to make other developer's lives easier.

References

MSDN
Infragistics Help
Andrew Flick's blog

Related Articles





About the Author

Srinath M S

I would love to leave a footprint in this flat world

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • As everyone scrambles to protect customers and consumers from the Heartbleed virus, there will be a variety of mitigating solutions offered up to address this pesky bug. There are a variety of points within the data path where solutions could be put into place to mitigate this (and similar) vulnerabilities and customers must choose the most strategic point in the network at which to deploy their selected mitigation. Read this white paper to learn the ins and outs of mitigating the risk of Heartbleed and the …

  • Packaged application development teams frequently operate with limited testing environments due to time and labor constraints. By virtualizing the entire application stack, packaged application development teams can deliver business results faster, at higher quality, and with lower risk.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds