C1FlexGrid:Filtering Image Column

Filtering is considered to be a basic functionality when we work with any DataGrid control in .Net. This apply for C1Flexgrid for Winforms as well. However, inspite of advanced filtering options in these grids, Image Filtering is a feature which still remains to be implemented. Even .Net with its rich set of libraries does not provide direct image comparison methods. General Image comparison technique involves comparing the Pixels, coordinate by coordinate. Now this may be a good approach but implementing this for a Filtering operation may not be suitable; considering the Performance for thousand of records. In this blog, I am going to provide an alternative solution to customize C1FlexGrid which simulates image filtering. This implementation makes use of alternative field from the DataSource in the background to apply Filtering. Lets start with the implementation.

Include Hidden Column

Before moving ahead just take a look at an important aspect of this implementation. As we are not going to apply filter on the Image column, we add a hidden column bound to another field in the DataSource. For implementation, we would bind this additional column to the field EmployeeID.


C1.Win.C1FlexGrid.Column cf;  
cf = flex.Cols.Add();  
cf.Name = "EmployeeID";  
cf.Visible = false;  
cf.AllowFiltering = C1.Win.C1FlexGrid.AllowFiltering.ByValue;  

Convert Byte Array into Image

Next step is to display the image in the grid cells which we receive from the database in Byte array. Using the given code snippet, we can convert the ByteArray into Image and display it in the cell through OwnerDrawCell event.


private Image GetImageFromByteArray(byte[] picData)  
{  
    if (picData == null)  
    {  
        return null;  
    }  
    // is this is an embedded object?  
    int bmData = (picData[0] == 0x15 && picData[1] == 0x1c) ? 78 : 0;  
    // load the picture  
    Image img = null;  

    try  
    {  
       System.IO.MemoryStream ms = new System.IO.MemoryStream(picData, bmData, picData.Length - bmData);  
       img = Image.FromStream(ms);  
    }  
    catch  
    {  
    }  
     // return what we got  
     return img;  
}  

void flex_OwnerDrawCell(object sender, C1.Win.C1FlexGrid.OwnerDrawCellEventArgs e)  
{  
   if (e.Col == 2 && e.Row>0)  
   {  
      e.Image = GetImageFromByteArray(flex[e.Row, e.Col] as Byte[]);  
      e.Style.ImageAlign = C1.Win.C1FlexGrid.ImageAlignEnum.Stretch;  
      e.Style.Display = C1.Win.C1FlexGrid.DisplayEnum.ImageOnly;  
   }  
}  

Modify FilterDropDown Structure

Once we have the images in the cell, next step is to modify the Filter dropdown. By default filter values contain only single entry for byte array. We are going to modify this to show images in the dropdown. To do so we replace the original ListBox in the dropdown with C1List control. But here lies the actual challenge. FilterDropDown child controls are not directly accessible and we use a hack to modify them. You can read this blog which explains how you can do so. Right now I am directly putting up my code to replace the existing ListBox with C1List control. We bind C1List control with the datasource returning the images shown in the grid data rows.


void flex_MouseClick(object sender, MouseEventArgs e)  
{  
   if (flex.HitTest(e.Location).Type == C1.Win.C1FlexGrid.HitTestTypeEnum.FilterIcon && flex.HitTest(e.Location).Column == 2)  
       ResizeFilterWindow();  
}  

