How do I position a datagridview to the searched text input - winforms

Using Windows forms and linq to Sql, I bound a datagridview to Products Table, I added to the form 1 Textbox to input the searched text.
I wonder how to position the datagridview according to the text entered to find a given ProductName.
Here I do not want to filter rows, I only want to reposition datagrid after each character entered, the used code:
private void textBox1_TextChanged(object sender, EventArgs e)
{
var searchValue = textBox1.Text.Trim().ToUpper();
var qry = (from p in dc.Products
where p.ProductName.ToUpper().StartsWith(searchValue)
select p).ToList();
int itemFound = productBindingSource.Find("ProductName", searchValue);
productBindingSource.Position = itemFound;
}
The execution of code give the next error: System.NotSupportedException was unhandled at the ligne:
int itemFound = productBindingSource.Find("ProductName", searchValue);
Any idea please ?

The MSDN documentation for BindingSource has the answer:
The Find method can only be used when the underlying list is an
IBindingList with searching implemented. This method simply refers the
request to the underlying list's IBindingList.Find method. For
example, if the underlying data source is a DataSet, DataTable, or
DataView, this method converts propertyName to a PropertyDescriptor
and calls the IBindingList.Find method. The behavior of Find, such as
the value returned if no matching item is found, depends on the
implementation of the method in the underlying list.
When you call this method on a BindingSource whose underlying data source does not implement IBindingList then you see the exception (thrown by the default implementation of IBindingList.FindCore:
System.NotSupportedException: The specified method is not supported.
You don't show what you bind the binding source to but clearly it doesn't implement this method.
Annoyingly, BindingList<T> the recommended list type to use for your data source does not provide a FindCore implementation.
If you are using BindingList you will need to create your own custom type. Here is the code for an absolutely bare bones implementation of a BindingList that supports find:
public class FindableBindingList<T> : BindingList<T>
{
public FindableBindingList()
: base()
{
}
public FindableBindingList(List<T> list)
: base(list)
{
}
protected override int FindCore(PropertyDescriptor property, object key)
{
for (int i = 0; i < Count; i++)
{
T item = this[i];
if (property.GetValue(item).Equals(key))
{
return i;
}
}
return -1; // Not found
}
}
You can do lots with your own implementations of BindingList such as supporting sorting. I've left my answer as just the minimum to support the find method. Search for SortableBindingList if you want to know more.
To use this class do something like this:
var qry = (from p in dc.Products
where p.ProductName.ToUpper().StartsWith(searchValue)
select p).ToList();
FindableBindingList<YourType> list = new FindableBindingList<YourType>(qry);
dataGridView1.DataSource = list;

Related

LINQ to SQL query to populate ListBox returns wrong results

Hello here's a LINQ to SQL query :
private void Stk_DT_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
DataTable dt = new DataTable();
DataGrid grid = sender as DataGrid;
#region Buttons Picking Libres
using(BdCretsDataContext dc=new BdCretsDataContext())
{
var placement = (from p in dc.PICKING
where p.ART_CODE == ArtCode_TxtBox.Text
select new { p.R_PLACEMENT }).Distinct().ToList();
LB.ItemsSource = placement;
}
#endregion
}
With this query I want to fill a ListBox. But I get this result :
All I want is just: 53.
Thanks for helping me
The point is that select new { p.R_PLACEMENT } creates a collection of objects that have a property called R_PLACEMENT. The ToString() of this object, which is invoked by the ListBox, returns a string representation of this object: { R_PLACEMENT = 53 }. You have to unwrap or collect the values from this property:
LB.ItemsSource = placement.Select(row => row.R_PLACEMENT);
This returns a collection of values only.
This is because you create a new (anonymous) type within select new { p.R_PLACEMENT }.
Your placement variable will thus hold a List<> of this new type. The ListBox however does not know how to display items of this type.
To make the ListBox display something useful you must tell it what it should make out of this anonymous type. ListBox does not figure out it on its own.
The simplest solution would probably be to create placement like this:
var placement = (from p in dc.PICKING
where p.ART_CODE == ArtCode_TxtBox.Text
select p.R_PLACEMENT.ToString()).Distinct().ToList();
(From your example I deduce that R_PLACEMENT is of some numeric type.) The .ToString() suffix makes placement a List<string> which the ListBox will be glad to display correctly.

