Bind datagrid to datareader - wpf

I want to be able to enter SQL into a textbox and display the results in a WPF Datagrid. I thought to start with an SqlDataReader, and set the datagrid's ItemsSource to the data reader:
using (var cmd = conn.CreateCommand()) {
cmd.CommandText = sql.Text;
sqlResults.ItemsSource = cmd.ExecuteReader();
}
but this fails with the following error: Invalid attempt to call FieldCount when reader is closed, which I understand to mean that by the time WPF gets around to reading the FieldCount property of the row object, the using block has already been exited.
So I tried using LINQ and ToList, to get something that would persist in memory:
sqlResults.ItemsSource = cmd.ExecuteReader().Cast<DbDataRecord>().ToList();
but this only displays the 'FieldCount' for each row, which is apparently the only property which DbDataRecord has.
Some solutions I have considered:
Bind to a DataTable instead of a DataReader? But I don't need editing capabilities.
Select each row into an in-memory data structure? What data structure could I use? I can't use anonymous types, because the names and types of the columns change based on the SQL statement. If I use List<object>, how will the datagrid know to generate columns for each object in the list?
Create a custom type descriptor? It seems like overkill.
But I feel the solution should be very simple and easy. Am I missing something basic here?

Maybe a DataTable is overkill but it does what you need it to do.

Well, yep, from what you've said, I would probably go with a struct containing properties that match the DB columns, and just fill that with the linq, so you can easily bind to it.
Working more with JavaScript, and wondering if you can find an equivalent to the Eval (which is suggested never to be used by the creator, btw :), and if that would help ...

Related

VB.Net ArrayList

I'm trying to wrap my head around array lists in vb.net. I am self teaching via the internet but can't seem to figure it all out. Some points i'm having trouble connecting the dots to:
How do i make the array list universal so it's not stuck in a subroutine and I can allow any sub to access the list.
Allowing the list to be added to or removed from from another control on the form.
Saving this array list so the program will populate the list box with it on startup.
Here is an image of the basic concept for the visual:
https://imgur.com/lBbopD8
Up to question 3, using a List(Of T) was the way to go. It may still be but certainly not completely and maybe not at all. Before the advent of the List(Of T), Microsoft recognised that storing Strings in a collection was the most common requirement so, to provide type-safety in that case, they provided the StringCollection class. You say that you want to persist your list of values between sessions so that probably means using My.Settings and it is actually possible to create a setting of type StringCollection.
I would suggest that you open the Settings page of the project properties and add a setting of type StringCollection. Once added, that list will be automatically loaded at startup and saved at shutdown, with no code required from you. You can access it anywhere in the app via My.Settings and you can call Add and Remove or index it or loop over it in exactly the same way as you would an ArrayList or List(Of String).
There is one small gotcha with a StringCollection in settings though. It will actually be Nothing by default. The trick to avoiding that is to edit its Value on the Settings page to add an item, commit that, then edit it again to remove the item. You'll see that, instead of the Value field being empty, it will then contain a snippet of XML. It's that that creates the StringCollection object in the settings file.
As I said, if you want to persist this list between sessions then I strongly recommend using settings this way. Just note that, in order to edit settings, they have to be User-scoped rather than Application-scoped. What that means is that each separate Windows user will have their own copy of the setting and thus their own value. If you only log into Windows with one account then it's of no consequence. If multiple Windows users use the app then it may be considered beneficial in most cases but may be a problem if you want universal settings that can be edited. If it's a problem, you will need to handle persistence yourself but be aware that a standard Windows user (as opposed to an admin) won't have access to write data everywhere, which is exactly why User-scoped settings work the way they do.
Also, while you must use a StringCollection for persistence in settings, you may or may not want to use the same collection in the rest of your code. You might access the collection directly all the time or you may choose to copy the collection to a List(Of String) at startup and then copy the data back at shutdown. Unless you want to avoid committing items until shutdown, I wouldn't bother with the extra collection.
So an important thing to know is that you can directly populate and edit the listbox without having an additional ArrayList. You would use the example code below as follows:
'addTb is the text box you had in the image; this will run on button press event
ListBox1.Items.Add(addTB.Text)
If you are looking to dump ArrayList data into the list box use something like this:
'creates new arraylist and adds items to it
Dim listStuff As ArrayList = New ArrayList
listStuff.Add("Hi")
listStuff.Add(2)
'makes listStuff the datasource for your list box
ListBox1.DataSource = listStuff
Finally, if you are wanting to loop through ArrayList items use something like this:
'remember to do count - 1 or you will receive error since index will be out of range
For i = 0 To listStuff.Count - 1
If listStuff.Item(i) = "" Then
'do stuff here
End If
Next
Hopefully that helps. Let me know if I need to be more clear since this is my first stack overflow answer :)
You are using a ListBox control to visualize a collection of presumably String values from a TextBox control. The ListBox exposes the visualized collection via the Items property.
How do i make the array list universal so it's not stuck in a subroutine and I can allow any sub to access the list.
Because the ListBox control resides on the Form, you can access the Items property through any access level in your Form's code.
Allowing the list to be added to or removed from from another control on the form.
From the Items property, you can use the Add method to add a single value, the AddRange method to add multiple values via an array or another ListBox collection, the Insert method to insert a value at a given index, the Remove method a specific item, and the RemoveAt method to remove an item at a given index.
So in your case, since you're presumably adding the value from the Text property of the TextBox to the ListBox, it is as simple as:
ListBox1.Items.Add(TextBox1.Text)
Saving this array list so the program will populate the list box with
it on startup.
You have a few options, but generally the idea is to is write each value in the Items property to its respective line at a given file when the application closes and then load each value back by reading each line from the same file. Another option is to use My.Settings, though I think with your level of expertise, it would probably be better to stick with the read/write to a file option so you don't have to worry about some pitfalls associated with this option. Here would be a quick example of reading/writing the items to a file:
'Write the items to the file
Dim items(ListBox1.Items.Count - 1) As String
ListBox1.Items.CopyTo(items, 0)
IO.File.WriteAllLines("file.txt", items)
'Read the items to the file
ListBox1.Items.AddRange(IO.File.ReadAllLines("file.txt"))

