I'm using C#, Windows Forms, .NET 3.5 SP1
I have a DataGridView with a lot of columns that I don't know about until run-time (i.e. I don't know I need a Foo column until run-time). To get data into and out of the cells, I'm thinking about the following architecture.
Am I on the right track, or am I missing something easier?
public interface ICustomColumn
{
object Format (DataGridView dgv, DataGridViewCellFormattingEventArgs e);
void Validate (DataGridView dgv, DataGridViewCellValidatingEventArgs e);
}
public class CustomDataGridView : DataGridView
{
protected override void OnCellFormatting (DataGridViewCellFormattingEventArgs e)
{
ICustomColumn col = Columns [e.ColumnIndex].Tag as ICustomColumn;
if ( col != null )
e.Value = col.Format (this, e);
base.OnCellFormatting (e);
}
protected override void OnCellValidating (DataGridViewCellValidatingEventArgs e)
{
ICustomColumn col = Columns [e.ColumnIndex].Tag as ICustomColumn;
if ( col != null )
col.Validate (this, e);
base.OnCellValidating (e);
}
}
class FooColumn : ICustomColumn
{
public FooColumn (Dictionary <RowData, Foo> fooDictionary)
{ this.FooDictionary = fooDictionary; }
// Foo has a meaningful conversion to the column type (e.g. ToString () for a text column
protected object Format (DGV dgv, DGVCFEA e)
{ return FooDictionary [(RowData) dgv.Rows[e.RowIndex].DataBoundItem]; }
// Foo has a meaningful way to interpret e.FormattedValue
void Validate (DGV dgv, DGVCVEA e)
{ FooDictionary [(RowData) dgv.Rows[e.RowIndex].DataBoundItem].Validate (e.FormattedValue); }
}
void CreateFooColumn (DataGridView dgv)
{
dgv.Columns.Add (new DataGridViewTextBoxColumn () { Tag = new FooColumn (fooDictionary) });
}
Another approach would be to use reflection.
To set up the DataGridView:
private void SetUpDataGridView()
{
// Create the columns based on the data in the album info - get by reflection
var ai = new AlbumInfo();
Type t = ai.GetType();
dataTable.TableName = t.Name;
foreach (PropertyInfo p in t.GetProperties())
{
var columnSpec = new DataColumn();
// If nullable get the underlying type
Type propertyType = p.PropertyType;
if (IsNullableType(propertyType))
{
var nc = new NullableConverter(propertyType);
propertyType = nc.UnderlyingType;
}
columnSpec.DataType = propertyType;
columnSpec.ColumnName = p.Name;
dataTable.Columns.Add(columnSpec);
}
}
The helper method:
private bool IsNullableType(Type theType)
{
return (theType.IsGenericType &&
theType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)));
}
To populate the DataGridView:
private void AddToGrid(AlbumInfo info)
{
// Add album info to table - add by reflection
Type t = info.GetType();
var row = new object[t.GetProperties().Length];
int index = 0;
foreach (PropertyInfo p in t.GetProperties())
{
row[index++] = p.GetValue(info, null);
}
dataTable.Rows.Add(row);
dataGridView.ClearSelection();
}
Related
I have a Database which has a table name CourseOffered that looks like this:
enter image description here
The only column i want to select (request to be returned) is Semester and add it to a dropdown list. Here's my code in the Repository Class.
public List<CoursesOffered> GetSemester()
{
List<CoursesOffered> SemestersName = null;
try
{
string sql = "SELECT DISTINCT Semester FROM CoursesOffered";
DataTable dt = _idac.GetManyRowsCols(sql, null);
SemestersName = DBList.ToList<CoursesOffered>(dt);
}
catch (Exception)
{
throw;
}
return SemestersName;
}
I'm calling GetManyRowsCols() from my DataAccess Class and I have a DBList and Entity Class that looks like this.
class DBList
{
public static List<T> ToList<T>(DataTable dt)
where T : IEntity, new()
{
List<T> TList = new List<T>();
foreach (DataRow dr in dt.Rows)
{
T tp = new T();
tp.SetFields(dr);
TList.Add(tp);
}
return TList;
}
}
public class EntityBase : IEntity
{
public void SetFields(DataRow dr)
{
// use reflection to set the fields from DataRow
Type tp = this.GetType();
foreach (PropertyInfo pi in tp.GetProperties())
{
if (null != pi && pi.CanWrite)
{
string nm = pi.PropertyType.Name.ToUpper();
if (nm.IndexOf("ENTITY") >= 0)
// In LINQ to SQL Classes, last properties are links to other tables
break;
if (pi.PropertyType.Name.ToUpper() != "BINARY")
pi.SetValue(this, dr[pi.Name], null);
}
}
}
}
This is how i'm trying to add it to the Drop down List,
private void CourseManagement_Load_1(object sender, EventArgs e)
{
try
{
List<CoursesOffered> semesterList = _ibusinessSemester.GetSemester();
cbCourseNumberDropDown.DataSource = semesterList;
cbCourseNumberDropDown.Refresh();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
but i keep getting an error that says Column 'CourseNum' Does not belong to the table
Based on your description, you want to convert one column to list.
Since I don't have the code about IEntity, I can not test your code.
However, I make the following code to make it.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
var list = DBList.ToList<string>(table, "Semeter").Distinct().ToList();// the code I have modified
comboBox1.DataSource = list;
}
DataTable table = new DataTable();
private void Form1_Load(object sender, EventArgs e)
{
table.Columns.Add("Course",typeof(string));
table.Columns.Add("Semeter", typeof(string));
table.Columns.Add("MaxEncrollment", typeof(int));
table.Rows.Add("CPSC 410","Fall 2016",35);
table.Rows.Add("CPSC 411", "Fall 2016", 30);
table.Rows.Add("CPSC 440", "Fall 2016", 35);
table.Rows.Add("Math301", "Fall 2016", 35);
}
}
class DBList
{
public static List<T> ToList<T>(DataTable dt,string columnname)
{
List<T> TList = new List<T>();
TList = dt.AsEnumerable().Select(r => r.Field<T>(columnname)).ToList();
return TList;
}
}
I'm building a Windows Forms Application that displays a custom class Record objects and sorts them by how long they've been in my SortableBindingList<Record> record_list. When I start my program, I have some "dummy" records loaded into this list already for the sake of testing.
The SortableBindingList<T> has been taken from here.
public partial class Form1 : Form
{
public SortableBindingList<Record> record_list = new SortableBindingList<Record> { };
public static DataGridViewCellStyle style = new DataGridViewCellStyle();
public Form1()
{
InitializeComponent();
dataGridView.DataSource = record_list;
FillData(); //Temporary function to insert dummy data for demo.
dataGridView.CellFormatting += new System.Windows.Forms.DataGridViewCellFormattingEventHandler(this.cell_formatting);
this.Controls.Add(dataGridView);
this.dataGridView.RowHeadersVisible = false;
this.dataGridView.Sort(this.dataGridView.Columns["UserName"], ListSortDirection.Ascending);
start_timer();
}
Result before "new" data is added (note: this was alphabetized automatically, specifically entered into the list out of alphabetical order):
Result after data is added:
Finally, result after I click the "UserName" header:
So, must I force a sort every time my DataSource is updated? If that's the case, how do I call a sort in such a manner?
Thank you for your assistance in advance!
You need to apply sort when the list changes.
The SortableBindingList<T> needs some changes to keep the the list sorted when some changes made in list. Here is the full code with changes which I made.
Pay attention The OnListChanged method of BindingList will be called automatically after adding and removing items. But if you need to OnListChanged also runs after changing properties of items, you should implement INotifyPropertyChanged for your model class.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
public class SortableBindingList<T> : BindingList<T>
{
private bool isSortedValue;
ListSortDirection sortDirectionValue;
PropertyDescriptor sortPropertyValue;
public SortableBindingList() : base() { }
public SortableBindingList(IList<T> list) : base(list) { }
protected override void ApplySortCore(PropertyDescriptor prop,
ListSortDirection direction)
{
Type interfaceType = prop.PropertyType.GetInterface("IComparable");
if (interfaceType == null && prop.PropertyType.IsValueType)
{
Type underlyingType = Nullable.GetUnderlyingType(prop.PropertyType);
if (underlyingType != null)
{
interfaceType = underlyingType.GetInterface("IComparable");
}
}
if (interfaceType != null)
{
sortPropertyValue = prop;
sortDirectionValue = direction;
IEnumerable<T> query = base.Items;
if (direction == ListSortDirection.Ascending)
query = query.OrderBy(i => prop.GetValue(i));
else
query = query.OrderByDescending(i => prop.GetValue(i));
int newIndex = 0;
foreach (object item in query)
{
this.Items[newIndex] = (T)item;
newIndex++;
}
isSortedValue = true;
sorting = true;
this.OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
sorting = false;
}
else
{
throw new NotSupportedException("Cannot sort by " + prop.Name +
". This" + prop.PropertyType.ToString() +
" does not implement IComparable");
}
}
bool sorting = false;
protected override PropertyDescriptor SortPropertyCore
{
get { return sortPropertyValue; }
}
protected override ListSortDirection SortDirectionCore
{
get { return sortDirectionValue; }
}
protected override bool SupportsSortingCore
{
get { return true; }
}
protected override bool IsSortedCore
{
get { return isSortedValue; }
}
protected override void RemoveSortCore()
{
isSortedValue = false;
sortPropertyValue = null;
}
protected override void OnListChanged(ListChangedEventArgs e)
{
if (!sorting && sortPropertyValue != null)
ApplySortCore(sortPropertyValue, sortDirectionValue);
else
base.OnListChanged(e);
}
}
This is my code
gridView1.Columns.Add(new DevExpress.XtraGrid.Columns.GridColumn()
{
Caption = "Selected",
ColumnEdit = new RepositoryItemCheckEdit() { },
VisibleIndex = 1,
UnboundType = DevExpress.Data.UnboundColumnType.Boolean
});
But I cant check multiple checkEdit at the same time.
Why was that?
And please show me the way out.
Thanks.
Well, there are two answers to that question, one very simple, and one very complex, let's start with the simple:
If you want to have an column that has the "Selected" caption and act as a checkbox to indicate that a particular record was selected, you have two options:
1) If you can alter the class in your data source to add a property that is bool and could be used with DataBinding, then, all is done in a very simple way, jast add the property and bind the data and it will work:
class SimplePerson
{
public string Name { get; set; }
public bool IsSelected { get; set; }
}
BindingList<SimplePerson> source = new BindingList<SimplePerson>();
void InitGrid()
{
source.Add(new SimplePerson() { Name = "John", IsSelected = false });
source.Add(new SimplePerson() { Name = "Gabriel", IsSelected = true });
gridControl.DataSource = source;
}
2) You cannot alter you classes, so you need to this by signing the correct grid events and drawing the column yourself, and also adding the right handlers for all the actions.... is a very complex case, but for your luck i have this done, because i have had this problem in the past, so i will post you my full class!
public class GridCheckMarksSelection
{
public event EventHandler SelectionChanged;
protected GridView _view;
protected ArrayList _selection;
private GridColumn _column;
private RepositoryItemCheckEdit _edit;
public GridView View
{
get { return _view; }
set
{
if (_view == value)
return;
if (_view != null)
Detach();
_view = value;
Attach();
}
}
public GridColumn CheckMarkColumn { get { return _column; } }
public int SelectedCount { get { return _selection.Count; } }
public GridCheckMarksSelection()
{
_selection = new ArrayList();
}
public GridCheckMarksSelection(GridView view)
: this()
{
this.View = view;
}
protected virtual void Attach()
{
if (View == null)
return;
_selection.Clear();
_view = View;
_edit = View.GridControl.RepositoryItems.Add("CheckEdit")
as RepositoryItemCheckEdit;
_edit.EditValueChanged += edit_EditValueChanged;
_column = View.Columns.Insert(0);
_column.OptionsColumn.AllowSort = DefaultBoolean.False;
_column.VisibleIndex = int.MinValue;
_column.FieldName = "CheckMarkSelection";
_column.Caption = "Mark";
_column.OptionsColumn.ShowCaption = false;
_column.UnboundType = UnboundColumnType.Boolean;
_column.ColumnEdit = _edit;
View.CustomDrawColumnHeader += View_CustomDrawColumnHeader;
View.CustomDrawGroupRow += View_CustomDrawGroupRow;
View.CustomUnboundColumnData += view_CustomUnboundColumnData;
View.MouseUp += view_MouseUp;
}
protected virtual void Detach()
{
if (_view == null)
return;
if (_column != null)
_column.Dispose();
if (_edit != null)
{
_view.GridControl.RepositoryItems.Remove(_edit);
_edit.Dispose();
}
_view.CustomDrawColumnHeader -= View_CustomDrawColumnHeader;
_view.CustomDrawGroupRow -= View_CustomDrawGroupRow;
_view.CustomUnboundColumnData -= view_CustomUnboundColumnData;
_view.MouseDown -= view_MouseUp;
_view = null;
}
protected virtual void OnSelectionChanged(EventArgs e)
{
if (SelectionChanged != null)
SelectionChanged(this, e);
}
protected void DrawCheckBox(Graphics g, Rectangle r, bool Checked)
{
var info = _edit.CreateViewInfo() as CheckEditViewInfo;
var painter = _edit.CreatePainter() as CheckEditPainter;
ControlGraphicsInfoArgs args;
info.EditValue = Checked;
info.Bounds = r;
info.CalcViewInfo(g);
args = new ControlGraphicsInfoArgs(info, new GraphicsCache(g), r);
painter.Draw(args);
args.Cache.Dispose();
}
private void view_MouseUp(object sender, MouseEventArgs e)
{
if (e.Clicks == 1 && e.Button == MouseButtons.Left)
{
GridHitInfo info;
var pt = _view.GridControl.PointToClient(Control.MousePosition);
info = _view.CalcHitInfo(pt);
if (info.InRow && _view.IsDataRow(info.RowHandle))
UpdateSelection();
if (info.InColumn && info.Column == _column)
{
if (SelectedCount == _view.DataRowCount)
ClearSelection();
else
SelectAll();
}
if (info.InRow && _view.IsGroupRow(info.RowHandle)
&& info.HitTest != GridHitTest.RowGroupButton)
{
bool selected = IsGroupRowSelected(info.RowHandle);
SelectGroup(info.RowHandle, !selected);
}
}
}
private void View_CustomDrawColumnHeader
(object sender, ColumnHeaderCustomDrawEventArgs e)
{
if (e.Column != _column)
return;
e.Info.InnerElements.Clear();
e.Painter.DrawObject(e.Info);
DrawCheckBox(e.Graphics, e.Bounds, SelectedCount == _view.DataRowCount);
e.Handled = true;
}
private void View_CustomDrawGroupRow
(object sender, RowObjectCustomDrawEventArgs e)
{
var info = e.Info as GridGroupRowInfo;
info.GroupText = " " + info.GroupText.TrimStart();
e.Info.Paint.FillRectangle
(e.Graphics, e.Appearance.GetBackBrush(e.Cache), e.Bounds);
e.Painter.DrawObject(e.Info);
var r = info.ButtonBounds;
r.Offset(r.Width * 2, 0);
DrawCheckBox(e.Graphics, r, IsGroupRowSelected(e.RowHandle));
e.Handled = true;
}
private void view_CustomUnboundColumnData
(object sender, CustomColumnDataEventArgs e)
{
if (e.Column != CheckMarkColumn)
return;
if (e.IsGetData)
e.Value = IsRowSelected(View.GetRowHandle(e.ListSourceRowIndex));
else
SelectRow(View.GetRowHandle(e.ListSourceRowIndex), (bool)e.Value);
}
private void edit_EditValueChanged(object sender, EventArgs e)
{
_view.PostEditor();
}
private void SelectRow(int rowHandle, bool select, bool invalidate)
{
if (IsRowSelected(rowHandle) == select)
return;
object row = _view.GetRow(rowHandle);
if (select)
_selection.Add(row);
else
_selection.Remove(row);
if (invalidate)
Invalidate();
OnSelectionChanged(EventArgs.Empty);
}
public object GetSelectedRow(int index)
{
return _selection[index];
}
public int GetSelectedIndex(object row)
{
return _selection.IndexOf(row);
}
public void ClearSelection()
{
_selection.Clear();
View.ClearSelection();
Invalidate();
OnSelectionChanged(EventArgs.Empty);
}
private void Invalidate()
{
_view.CloseEditor();
_view.BeginUpdate();
_view.EndUpdate();
}
public void SelectAll()
{
_selection.Clear();
var dataSource = _view.DataSource as ICollection;
if (dataSource != null && dataSource.Count == _view.DataRowCount)
_selection.AddRange(dataSource); // fast
else
for (int i = 0; i < _view.DataRowCount; i++) // slow
_selection.Add(_view.GetRow(i));
Invalidate();
OnSelectionChanged(EventArgs.Empty);
}
public void SelectGroup(int rowHandle, bool select)
{
if (IsGroupRowSelected(rowHandle) && select) return;
for (int i = 0; i < _view.GetChildRowCount(rowHandle); i++)
{
int childRowHandle = _view.GetChildRowHandle(rowHandle, i);
if (_view.IsGroupRow(childRowHandle))
SelectGroup(childRowHandle, select);
else
SelectRow(childRowHandle, select, false);
}
Invalidate();
}
public void SelectRow(int rowHandle, bool select)
{
SelectRow(rowHandle, select, true);
}
public bool IsGroupRowSelected(int rowHandle)
{
for (int i = 0; i < _view.GetChildRowCount(rowHandle); i++)
{
int row = _view.GetChildRowHandle(rowHandle, i);
if (_view.IsGroupRow(row))
if (!IsGroupRowSelected(row))
return false;
else
if (!IsRowSelected(row))
return false;
}
return true;
}
public bool IsRowSelected(int rowHandle)
{
if (_view.IsGroupRow(rowHandle))
return IsGroupRowSelected(rowHandle);
object row = _view.GetRow(rowHandle);
return GetSelectedIndex(row) != -1;
}
public void UpdateSelection()
{
_selection.Clear();
Array.ForEach(View.GetSelectedRows(), item => SelectRow(item, true));
}
}
And now you need to know how to use this:
void InitGrid()
{
gridControl.DataSource = source;
// Do this after the database for the grid is set!
selectionHelper = new GridCheckMarksSelection(gridView1);
// Define where you want the column (0 = first)
selectionHelper.CheckMarkColumn.VisibleIndex = 0;
// You can even subscrive to the event that indicates that
// there was change in the selection.
selectionHelper.SelectionChanged += selectionHelper_SelectionChanged;
}
void selectionHelper_SelectionChanged(object sender, EventArgs e)
{
// Do something when the user selects or unselects something
}
But how do you retrieve all the selected items? There is a example assuming that the type bond is 'Person'
/// <summary>
/// Return all selected persons from the Grid
/// </summary>
public IList<Person> GetItems()
{
var ret = new List<Person>();
Array.ForEach
(
gridView1.GetSelectedRows(),
cell => ret.Add(gridView1.GetRow(cell) as Person)
);
return ret;
}
Currently I am using a class as follows to check if yhe TextBoxes on the form that I register to it, all have a non-blank text or not and it work fine, But now I want to also add a ComboBox to this validation so that validation should be done when none of the registered textboxes AND Combobxes on the form are blank.
So if I want to add a Combobx to this class, how should it look like? what is the best pracitce to do it?
public class InputValidator
{
public delegate void ValidationDoneDelegate(bool enable);
public event ValidationDoneDelegate ValidationDone;
public void RegisterTextBox(TextBox tb)
{
tb.TextChanged += (s, e) => this.Validate(s);
}
private void Validate(object sender)
{
var t = sender as TextBox;
if (t == null)
{
return;
}
var validationDone = ValidationDone;
if (validationDone != null)
{
validationDone(!string.IsNullOrEmpty(t.Text));
}
}
}
I have two lists setup which will hold all the TextBox and ComboBox references. When it is time to validate, we will check all of the registered controls and if ANY of them are empty, we will be invalid. I think you will also be able to see how this can easily be extended to support additional control types.
public class InputValidator
{
public delegate void ValidationDoneDelegate(bool enable);
public event ValidationDoneDelegate ValidationDone;
private List<TextBox> textBoxes = new List<TextBox>();
private List<ComboBox> comboBoxes = new List<ComboBox>();
public void RegisterTextBox(TextBox tb)
{
tb.TextChanged += (s, e) => this.Validate();
textBoxes.Add(tb);
}
public void RegisterComboBox(ComboBox cb)
{
cb.SelectedValueChanged += (s, e) => this.Validate();
comboBoxes.Add(cb);
}
private void Validate()
{
bool isValid = true;
foreach (var tb in textBoxes)
{
if (string.IsNullOrEmpty(tb.Text))
isValid = false;
}
if (isValid)
{
foreach (var cb in comboBoxes)
{
if (cb.SelectedItem == null)
isValid = false;
}
}
var validationDone = ValidationDone;
if (validationDone != null)
{
validationDone(isValid);
}
}
}
Now I'm not sure exactly what you consider to be invalid input for the ComboBox. So you may need to tweak this line to meet your needs: isValid = cb.SelectedItem != null;. I have assemed that as long as something is selected that the selection is valid.
EDIT: I had forgotten to switch the last line to validationDone(isValid);
I am trying to develop a custom User control. In my User Control I use two controls a List Box and a Text Box. Text Box is use for filtering items in List Box. To doing this I am facing a problem in my filter method. In my filter method I need to cast object to ItemSource type. But I don`t understand how can I cast it. Here is my Code which I try:
public static readonly DependencyProperty ItemSourceProperty = DependencyProperty.Register("ItemSourrce", typeof(IEnumerable), typeof(SSSearchListBox), new PropertyMetadata(new PropertyChangedCallback(OnItemsSourcePropertyChanged)));
private static void OnItemsSourcePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var control = sender as SSSearchListBox;
if (control != null)
control.OnItemsSourceChanged((IEnumerable)e.OldValue, (IEnumerable)e.NewValue);
}
private void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
// Remove handler for oldValue.CollectionChanged
var oldValueINotifyCollectionChanged = newValue as INotifyCollectionChanged;
if (null != oldValueINotifyCollectionChanged)
{
oldValueINotifyCollectionChanged.CollectionChanged -= new NotifyCollectionChangedEventHandler(newValueINotifyCollectionChanged_CollectionChanged);
}
// Add handler for newValue.CollectionChanged (if possible)
var newValueINotifyCollectionChanged = newValue as INotifyCollectionChanged;
if (null != newValueINotifyCollectionChanged)
{
newValueINotifyCollectionChanged.CollectionChanged += new NotifyCollectionChangedEventHandler(newValueINotifyCollectionChanged_CollectionChanged);
}
}
void newValueINotifyCollectionChanged_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
//Do your stuff here.
}
public IEnumerable ItemSourrce
{
get { return (IEnumerable)this.GetValue(ItemSourceProperty); }
set { this.SetValue(ItemSourceProperty, value); }
}
public void TextFiltering(ICollectionView filteredview, DevExpress.Xpf.Editors.TextEdit textBox)
{
string filterText = "";
filteredview.Filter = delegate(object obj)
{
if (String.IsNullOrEmpty(filterText))
{
return true;
}
string str = obj as string; // Problem is here.
// I need to cast obj to my ItemSourrce Data Type.
if (String.IsNullOrEmpty(obj.ToString()))
{
return true;
}
int index = str.IndexOf(filterText, 0, StringComparison.InvariantCultureIgnoreCase);
return index > -1;
};
textBox.EditValueChanging += delegate
{
filterText = textBox.Text;
filteredview.Refresh();
};
}
private void textEdit1_GotFocus(object sender, RoutedEventArgs e)
{
ICollectionView view = CollectionViewSource.GetDefaultView(ItemSourrce);
TextFiltering(view, textEdit1);
}
calling this Use Control :
List<testClass> myList = new List<testClass>();
public void testMethod()
{
for (int i = 0; i < 20; i++)
{
myList.Add(new testClass { testData=i.ToString()});
}
myTestControl.ItemSourrce = myList;
}
public class testClass
{
public string testData { get; set; }
}
thank`s
well everything what you want to assing to ItemSource (or ItemsSource for naming consistence) has to implement the IEnumerable interface. Thist means basicaly erverything you can iterate through a foreach (to put it simply). So every List, array, collections, Dictionaries and so on.
Normaly you can't iterate throu a normal out of the box string!
So if your Object is realy a String (check it with a breakpoint on debugging) you have to split it somehow.
ps: by the way this code
if (String.IsNullOrEmpty(obj.ToString()))
{
return true;
}
never (except some rare unmeaningfull example where you explicitly return an empty string for ToStirng()) returns true because obj.ToString() is never null or Empty. In the standard implementation of the type Object (which every .net Type is based on) it returns the Namespace and the name of the type.
If I understood well your objects are of type testClass and not of string.
So you code should be where you try to do the casting.
string str = string.Empty;
testClass myObject = obj as testClass;
if (myObject == null)
return true;
else
str = myObject.testData;
if (string.IsNullOrEmpty(str))
return true;
return str.IndexOf(filterText, 0, StringComparison.InvariantCultureIgnoreCase) > -1;