DataGridView with DataSource inheritance - winforms

I'm trying to create an extended version of the WinForms DataGridView (ElementDataGrid) to allow sorting and filtering. Since this will be a widget used by multiple developers, I want to hide the SortableBindingList class internally and have the user pass in just a normal List with the control creating the SortableBindingList.
I created a base class called Element, which other developers can extend, but when I set the DataPropertyName of a column to a property that isn't in Element, nothing shows up in that column. As an example, I have a Comment class that inherits from Element. I want to display the Comment Date and Comment Text in the datagrid. Neither of those columns have any data in them, but the columns using properties inherited from Element display properly.
Is there a straightforward way to have the grid display property values from classes that inherit from the Element base class? Alternately, is there a way I could have the property take a generic List?
UPDATE: Here's the method I'm using to set the data source to my SortableBindingList. As I said, properties from Element are being populated in the grid when I want to show them, but properties from Comment, which inherits from Element are not.
public List<Element> DataElements
{
set
{
bindingDataSource.Clear();
SortableBindingList<Element> boundDataSource = new SortableBindingList<Element>();
bindingDataSource.DataSource = boundDataSource;
foreach (Element e in value)
{
bindingDataSource.Add(e);
}
this.DataSource = bindingDataSource;
}
}

Take a look at Marc Gravell's answer to this SO question. Assuming, as he talks about in his answer, that the data is homogeneous (meaning that you aren't mixing Comments and SomeOtherClass in your List of Element) and has at least a single element in it (so that it is able to infer the actual type of the data in the list), I think it would work for your situation.

Related

Wpf data grid custom column sorting

