UltraWinGrid nested object properties binding with BindingSource - winforms

I am working on a winforms application where I am rendering domain/object data via an ultrawingrid. I am using a bindingsource to bind the object to the grid.For simple objects this works quite well.
The thing I'm trying to get my head around is rendering an object with nested objects for e.g a Person class will have a property of Address class. I would like to display the properties of Address (Street, City, Country) as columns on the grid along with the properties of the Person class.
The grid has to be editable and any user changes need to reflect back on the domain object (which I'm doing via the binding source).
What's the best way to go about this?

Binding
I typically use some sort of code like this:
Dim persons = new BindingList(Of Person)
UltraGrid1.DataSource = persons
The binding list will handle the add/remove of rows for you, but it doesn't know about the properties inside Person. To get that part of the binding to work, you'll need to have Person implement INotifyPropertyChanged. This will notify the ultragrid when the properties have changed. The code will look something like this (yes, unfortunately this makes it so you can't use auto-implemented properties):
Private _phoneNumber As String
Public Property PhoneNumber As String
Get
Return Me._phoneNumber
End Get
Set(ByVal value As String)
If value <> _phoneNumber Then
Me._phoneNumber = value
NotifyPropertyChanged("PhoneNumber")
End If
End Set
End Property
Flattening object hierarchies
It looks like what you're ask for isn't directly possible. There are a few options:
Unbound columns in the UI that you fill in during the InitializeRow event
Modify your Person class to expose the properties of Address with some pass through code to handle the setting of the properties.
(I can provide a code samples if needed)
One-to-many Nested Objects
If you, for example, had multiple addresses per person, you could show them nested in an expandable section under each Person row. To do this, inside your Person you'd have a BindingList(Of Address) which also implements INotifyPropertyChanged. Not exactly what you want, but an option :)
Words of caution
A few notes if you are doing MVP. You'll need to have the same reference to the BindingList in your view and presenter, obviously. Also, if you need to reset the contents, I'd recommend calling list.Clear() instead of creating a new one. If you create a new one in your presenter, you'll have broken the connection with the UltraGrid and you'll have to re-set the DataSource property in the view.

Related

Advantage of Binding?

I am not sure that I fully understand the advantage of binding. For example, if I want to bind a string value to a TextBlock I need to do the following:
Create a class that extends INotifyPropertyChanged
Add a string to that class (say: MyString)
Extend the set method for MyString so that it calls another method (say: OnPropertyChanged)
Create the OnPropertyChanged method to call the PropertyChangedEventHandler event
Then I need to create a new instance of the class, set my TextBlock.DataContext to point to that class, and finally add the XAML bit for the binding.
Can someone explain the advantage of this over simply setting:
TextBlock.Text = MyString;
Thanks!
Any changes to MyString won't be automatically reflected in your UI.
Your code behind will be littered with "when this event occurs, update these pieces of data", so you'll essentially be writing your own messy data binding logic for each and every view.
The advantage is that you can both change and display the value in multiple places, without having to update some method to add another TextBlock assignment each time the value changes. Any new display control just binds itself to the property, the rest is automatic.
Now if you really just set the value in one place and show it in one control, then you're right, there's not much point.
The gain of using Data Binding isn't particularly noticeable for a TextBlock binding to a static string.
However if the value of MyString changes during application runtime it becomes much more useful - especially in a case where the object that owns that property is unaware of the TextBlock. This separation between UI and the underlying data layer can be created using a design pattern such as MVVM.
Data Binding is also useful for more complex properties such as Items in a ListBox control. Just bind the ListBox.Items to a property that is of type ObservableCollection and the UI will automatically update whenever the content of that collection changes.

How can I determine if my BindingSource's List has changed?

I have a BindingSource which I use to bind to a grid. The binding source itself binds to a custom class. For instance
MyGrid.DataSource = MyBindingSource
'Bind the Binding source to data
For each classInstance as myClass in MyCollection
MyBindingSource.List.Add(classInstance)
Next
A user may add or delete items to/from this List. My aim is to save this updated List to the database. I need to determine if my binding source's list has changed (i.e. has items added to it, or has items removed from it).
I am aware that I could implement the INotifyPropertyChanged on my custom class, and exploit the OnPropertyChanged event, but my classes' property would never change in my case. The other solution that I can think of is to use the BindingSource's ListChanged event, and maintain a collection of all the added and the deleted rows there. Although this approach may work for me, I reckon its a bit flaky.
Does the binding source or a Collection (such as the IList in my case) provide any other properties that can help me determine the above?

How to store additional data in a listbox?

I have a wpf application using Caliburn.Micro. I need to bind a ListBox to a collection of objects, but I want to display one of the object's fields, and also somehow to attach a Guid (another field) to each item. Could you please tell me how I can do that? I don't know if Caliburn.Micro has something specific for it, or I just have to use WPF.
Thanks.
(sorry for my bad english)
If the Guid field is part of your object, you do not need to store it on another place. The listbox will show a field but it is still bounded to the original object, you can get it with ((MyObjectType)MyListBox.SelectedItem).Guid. With Caliburn it is even easier since you just need to bind a property on your VM to SelectedItem.
But if the Guid is not part of your object, you can use the Tag property, as Paul Sasik said. I do not like to use the Tag property so this is another easy (and more flexbible) way you can solve this, you need to encapsulate your object on another object:
public class GuidObject<T>
{
public T Instance {get;set;}
public Guid Guid {get;set;}
}
You can use it like this:
//this is your original guidless items list
var myObjectsList = new[] { new MyObject { Name = "Dostoyevsky" },
new MyObject { Name = "Ozzy" } };
var myObjectsWithGuidList = new ObservableCollection<GuidObject<MyObject>>();
//encapsulate each MyObject on a GuidObject and include a Guid
//if your myObjectsList is already a List, you do not need to call ToList()
myObjectsList.ToList().ForEach(o => myObjectsWithGuidList.Add(new GuidObject<MyObject>() { Instance = o, Guid = Guid.NewGuid() }));
//now myObjectsWithGuidList contains a list of your itens and a Guid field, you can bind it to your ListBox
Here you can see this running.
You can use the Tag property of each ListBox object to store arbitrary information.
From the link:
This property is analogous to Tag properties in other Microsoft
programming models, such as Microsoft Visual Basic for Applications
(VBA) or Windows Forms. Tag is intended to provide a pre-existing
property location where you can store some basic custom information
about any FrameworkElement without requiring you to subclass an
element.
Because this property takes an object, you would need to use the
property element usage in order to set the Tag property in Extensible
Application Markup Language (XAML) to anything other than an object
with a known and built-in type converter, such as a string. Objects
used in this manner are typically not within the standard Windows
Presentation Foundation (WPF) namespaces and therefore may require
namespace mapping to the external namespace in order to be introduced
as XAML elements.

How do I structure MVVM with Collections?

I'm having trouble understanding how to apply the MVVM pattern when Lists/Collections are involved.
Say the MainModel has a few properties and methods, as well as a list that contains other DetailModel objects. The DetailModel objects can be added, removed, or re-ordered.
The MainView will show a few controls related the the root model, and have a ListBox populated from the list. Each item will have it's own sub-view via a DetailModelView UserControl.
Finally, there is a MainViewModel. This has properties backed by the MainModel's properties and methods, bound to the Main View, with change notification keeping everything in sync. (Up to this point, I am comfortable with the pattern - more stating this in case there is something fundamental I am missing...)
When it comes to handling the list, I get confused. I have come across several examples where the MainViewModel simply exposes the list of DetailModels to the view, and the DetailModelViews are bound directly to the models. This functions, but is problematic. It does not consistently following the pattern (no DetailViewModel exists), and it drives me to include some UI-related code in my detail models. It seems clear to me that the MainViewModel should expose a list of DetailViewModels for the UI to bind, but I am stuck on how to implement such a thing!
How should manage the two lists (DetailModels and DetailViewModels)? I am really confused as where I initially populate the DetailViewModel list, and how I should handle adding, removing, or changing the order of the items to keep them synchronized!
Usually Models are nothing more than data objects. They shouldn't contain any code to do things like add/remove items from a list. This is the ViewModel's job.
In your case, I would create a MainViewModel that has the following properties:
ObservableCollection<DetailViewModel> Details
ICommand AddDetailCommand
ICommand RemoveDetailCommand
If your MainModel class is a data object, you can either expose it, or it's properties from the MainViewModel as well. Exposing it's Properties is the "MVVM purist" approach, while exposing the entire Model is sometimes more practical.
Your MainViewModel is in charge of creating the initial list of DetailViewModels, and it is in charge of Adding/Removing these items as well. For example, in the PropertyChanged event for the MainViewModel.MainModel property, it might rebuild the MainViewModel.Details collection, and the CollectionChanged event for the MainViewModel.Details property would update MainViewModel.MainModel.Details
You are right to have a separate DetailModels list and DetailViewModels list. The DetailViewModels list should be a property of type ObservableCollection<DetailViewModel>. You can populate the observable list when you set the Model (or at construction time, if you pass the model into the constructor of your ViewModel.)
private ObservableCollection<DetailViewModel> m_details;
public IEnumerable<DetailViewModel> Details
{
get { return m_details; }
}
You can the subscribe to m_details.CollectionChanged. This is where you can handle re-ordering the contents of the list in the Model.
I hope this helps.
In my experience, the only time you get away with exposing model objects to the view is if you're doing simple read-only presentation, e.g. displaying a string property in a ComboBox. If there's any kind of actual UI involving the object (especially one involving two-way data binding), a view model is needed.
Typically, a master VM's constructor will look like this:
public MasterViewModel(MasterModel m)
{
_Model = m;
_Detail = new ObservableCollection<DetailViewModel>(m.Detail);
}
where MasterModel.Detail is a collection of DetailModel objects, and _Detail is a backing field for a Detail property that's exposed to the view.
As far as adding, removing, and reordering items in this list is concerned, in the UI at least this will be done through commands on the MasterViewModel, which must manipulate both MasterModel.Detail and MasterViewModel.Detail. That's a bit of a pain, but unless you want to repopulate MasterViewModel.Detail after every change to MasterModel.Detail, it's really unavoidable.
On the other hand, if you've been wondering "why would I ever need to write unit tests for view models?", now you know.
Here is an answer that I think addresses this issue very nicely using an ObservableViewModelCollection<TViewModel, TModel>
It's nice and lazy. It takes an ObservableCollection and a ViewModelFactory in the ctor. I like it because it keeps state at the model layer where it belongs. User operations on the GUI can invoke commands at the VM which manipulate the M via public methods on the M. Any resulting changes at the M layer will be automatically handled by the class in this link.
https://stackoverflow.com/q/2177659/456490
Note my comment regarding SL vs. WPF

Problem with filtering DataGrid

I have bound my DataGrid to a DataTable and only few of the details are displayed in the grid. When I wanted to filter the DataGrid I created a View with my DataGrid's ItemsSource.
Code:
Dim myView As ICollectionView = CollectionViewSource.GetDefaultView(MyDGrid.ItemsSource)
myView.Filter = New Predicate(Of Object)(AddressOf filterFunc1)
Now When I do the search, the non-displayed fields are also included in the search.
Public Function filterFunc1(ByVal obj As Object) As Boolean
Dim filStr As String = "*" & TextBox1.Text & "*"
For Each item As String In obj.Row.ItemArray
**If item.ToLower Like filStr.ToLower Then**
Return True
End If
Next
Return False
End Function
Also I Have ComboBox fields in the DataGrid which are loaded separately from other DataTable's. Now I cant Include them in the search.
A screenshot from my App:
So how do I make a search that includes only the Text from Displayed part.
EDIT: Also how do I skip searching the null valued fileds? 'cause thats causing an exception in my case.
Well then...
Your question is pretty disjointed and I can't understand all of it - maybe that's why you didn't get an answer so far. Skipping null fields is simply a matter of adding a new condition in filterFunc1 - if Convert.IsDBNull(item) then continue for (assuming item is a field in a DataRow, of course).
However, this programming style is pretty foggy and I'd recommend at the very least being more clear on which columns you filter, and the types of objects in the columns. A much better approach would be to map the data you're getting from the database to actual objects in your application - that allows for more type-safe programming. I think the main problem here is that nobody can really tell what's going on there from a few lines of code because nobody can make any assumptions about what kind of objects are there.
About the items in the ComboBox, no idea what kind of difficulties you're having, you might want to clear that up a bit.
you could maintain, instead of simply strings, structures containing both captions and IDs, like say
public class YourComboItem
public property Id as string [get/set]
public property Title as string [get/set]
end class
Then bind your ComboBox's ItemsSource to a collection of these items retrieved from the database, and set DisplayMemberPath to Title and ValueMemberPath to Id. Then you can use the ComboBox's SelectedValue to get the selected ID. As you can see, having objects instead of raw data structures can have quite some advantages.
Of course, I described getting the SelectedValue directly from the ComboBox, while a much better architecture would be MVVM, with the ViewModel containing an ObservableCollection(Of YourComboItem) and the ComboBox's ItemSource bound to it with an actual binding. Then you can also bind the SelectedItem to a property in your ViewModel, and have the item as a whole, including both Id and Title, to work with without knowing anything about your user interface. Alternatively you could have an ICollectionView generated from the collection of items and bind the ItemsSource to that, then you'd have the selected item in the ICollectionView's CurrentItem property.
I'd really recommend reading up on MVVM (Model-View-ViewModel) to make your work with WPF a whole lot easier.

Resources