Return type string array or datatable?

I have a utility method from which I need to return set of strings which could binded to a list control in the UI. I am in doubt whether to go with array of strings which I think is the lighter than a datatable Or add them into datatable so that I can directly bind the datatable to list control? Which one is bettter in performance?
Thanks in advance.
Speaking of performance I think that use an array of string is faster, but you should choose one of them as needed and this depends on how do you want to manage this set of data, because if your goal is just to bind them to a list control I recommend you use a simple array of strings, else if you need something more specific like if you're dealing with a set of data from a database you should use a DataTable because it allows you to set a PrimaryKey create relations with others DataTables and other things. So for more info on the DataTable look here.

Entity set assigned to datagridview will disable sorting

I use Entity Framework. When I bind an entity set to a standard DataGridView control I lose sort-by-click-on-header functionality. I even tried to bind the entity set to a binding source first but results are the same.
Also if I try to sort a column from code I even get an exception that an interface is not implemented... Are standard EF classes un-sortable (would be a bummer)? Needless to say, sorting works if a DataView is provided as data source.
How could I get around this problem? Thanks.
Late to the party but thought I'd at least post an answer... There are two ways to achieve this with little fuss (actually the same method, just slightly different route).
So we have a basic EF context query, pre-execution.
var query = context.Projects
.Where(x => x.Division == selectedDivision);
Load the query so entities are in the local cache. Then point the DGV's binding source to the Local cache's synced BindingList
query.Load();
projectBindingSource.DataSource = context.Projects.Local.ToBindingList();
OR... There were times when I didn't want/need change tracking or the cache "got in the way" of other operations so I needed a non-tracked collection.
Execute the query without tracking and load it's result into an ObservableCollection (Which is what .Local is). Point the DGV's binding source to the ObservableCollection's synced BindingList
var locs = new ObservableCollection<Location>(query.AsNoTracking().ToList());
locationBindingSource.DataSource = locs.ToBindingList();
All text, numeric, and bool columns will have sort enable for header-click. What won't sort are columns for Navigation Properties: Say a Project has an Owner navigation property, since I have Owner entity's ToString() override display the Owner.FullName property I will see an Owner column with the FullName value but I assume the sorter still sees the column's type as the System.Data.Entity object (instead of the bubble up text that is displayed) so doesn't have a default sorter for it.

