WPF databinding: database or objects? - c#

I'm trying to create this:
Tag1 has the ordered list of objects: O1, O3, O2.
Tag2 has the ordered list of objects: O1, O4.
Every time I click a tag, I want to see the list of objects. So clicking Tag1 would show in a listbox:
O1
O3
O2
But I would like to keep the auto update so every time I edit or add/delete an object it auto updates (I suppose I need to implement something like the interfaces INotifyCollectionChanged and INotifyPropertyChanged?).
I could use a database and have the tables Tag, Object and TagObject, this last one with the TagID and ObjectID. But I also would like to avoid databases, since it's a desktop application.
I could also use objects like ObservableCollections, but I have the problem of having duplicate objects. I can use references to the objects but it gets messy.
Anyone has any suggestion on how to do this?
Thank you.

One option would be to create an object that contains a dataset (System.Data namespace). Inside the dataset it would have 3 tables that would be linked using defined foreign keys the same as if it was in a database. These can later be stored to XML if need be.
Your object would then have to expose a dataview which can be set as the datacontext and bound too.
Editing the dataset in code then updates the screen and editing the screen will update the dataset.

Move all the logic controlling the updated data out of the WPF page and into another class that pushes the new data into the WPF page, when it changes, rather than the WPF pulling the data out of the objects.
Here is some example code:
class WpfPage
{
public List OrderedListForTag1 { set { /* whatever GUI binding code you need to deal with the new list for tag 1 */ }
public List OrderedListForTag2 { set { /* whatever GUI binding code you need to deal with the new list for tag 2*/ }
}
class WpfPresenter
{
WpfPage thePage;
public void Tag1Selected()
{
//Calculate changes to 01, 02, 04 etcetce
//If changed, update the page
thePage.OrderedListForTag1 = //new list of objects
}
}
This is one of the model-view-controller pattern(s) which is very common in GUI construction. This series of articles covers the concepts.

Related

Create a copy of an ObservableCollection that doesn't update

I am building a control in xamarin forms that binds to a list of objects. To get this binding to work I need to use observable collections (otherwise propertychanged methods don't fire).
I noticed a really frustrating interaction as a result of needing to use OC's as opposed to lists. Whenever the binded OC updates, the values in my controls are automatically updated, even if they are just references of the OC, Here is how i am copying the OC.
//Internal list of events
private List<EventItem> _events;
void OnEventsChanged(ObservableCollection<EventItem> eventsCollection)
{
//Error handle
List<EventItem> events = eventsCollection.ToList();
//Do something
_events = events;
}
The problem comes when the OC updates, I want to check for new/deleted AND altered objects. The issue is that when the OC updates, it is updating the internal list (_events) aswell. This means when I go to do comparisons between the old & new values, they are the same.
Honestly I don't really understand how c# handles copying references of objects around, I had a similar issue a while back with DateTime.Now being calculated as opposed to copying the value of the already initialised object.
var time = DateTime.Now;
await Task.Delay(1000);
var time2 = time; //This is 1 second later than time, not the value of time (which is what I wanted)
I have used Objective-C in the past and that has the concept of MutableCopy where you can assign a new list from an existing one, they have the same values but aren't linked.
How can I do this in C# so that my controls internal list is only updated by me and not the OC?
Thanks
That's perfectly normal. If I have enough time, I'll try to explain it to you.
In a nutshell, the observableList (or a List) is a list of reference to the objects and not a list of objects. The thing is that the objects are not copied inside a list but the list contains a reference to the different objects. That means that if you do something like ToList(), you get another list of references to the exact same objects.
Now to solve your problem. Just create a new list with new objects with something like
var newList = oldList.Select(x => new Model(x)).ToList();
And of course the Model class has a constructor that accept a Model as a parameter and copy the properties.
When you write _events = events;, you create not a new object, but a reference for the same object. https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/index .
You should to clone (create a copy of object itself) as it mentioned in comment by #Matt.

One ViewModel containing a collection of ViewModels, different Views depending on value of ViewModel property

I am new to the MVVM-pattern, and am trying out Caliburn.Micro on a project.
I want to have one ViewModel (which contains a collection of ViewModels) shared by multiple Views, where each view only displays items which have a certain value on one of it's properties.
To get specific, I am using a service which allows me to monitor different values that update frequently. I then get an object of type MonitoredItem, which contains a property of type DataValue, which in turn contains an object for the Value and a property for the value's datatype.
So far I have a MonitoredItemViewModel which uses this service's MonitoredItem class as it's model, and a MonitoredItemsViewModel which contains BindableCollection<MonitoredItemViewModel> MonitoredItems, and commands for adding/removing items.
I also have a MonitoredItemsView where I can see all the items I am currently monitoring.
How do I go about splitting up the view, so that I can have all MonitoredItems where DataValue is an integer/float/double displayed in one area in my window, boolean values displayed somewhere else etc?
Don't do it in the view, instead expose different collections on your ViewModels according to what you need to filter.
This can be done either as known collections, e.g.
public ObservableCollection<MonitoredItemViewModel> ItemsWhereFooIsBar ...
public ObservableCollection<MonitoredItemViewModel> ItemsWhereFooIsntBar ...
or you could do it more generically to return filtered collections on demand
public ObservableCollection<MonitoredItemViewModel> GetItems(Func<DataValue, bool> matches)
{
//Filter collection with
return ... allItems.Where(x=>matches(x))... ;
}
and call via
GetItems(x=>x.Foo == Bar)
The problem you are going to have is when the items change and should switch from collection to collection. If you were using ReactiveUI this would be incredibly easy as you can use Rx to trigger its built in item tracking and also use its .CreateDerivedCollection(...) to build the new collections automatically (hint, hint :-))
If not then you have a few choices.
You can derive a class from ObservableCollection so that as well as being notified via CollectionChanged when new items are added or removed, or also get notified when the properties of the items change as well.
Or you could make your ItemViewModel immutable so that its properties never change, but instead you drop the old item and add an updated one into the correct collection.

ComboBox not updating when object added to bound list

I have an object that represents a client, and that object has a list of the clients branches:
private List<Branch> _branches;
[System.Xml.Serialization.XmlArray("Branches"), System.Xml.Serialization.XmlArrayItem(typeof(Branch))]
public List<Branch> Branches
{
get { return _branches; }
set
{
_branches = value;
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs("Branches"));
}
}
}
In one form (WinForms) I have a ComboBox that I've bound to that list:
// creating a binding list for the branches
var bindingList = new BindingList<Branch>(Client.Branches);
// bind list to combo box
cmbBranches.DataSource = bindingList;
cmbBranches.DisplayMember = "Name";
cmbBranches.ValueMember = "Name";
In another function, I create a new Branch object and add it to the existing list: Client.Branches.Add(newBranch). I would expect this to update the ComboBox but it doesn't. Why not, and how do I make it update? (Edit: I'd also like this to update when removing an object from the list. The reason it doesn't work is, I assume, directly related to why the box isn't updating when Add is called.)
In doing research, I found this SO answer, which seems to imply that it will work. I feel like I'm missing something simple ...
difference between ObservableCollection and BindingList
Edit: Some further information about what I've tried and some additional goals.
I cannot use ObservableCollection<T> instead of List<T> as I need to use Exists in the code. Which the former doesn't have.
I need to update the original list when the new object is added, in addition to updating the drop down box.
To summarize my comments below, I attempted adding this:
var bindingList = (BindingList<Branch>) cmbBranches.DataSource;
bindingList.Add(frmAddBranch.NewBranch);
But that results in the object being added to the ComboBox twice. Somehow by calling bindingList.Add it's "resetting" the data source and doubling up. I cannot find any function that "refreshes" the data display once it's bound. Control.ResetBindings() did not work.
Well, it doesn't work that way. The inner List<T> has no change notification mechanism, so adding directly to inner List<T> will not generate any change notification that would eventually reach the combo box. Most convenient way to do what you want is adding the item through the BindingList<T> instead.
I believe you have to add the items directly to the BindingList (but not to the backing Branches list - the BindingList should take care of this for you).