private void ResizeFilterWindow()  
{  
   foreach (Form frm in Application.OpenForms)  
   {  
       if (frm.Name == "FilterEditorForm" && frm.GetType().ToString() == "C1.Win.C1FlexGrid.FilterEditorForm")  
       {  
          filterForm = frm;  

          frm.FormClosing += new FormClosingEventHandler(frm_FormClosing);  
          (frm.Controls[0] as ToolStrip).ItemClicked += new ToolStripItemClickedEventHandler(Form1_ItemClicked);  
          frm.Controls.RemoveAt(1);  

          C1.Win.C1List.C1List c1List1 = new C1.Win.C1List.C1List();  
          frm.Controls.Add(c1List1);  
          c1List1.Caption = "";  
          c1List1.Dock = DockStyle.Fill;  
          c1List1.ShowHeaderCheckBox = true;  
          c1List1.SelectionMode = C1.Win.C1List.SelectionModeEnum.CheckBox;  

          c1List1.DataMode = C1.Win.C1List.DataModeEnum.Normal;  
          string connectionString = @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\Users\\<UserID>\\Documents\\ComponentOne Samples\\Common\\C1Nwind.mdb;User Id=admin;Password=;";  
          OleDbConnection conn = new OleDbConnection(connectionString);  
          OleDbDataAdapter dataAdapter = new OleDbDataAdapter("Select EmployeeID, Photo from Employees", conn);  
          DataSet dataSet = new DataSet();  
          dataAdapter.Fill(dataSet, "Employee");  

          c1List1.DataSource = dataSet.Tables[0];  
          c1List1.Columns[0].ValueItems.Translate = true;  
          c1List1.Columns[0].Caption = "(Select All)";  
          c1List1.Splits[0].DisplayColumns[0].HeadingStyle.HorizontalAlignment = C1.Win.C1List.AlignHorzEnum.Near;  
          c1List1.ItemHeight = 100;  
          for (int i = 0; i < dataSet.Tables[0].Rows.Count; i++)  
          {  
             Image img = GetImageFromByteArray(dataSet.Tables[0].Rows[ i ]["Photo"] as byte[]);  
             C1.Win.C1List.ValueItem vItem = new C1.Win.C1List.ValueItem(dataSet.Tables[0].Rows[ i ]["EmployeeID"].ToString(), img);  
             c1List1.Columns[0].ValueItems.Values.Add(vItem);  
          }  

          if (cf.Filter.IsActive)  
          {  
             foreach (object obj in ((C1.Win.C1FlexGrid.ValueFilter)(cf.Filter)).ShowValues)  
             {  
                if (obj != null)  
                {  
                   int val = (int)obj;  
                   int rowIndex = c1List1.FindStringExact(val.ToString(), 0);  
                   c1List1.SelectedIndices.Add(rowIndex);  
                }  
             }  
          }  

          break;  
      }  
   }  
}  

Apply Filter Based on Selected Images

Above section shows the implementation to display Checkable C1List control. Final step is to apply the filter based on the images selected in the list. We would do this in the Closing event for FilterForm.


string filterActionString;  

void Form1_ItemClicked(object sender, ToolStripItemClickedEventArgs e)  
{  
     if (e.ClickedItem.Name == "_btnApplyFilter")  
        filterActionString = "Apply";  
     else if (e.ClickedItem.Name == "_btnClearFilter")  
        filterActionString = "Clear";  
     else  
        filterActionString = "Cancel";  
}  

void frm_FormClosing(object sender, FormClosingEventArgs e)  
{  
     if (filterActionString == "Apply")  
     {  
         C1.Win.C1List.C1List lst = filterForm.Controls[1] as C1.Win.C1List.C1List;  

         List<object> selList = new List<object>();  
         for (int i = 0; i < lst.SelectedIndices.Count; i++)  
         {  
            int rowIndex = lst.SelectedIndices[ i ];  
            selList.Add(lst.Columns[0].CellValue(rowIndex));  
         }  

         C1.Win.C1FlexGrid.ValueFilter vFilter = new C1.Win.C1FlexGrid.ValueFilter();  
         vFilter.ShowValues = selList.ToArray();  
         cf.Filter = vFilter;  
      }  
      else if (filterActionString == "Clear")  
          cf.Filter = null;  
}  

Click on the image below to see a demo of the implementation. Refer the attached samples for complete implementation. Download C# Sample Download VB Sample

GrapeCity

GrapeCity Developer Tools
comments powered by Disqus