How do I add a special sorting method to a particular column to allow different types of sorting(such as sorting 120.5.1.50 in between 120.5.1.12 and 120.5.1.110 instead of having 120.5.1.110 be the lowest value.
Also how do I allow click header sorting of a custom type bound it a template column. Is this even possible?
You can implement IComparer and define your own comparing logic.
public class MyComparer : IComparer<Object>
{
public int Compare(Object stringA, Object stringB)
{
// Your logic here
}
}
After you can just use LINQ OrderBy method with your custom comparer.
items = items.OrderBy(x => property, comparer).ToList();
Refer to this link.
Edit
TO override the default sorting behaviour of a WPF Datagrid, refer to the answer in this link.
If you want to maintain the custom sort order after clicking the column header, you can use an attached behaviour. I came up with this solution which seems to work well:
WPF DataGrid CustomSort for each Column
This is an MVVM solution - there are probably simpler ways of doing this if you want to delve into the world of code-behind.

How do I pass an extra variable to a a datatemplate

I have a DataTemplate(well two data templates) that I want to use as views for some
basic form viewmodels(that that contain a value and and boolean indicating whether I want to use the value).
I want to use the datatemplate(s) several times for separate form items. I think the right way to do this is to set it as the ContentControl's ContentTemplate (in that case it will have the same data context right?) but I also want to pass the label string and since the label string is part of the ui and doesn't change it seems wrong to put it in the viewmodel object. How do I give access of the label string to the DataTemplate instance?
Just like its name, a DataTemplate is used to template the Data... For example, if you have a class called MyItem which has a Name and Value and you want this shown in a specific way, you'll set a datatemplate for Item and use it whenever needed.
In your case, you're speaking about having very similar views, with only a minor change between them. This minor change (if I understood your question correctly) is not something that comes from the model or from the viewmodel but something which is entirely view-oriented (a different title for the page, for instance).
If you plan on using a different viewmodel for every view, and each viewmodel has a different purpose - I don't see a problem with adding a Title property to the VM and bind to that too (Remember, MVVM is a set of guidelines, not rules...)
If you still rather have it separated from the viewmodel, then you can use an Attached Property. Create an Attached Property called TemplateTitle, for instance, and have each contentcontrol in each view change it. The label, of course, will bind to that Attached Property.

WPF: How to create a collection that can be bound to

My app has a background thread that periodically retrieves data from an external source, in the form of key/value pairs. I would like to expose this data for binding, presumably by storing them in some kind of static(?) model, as the data will be needed by numerous views throughout my app. There are potentially hundreds of these keys, and may be different for each customer, so I can't simply create an INotifyPropertyChanged model with a property for each value.
The app has multiple views visible at any one time, and each of these will have numerous controls (usually textboxes) that I want to bind to individual items in the above collection. When a value in the collection is updated, any controls bound to only that item should change to reflect the new value. I'm assuming an ObservableCollection wouldn't be suitable here, as a change to a single item will result in all controls updating, regardless of which item they are bound to?
To throw a further complexity into the mix, some values (which are numeric) will need formatting for display, e.g. number of decimal places, or adding a suffix such as "volts". The formatting rules are user-defined so I can't hardcode them into (say) the XAML binding's StringFormat expression. Ideally I should be able to access both the raw value (e.g. for calculations), and the formatted version (for display purposes). I'm sure it must be possible to achieve the latter using some clever WPF feature!
I would appreciate any pointers on how I can solve these requirements.
Edit: it's worth mentioning that I've previously tried implementing the model as some kind of collection. The problem is that it won't be initially populated with all values, and these only get added some time later. When they do eventually get added, a bound control doesn't update - presumably because it wasn't initially able to bind to the missing value.
I would take a different approach, namely a variation of Event Aggregation. I would have a single class that manages the overall collection (probably a singleton class like franssu suggested), but instead of binding directly to the collection in that class you create smaller models that are more specific to the individual views.
When your main model receives a new item, it publishes an event, which is consumed by the smaller models who can inspect the new item and determine whether or not they should add that item to their internal collection (the one the individual views are bound to). If it doesn't "belong" to their view, they can simply ignore the event.
You could use similar event publishing for updates to items and such, although if you're binding to the actual items you probably don't need that.
Just implement the INotifyCollectionChanged Interface and the INotifyPropertyChanged and you ll get a Collection like the ObservableCollection.
But rember if you select a Item from your Collection (as example a ObservableCollection) and you change that item your other controls won t update. So if you have a class Person in your Collection and you change the name of one person the other controls won t get the new name of the person.
Inside the Person object you still have to implement the INotifyPropertyChanged Interface and raise the event when your name changes.
So what I want to tell you is: A Collection with the interface INotifyCollectionChanged will only tell the bound controls: There is a new Item, there has been a item removed or a items index changed BUT not if the item itself changes.
So you ll need a Collection that provides the points above and a Item contained by the collection that raises events if a property of it changes.
ObservableCollection is perfect here. You should find that a standard ItemsControl bound to an ObservableCollection will only update the controls of the items that have changed, not every item in the collection.
This is the reason ObservableCollection exists - the events that it raises specifically identify items that have changed, so that the UI can handle them sensibly.
I've tested this locally with a small WPF app and it works fine. Worth noting, though, that a virtualised items panel would probbaly appear to break this behaviour when it scrolls...
EDIT: rereading your question, you actually say "When a value in the collection is updated..." If your collection contains instances of a class, and you update properties on the class, you don't even need ObservableCollection for this to work - you just need the class to implement INotifyPropertyChanged.

Items control data source's item property update, not reflecfted in the items control's itempanel template instance

I have a custom panel control that is intended to be used as an itemspaneltemplate in a items control.
The itemscontrol will be databound to a data source.
This datasource is a List, and each item in the list is a custom business object.
In the application, the user is able to update each of these business objects in the list, and that fires the notification on property changed as expected.
Now my problem is here:
When the user updates the object's properties in the data source (the itms in the List) that the items control is bound to, my custom panel control is not able to get that notification, so as a result the items control does not get updated with the updated items in its view.
I tried using an ObservableCollection instead of List - the problem is still the same.
I must be missing something fundamental here... please help with any pointers, answers or solution.
Change notification in a collection is a bit tricky. Say you have a collection of Products. you can implement change notification is three different places.
Change notification in the Product class (implementing INotifyPropertyChanged in class Product)
Change notification in the collection itself (i.e. using ObservableCollection)
Change notification in the class that holds the collection, that is, implementing INotifyPropertyChanged on the class that contains the collection. (usually this would be the ViewModel under MVVM)
Those tree ways are not the same, and each is for a different situation.
Let's say that the collection is ObservableCollection<Product> Products {get;set;}
if you want changes in the product to register (i.e., if you're doing something like Products[0].Name = "New Product"; then #1 is the right one.
If you want to do Products.Add(new Product(...)) then #2 is the right one.
If you want to do Products = new ObservableCollection<Product>() then #3 is the correct one. This is especially tricky since i'm not changing the collection, but creating a new one, so the ObservableCollection won't help - I'd need to implement INPC in the containing class.

Filtering WinForm DataGridView

I have a DataGridView control that is bound to a custom typed list (that inherits BindingList). I would like to be able to filter rows based on a simple column value (bool type). Ultimately, the fonctional goal is to be able to mark an item as deleted but just flag it as deleted in the DataSource, not remove it. Juste remove it from the grid, not the DataSource.
Any idea ?
You can use LINQ to filter your data then create a new BindingList and reassign it to the dataGridView.
Assuming you have a flag in the person class called WillBeDeleted:
dataGridView1.DataSource = new SortableBindingList<Person>
(SampleData.Where(p => !p.WillBeDeleted).ToList());
Good luck!
Just to make it clearer, I used this code to create the SortableBindingList http://www.timvw.be/presenting-the-sortablebindinglistt-take-two/ (I translated it to VB.NET)
Then, I have my own collection object that contains properties and a SortableBindingList of my entities.
Private mListeNotes As New SP1ZSortableBindingList(Of SP5004ZNoteEvolutiveEntite)
And that is what I bind my grid to so I it is now sortable. So I need it to remain of that type, not generic list.

Resources