Use deserialized XML data in TabController + DataGrid

After this: Acess to a DataGrid through C# code and manipulate data inside the DataGrid I decided that i should deserialize my XML data and use it that way because i need basic CRUD manipulation in my application.
I already have my xml data class (using XSD tool, you can find the class here -> http://pastebin.com/6GWFAem6) and have my data deserialized, problem is:
I need a TabControl with as many tabs as Semestre in my xml, each tab will have GPASemestre.Nome header.
Inside each tab i need a DataGrid with Cadeiras of that specific Semestre.
I need to be able to CRUD data in the DataGrid and the tabs.
Questions:
To solve all of this what do you think is best? Creating everything (tabs + datagrid) and make the necessary binds (which i don't really know what they will be) / populate the DataGrid somehow, in C# only? Or there is a way to simplify code in C# using XAML?
Cadeiras are stored in arrays so, each time i add a new one, i need to create a new array (or create a new array with more spaces and manage it), i already saw some questions here where ppl used List's but where having troubles with it, is it possible to use a list or not? If so, what do i have to change in the XSD auto generated class?
Thanks in advance!
I would suggest the use of data-binding and data-templating (read those if you have not yet done so) for as much as possible, for that to work well the auto-generated classes need to be adjusted to support it.
The first step is implementing INotifyPropertyChanged in all the properties which are not collections so that if the property is changed the UI will update automatically. Only use arrays for deserialisation at best, after that copy the elements to a property which is of type ObservableCollection<T>, or any other collection which implements INotifyCollectionChanged so that the grids update when a new item is added to the collection and never touch the arrays again.
You could also make the Array property "virtual" (has no backing field, just manipulates on get & set), e.g.:
//The property & field used for binding and editing
private readonly ObservableCollection<GPASemestre> _ObservableSemestre = new ObservableCollection<GPASemestre>();
public ObservableCollection<GPASemestre> ObservableSemestre { get { return _ObservableSemestre; } }
//The property used for serialisation/deserialisation
public GPASemestre[] Semestre
{
get
{
return ObservableSemestre.ToArray();
}
set
{
ObservableSemestre.Clear();
foreach (var item in value)
{
ObservableSemestre.Add(item);
}
}
}

MVVM and DataGrid View re-use as embedded controls within otherviews with subset data

Okay here's the situation. Net 4 WPF NO Silverlight.
I have several Views that present a datagrid showing the contents of some observable collections e.g.
ObservableCollection<ClassAViewModel> sourceA;
ObservableCollection<ClassBViewModel> sourceB;
ObservableCollection<ClassCViewModel> sourceC;
The collections are populated by a call to the data access layer. I can display this data easily enough with a Usercontrol that contains a datagrid bound to the appropriate collection.
I have
ClassAView and ClassAViewModel to
control display of single ClassA
data,
ClassBView and ClassBViewModel to
control display of single ClassB data
ClassCView and ClassCViewModel to
control display of single ClassC data
I also have:
AllClassAView and AllClassAViewModel
to display a DataGrid with data
relating to all ClassA instances.
AllClassBView and AllClassBViewModel
to display a DataGrid with data
relating to all ClassB instances.
etc.
Now say that ClassA contains a subset of the ClassB collection and a subset of the ClassC collection etc.
In my resource file I have bound the ViewModels and their Views together in the following manner (vm and vw are the namespaces of where they are) :
<DataTemplate DataType="x:Type vm:ClassAViewModel}">
<vw:ClassAView/>
</DataTemplate>
Now what I was hoping to do was use an AllClassBView or AllClassBViewModel within the ClassAView to display the subset of ClassB instances that relate to it.
What is the best way to call up this data?
Can I re-use the AllClassBView UserControl to display a subset of the ClassB ObservableCollection and what is the best way of doing this?
I don't want to place any code within the ClassAView.cs file only within the ClassAView.xaml or ClassAViewModel.
Should I just add a new property to the AllClassBView and use that to filter the list? For example within ClassBViewModel where I generate the list of ClassBViewModels (for use in the DataGrid) I can use:
if(this.ClassA_Id!=0)
{
List<ClassBViewModel> all = (from ClassB in this.DataRepository.GetClassBs().Where(x=>x.ClassA_Id == this.ClassA_Id) select new ClassBViewModel()).ToList();
}
else
{
List<ClassBViewModel> all = (from ClassB in this.DataRepository.GetClassBs() select new ClassBViewModel()).ToList();
}
this.sourceB= new ObservableCollection<ClassBViewModel>(all);
Well I've cracked it.
My problem may not have come across as very clear.
Whether my solution is good practice or not I don't know.
What I have done is as follows:
I placed an extra property into the AllClassBViewModel that allows me to filter its list of ClassBViewModels to those that relate to ClassA.
public ulong ClassA_Id{get;set;}
So now when the AllClassBViewModel builds its list of ClassBViewModels it can now filter them by:
if(this.ClassA_Id!=0)
{
List<ClassBViewModel> all = (from ClassB in this.DataRepository.GetClassBs().Where(x=>x.ClassA_Id == this.ClassA_Id) select new ClassBViewModel()).ToList();
}
else
{
List<ClassBViewModel> all = (from ClassB in this.DataRepository.GetClassBs() select new ClassBViewModel()).ToList();
}
this.sourceB= new ObservableCollection<ClassBViewModel>(all);
I added a property and field to my ClassAViewModel that were of the AllClassBViewModel type.
private AllClassBViewModel fieldAllClassBViewModel;
public AllClassBViewModel AllClassBVM{get{return this.fieldAllClassBViewModel;}}
I then added an entry to an AllClassBView into the ClassAView that had a data context bound to the AllClassBVM property within ClassAViewModel.
<vw:AllClassBView DataContext="{Binding AllClassBVM}"/>
No all I have to do is check through the command binding.

Categories

Resources