How to improve performance of Search as you type?

I am trying to implement a Search as you type functionality ( like the one in the search in the default email app ) - I have a listbox with say 50 items - each item bound to a class object which has string fields ... I wish to search and display items which have the text in the search box in one of their string fields - this as the user keys in to the textbox ... tried a couple of approaches -->
1 >> Using a CollectionViewSource
- Bound a CollectionViewSource to All the items from the DB
- Bound the List Box to a CollectionViewSource
- Setting the filter property of the CollectionViewSource - to whose function which searchs for the text in the Search box in the items and set the e.Accepted - on every keyup event
- Filtering works fine but its slow with 50 items :( - guess cos of filter taking each item and checking if to set e.Accepted property to true
.... One DB call on load but seems to be a lot of processing to decide which element to disply in the filer by the CollectionViewSource
2 >> Filter # DB level
- on keyup - sending the text in the search box to the ViewModel where a function returns an ObservableCollection of objects which has the search string
- ObservableCollection is bound to the listbox
.... Not much processing # the top layer but multiple DB calls on each Keypress - still slow but just a tad faster than Approach One
Is there any other approch you would recommend ? or any suggestions to further optimize the above mentioned approaches? - any tweak to give smooth functioning for the search?
First time into mobile dev :) ... Thanx in advance :)
You can optimize the search process further by delaying the search action by x milliseconds to allow the user to continue the writing of the search criteria. This way, each new typed char will delay the search action by xxx milliseconds till the last char where the action is triggered, you'll get a better performance using just one call instead of say 10 calls. Technically speaking, you can achieve this by using a Timer class.
Here you'll find a sample source code example that shows u how to implement this.
private void txtSearchCriteria_TextChanged(object sender, TextChangedEventArgs e)
{
if (txtSearchCriteria.Text == "Search ...")
return;
if (this.deferredAction == null)
{
this.deferredAction = DeferredAction.Create(() => ApplySearchCriteria());
}
// Defer applying search criteria until time has elapsed.
this.deferredAction.Defer(TimeSpan.FromMilliseconds(250));
}
private DeferredAction deferredAction;
private class DeferredAction
{
public static DeferredAction Create(Action action)
{
if (action == null)
{
throw new ArgumentNullException("action");
}
return new DeferredAction(action);
}
private Timer timer;
private DeferredAction(Action action)
{
this.timer = new Timer(new TimerCallback(delegate
{
Deployment.Current.Dispatcher.BeginInvoke(action);
}));
}
public void Defer(TimeSpan delay)
{
// Fire action when time elapses (with no subsequent calls).
this.timer.Change(delay, TimeSpan.FromMilliseconds(-1));
}
}
public void ApplySearchCriteria()
{
ICollectionView dataView = this.ViewSource.View;
string criteria = this.txtSearchCriteria.Text;
string lowerCriteria = criteria.ToLower();
using (dataView.DeferRefresh())
{
Func<object, bool> filter = word =>
{
bool result = word.ToString().ToLower().StartsWith(lowerCriteria);
return result;
};
dataView.Filter = l => { return filter.Invoke(l); };
}
this.lstWords.ItemsSource = dataView;
if (this.lstWords.Items.Count != 0)
{
this.lblError.Visibility = Visibility.Collapsed;
}
else
{
this.lblError.Visibility = Visibility.Visible;
}
}

How To Bind an Array/List's Elements To DataGridViewTextBoxColumn's