How to bind a collection of ExpandoObjects to a Data Grid?

I'm trying to read a table from an Excel file (.xls) and display it in a DataGrid. The table has unknown dimensions and each column has values of one unknown type (string, double or int).
I access the file via COM and put the table in a List<> of ExpandoObjects. When I set DataGrid.ItemSource to the List the Grid remains visually empty.
Explicitly defining columns and their data binding yields the runtime message that the application can't find the specified properties in the ExpandoObjects.
How can I display the table in the GridView? I work with Silverlight 5 RC and was hoping for a simple way to do it. At least simpler than the solutions I saw for Silverlight 2 and 3 so far.
If it's dynamic and it doesn't implement ICustomTypeProvider, in Silverlight 5 it will not bind. It's really unfortunate since we have dynamic databinding in WPF, and in the case that properties can be known (like Expando), even writing a CustomType that would work for any IDynamicMetaObject provider isn't difficult, not to mention they could have just added it for Expando, especially since it's sealed.
So the bottom line is that you need to write your own dynamic type implementing ICustomTypeProvider
I realized Vladimir Bodurov's solution works fine for me. I replaced the ExpandoObjects by Dictionaries and used Bodurov's class to transform the List into something the DataGrid can process.

Entity Framework 4.0 Databinding with sorting not working

I want to do something that I thought would be very simple. I want to bind a generated Entity Framework EntityCollection to a WPF DataGrid. I also want this grid to be sortable.
I have tried all kinds of things to make this happen, including using a CollectionViewSource. However, nothing seems to work. Using a normal CollectionViewSource around the EntityCollection gives me:
'System.Windows.Data.BindingListCollectionView' view does not support sorting.
Ok...strange. I would have thought this would work. Next on the CollectionViewSource, I try setting:
CollectionViewType="ListCollectionView"
Great, sorting now works. But wait, I can't add or remove entities using the grid now, presumably because ListCollectionView doesn't support this with an entity framework context.
So, I guess I need to capture events coming out of the datagrid in order to add or remove entities manually from my context. Now I can't find an event to capture to detect an add...!
Why is this so hard? This should be the standard "demo" case that Microsoft should have designed around.
Any ideas?
BindingListCollectionView is not directly the problem. See 'System.Windows.Data.BindingListCollectionView' view does not support sorting on Microsoft Connect for details why it does not support sorting.
On the other hand ListCollectionView supports sorting obviously using a different technique.
I have also tried the following code and it worked beautifully. I have basically implemented your XAML from the other post in code.
DatabaseContext.ObjectStateManager.ObjectStateManagerChanged += (o, args) => Debug.WriteLine(args.Element.ToString());
var collectionViewSource = new CollectionViewSource();
((ISupportInitialize)collectionViewSource).BeginInit();
collectionViewSource.CollectionViewType = typeof (ListCollectionView);
collectionViewSource.Source = ((IListSource) DatabaseContext.Survey).GetList();
collectionViewSource.SortDescriptions.Add(new SortDescription {PropertyName = "Name"});
((ISupportInitialize)collectionViewSource).EndInit();
var editableCollectionView = (IEditableCollectionView)collectionViewSource.View;
var survey = editableCollectionView.AddNew();
// Before this point ObjectStateManager event has occurred and Debug Output is written to.
editableCollectionView.CommitNew();
DatabaseContext.SaveChanges(); // THIS WORKS TOO!
My DatabaseContext.Survey is an ObjectQuery<Survey>. Are you showing an ObjectQuery or a Linq-to-EF query? The former obviously works for me. The latter is where I see a problem. That is not supposed to work.
It seems like, the View just doesn't get notified, when chages occur. So, I just do
myCollectionViewSource.View.Refresh(); //refresh CollectionViewSource of CollectionViewType="ListCollectionView"
after add/remove of list items.
But then the whole state gets refreshed (e.g. you have to reset preselected sorting again). You need to check, if this fits your needs.

Resources