In DataGridView, how to bind an array or list (with n elements) to n DataGridViewTextBoxColumn's?
Maybe it's not so clear, for example, I have a class:
public class DynamicNumberFieldsClass
{
public string FullName { get; set; }
public int[] Years { get; set; }
}
The expected form a DataGridView should display:
FullName Year1 Year2 Year3...
Peter 11 12 13
Bryan 21 22 23
If I have to use reflection, it's okay for me.
Any idea?
Thanks!
Peter
P.S.: We can assume the array field will never be null.
we can even assume that the number of elements in the array is fixed once a session is started, but the user can change it in Settings, so in the next session, the number of elements might not be the same.
I am assuming you are working in Windows Forms. I was able to at least display the data in the format you've shown by putting the DataGridView in Virtual Mode. You had best follow the entire walkthrough for a complete solution, but here is what I did in a quick test (hacked up from the samples in the walkthough):
CellValueNeeded handler:
private void dataGridView1_CellValueNeeded(object sender, DataGridViewCellValueEventArgs e)
{
// If this is the row for new records, no values are needed.
if (e.RowIndex == this.dataGridView1.RowCount - 1) return;
DynamicNumberFieldsClass objectInRow = (DynamicNumberFieldsClass)this._people[e.RowIndex];
// Set the cell value to paint using the DynamicNumberFieldsClass object retrieved.
switch (this.dataGridView1.Columns[e.ColumnIndex].Name)
{
case "FullName":
e.Value = objectInRow.FullName;
break;
default:
e.Value = objectInRow.Years[e.ColumnIndex - 1];
break;
}
}
where _people is a collection of DynamicNumberFieldsClass.
Form Load:
private void Form1_Load(object sender, EventArgs e)
{
this.dataGridView1.VirtualMode = true;
this.dataGridView1.CellValueNeeded += new DataGridViewCellValueEventHandler(dataGridView1_CellValueNeeded);
// Add columns to the DataGridView.
DataGridViewTextBoxColumn fullNameColumn = new
DataGridViewTextBoxColumn();
fullNameColumn.HeaderText = "FullName";
fullNameColumn.Name = "FullName";
this.dataGridView1.Columns.Add(fullNameColumn);
for (int i = 0; i < _numYears; i++)
{
DataGridViewTextBoxColumn yearIColumn = new
DataGridViewTextBoxColumn();
yearIColumn.HeaderText = "Year" + (i+1);
yearIColumn.Name = "Year" + (i+1);
this.dataGridView1.Columns.Add(yearIColumn);
}
this.dataGridView1.AutoSizeColumnsMode =
DataGridViewAutoSizeColumnsMode.AllCells;
// Set the row count, including the row for new records.
this.dataGridView1.RowCount = 3; //two objects in _people and the empty row
}
where _numYears is the fixed value you mentioned. If you have at least one instance of the object available, you could use the size of the Years array in that instance as the loop limiter, so that it is completely dynamic.
You would need to expand the switch statement if any other properties are included in the class, and of course add more error checking in general. The walkthrough shows how to support editing in the DataGridView, as well.
(I know you asked how to bind the columns, not force their values, so you probably wanted to be able to decorate the class definition, assign the data source, and go. To that end, I first looked into doing this with Reflection via CustomTypeDescriptor and TypeDescriptionProvider, etc., which is how second-level properties can be bound. Of course, the individual elements of the array are not exposed as properties, so this doesn't work. I can't think of a way to support auto-generation of the columns the way you want, but maybe someone else will find one.)

Convert dataset to observable collection

I was trying to bind a dataset to a listbox..certainly because i want to display a couple of tables information in a datatemplate..But this seems not possible and i will have to convert it to an observable collection.But how can i do it.My bl returns dataset objects.How can i convert this to observablecollection..? Is there any way so that i can handle this situation in MVVM..? How do people handle datasets in MVVM architecture..?
A DataSet is a .Net representation of a set of tables, and the relationahips between them. It is kind of like an in-memory code-accessible representation of a mini-database. Only a few controls can bind directly to a dataset - those that are coded to analyze the relationships between the datasets tables and represent the various tables' data in some kind of hiearchical display, (like a treeview or a hiearchical grid) Anything that requires a simple list of items, with one or two properties for each item can not be bound directly to a dataste, it can only be bound to one of the contained datatables.
Alternatively, you need to dynamically construct and populate a datatable of your own, constructed from the various tables in the dataset you are using, to properly service the specific control you want to bind it to.
Here is how you convert a datatable to an observable collection:
You need to create a class which contains properties. Each property represents a column in
the datatable. Hence you need to set the types of properties as the types of columns in the datatable.
Next you create a property in your View model with which you want to bind any control in xaml. This property would be of the type ObservableCollection. You can bind this property to a grid. In the case of Listbox you can make an ObservableCollection of strings and bind it to a listbox.
You can directly populate you result from DB in Observable collection using LINQ, or alternatively you could manually add items in ObservableCollection from datatable.
There is no built-in function or cast with which you can convert a datatable to an observablecollection
Here is the code from Datatable to ObservabaleColleaction as #Hasan Fahim suggested...
DataTable dtValues = new DataTable();
dtValues.Columns.Add("Value1");
dtValues.Columns.Add("Value2");
dtValues.Columns.Add("Value3");
dtValues.Columns.Add("Value4");
DataRow dr = dtValues.NewRow();
dr["Value1"] = "asad";
dr["Value2"] = "naeeem";
dtValues.Rows.Add(dr);
ObservableCollection Values = new ObservableCollection<MyClass>
(dtValues.AsEnumerable().Select(i => new MyClass
{
Value1 = Convert.ToString(i["Value1"]),
Value2 = Convert.ToString(i["Value2"]),
Value3 = Convert.ToString(i["Value3"]),
Value4 = Convert.ToString(i["Value4"])
}));
I'm still working on it, but how about something like this:
public class cDTObservable<DTType, RowType> : ObservableCollection<RowType>
where DTType : DataTable
where RowType : DataRow
{
private DTType _dt;
public cDTObservable(DTType dt)
: base(dt.Rows.OfType<RowType>())
{
_dt = dt;
}
protected override void ClearItems()
{
_dt.Clear();
base.ClearItems();
}
protected override void InsertItem(int index, RowType item)
{
if (index > _dt.Rows.Count) throw new ArgumentOutOfRangeException("Argument is out of range");
if (index == _dt.Rows.Count)
_dt.Rows.Add(item);
else
_dt.Rows.InsertAt(item, index);
base.InsertItem(index, item);
}
protected override void MoveItem(int oldIndex, int newIndex)
{
if (oldIndex >= _dt.Rows.Count || newIndex >= _dt.Rows.Count)
throw new ArgumentOutOfRangeException("Argument is out of range");
int MyNewIndex = newIndex; //so that I don't override anything that goes to base.MoveItem
if (oldIndex < newIndex)
MyNewIndex--;
RowType dr = (RowType)_dt.Rows[oldIndex];
_dt.Rows.RemoveAt(oldIndex);
if (MyNewIndex == _dt.Rows.Count)
_dt.Rows.Add(dr);
else
_dt.Rows.InsertAt(dr, MyNewIndex);
dr = null;
base.MoveItem(oldIndex, newIndex);
}
protected override void RemoveItem(int index)
{
if (index >= _dt.Rows.Count) throw new ArgumentOutOfRangeException("Argument is out of range");
_dt.Rows[index].Delete(); //Or if you do not need the data to persist in your data store, simply _dt.Rows.RemoveAt(index);
base.RemoveItem(index);
}
protected override void SetItem(int index, RowType item)
{
if (index >= _dt.Rows.Count) throw new ArgumentOutOfRangeException("Argument is out of range");
_dt.Rows.RemoveAt(index);
if (index > _dt.Rows.Count - 1)
_dt.Rows.Add(item);
else
_dt.Rows.InsertAt(item, index);
base.SetItem(index, item);
}
}
The idea is that you inherit from ObservableCollection and rather than manipulating copies of the data you simply manipulate the references to your data. This way, when your Row is updated in the datatable it will be updated in your ObservableCollection as well (though no ObservableCollection events will be fired).
Because of the generics, this also should work for Typed DataTables and provide you access to the DataRow's properties (including DataColumns).
This should also, in theory, allow you to use the ObservableCollection as a proxy to your DataTable for Add/Delete/Modify stuff.
Obviously, since I am still working on this for my project, it is entirely possible that I may have missed something big, but as of this moment, I don't see why this wouldn't work (as I'm pretty sure that Rows[Index].Delete() set the DataRowState property instead of actually Deleting the DataRow object).

NHibernate, WinForms, and DataBinding - do they play well together?

I've been using WinForms databinding to display data from a database mapped with Fluent NHibernate, and that's been working great.
For example, I can just set a DataGridView's DataSource property from an entity's IList property, and voila - there's all the data!
But now I need to start adding and saving new data rows, and that's not going so well. I thought I'd be able to just enable the grid's AllowUserToAddRows property, and new rows would get added to the underlying IList in the entity, but that didn't work.
Then, after a little searching, I tried setting the DataSource property to a BindingList that was populated from the IList, but that's not being updated with new rows either.
During the course of my searches, I also came upon a few people reporting difficulty with WinForms and DataBinding in general, which makes me wonder if I should pursue that approach any further.
Is the DataBinding approach worth continuing? If so, can anyone suggest where I'm going wrong?
Or is it better to just handle all the DataGridView events associated with adding a new row, and writing my own code to add new objects to the IList property in my entity?
Other suggestions? (though I don't think switching to WPF is going to be an option, no matter how much better the databinding may be)
Can you load (or copy) your nHibernate entities into a generic List? If so, I have had good success in with two-way binding using a DataGridView bound to a generic List.
The key points are:
The generic list contains list objects where each is an instance of your custom class.
Your custom class must implement public properties for each of the fields to bind. Public fields didn't work for me.
Use a BindingSource to wrap the actual generic list.
The BindingSOurce allows you to set the AllowNew property to true. Binding directly to the List almost works, but the DataGridVieww does not display the "New row" line, even if AllowUsersToAddRows = true.
For example, add this code to a Form with a dataGridView1:
private List<MyObject> m_data = new List<MyObject>();
private BindingSource m_bs =new BindingSource();
private void Form1_Load(object sender, EventArgs e)
{
m_data.Add(new MyObject(0,"One",DateTime.Now));
m_data.Add(new MyObject(1, "Two", DateTime.Now));
m_data.Add(new MyObject(2, "Three", DateTime.Now));
m_bs.DataSource = m_data;
m_bs.AllowNew = true;
dataGridView1.DataSource = m_bs;
dataGridView1.AutoGenerateColumns = true;
dataGridView1.AllowUserToAddRows = true;
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
for (int i = 0; i < m_data.Count ; i++)
{
Console.WriteLine(string.Format("{0} {1} {2}", m_data[i].ID, m_data[i].Name, m_data[i].DOB));
}
}
}
public class MyObject
{
// Default ctor, required for adding new rows in DataGridView
public MyObject()
{
}
public MyObject(int id, string name, DateTime dob)
{
ID = id;
Name = name;
DOB = dob;
}
private int m_id;
public int ID
{
get
{
return m_id;
}
set
{
m_id = value;
}
}
private string m_name;
public string Name
{
get
{
return m_name;
}
set
{
m_name = value;
}
}
private DateTime m_dob;
public DateTime DOB
{
get
{
return m_dob;
}
set
{
m_dob = value;
}
}
}
When the form closes, the contents of the bound List are printed to the Output window.

Resources