ObservableCollection not showing up in UI - c#

I'm trying to display list of items in XAML. I get the list from public API, convert it to the class I need and then I want to display it.
public static async Task PopulateListAsync(ObservableCollection<MyClass> myList) {
var listContainer = await GetListAsync();
foreach (var item in listContainer) {
//converting from one class to another, editing some properties and such
myList.Add(item );
}
}
and on the MainPage.cs I had
public ObservableCollection<MyClass> Value { get; set; }
public MainPage() {
this.InitializeComponent();
Value = new ObservableCollection<MyClass>();
}
private async void Page_Loaded(object sender, RoutedEventArgs e) {
await PopulateListAsync(Value);
}
And I displayed in the XAML fine.
But then I wanted to introduce filtering. So I get the data, convert them to some class and insert them to a list, which I then filter with LINQ (seems easier then filtering in ObservableCollection).
Basically I replaced the PopulateListAsync() with FormatListAsync() which instead of inserting the data directly into the ObservableCollection<>, returns a List<>. Then I have a "middle man" function
public static async Task PopulateListAsync(ObservableCollection<MyClass> myList) {
myList = new ObservableCollection<MyClass>(await FormatListAsync());
//filtering itself isn't implemented yet, but it would be placed here
}
I probably could just loop trough mylist and add it one by one into the ObservableCollection<>, but I feel like there surely is a better way.
I think I'm supposed to implement some PropertyChanged event or something like that, but I tried a few (this one for example), unsuccessfully. I don't think I quite understand how to implement it.

If you are assign new value for method parameter then you just change reference's copy to the collection and don't change source reference. You can read more about passing reference types as method parameters on MSDN.
Also, if you will change property that not implements INotifyPropertyChanged itself then you'll have no changes in UI because your view doesn't know about the changes.
In the simple and easy way you can manipulate source collection instead of creating new one. Just do something like
public static async Task PopulateListAsync(ObservableCollection<MyClass> myList)
{
// newList can be an List<MyClass> type, not ObservableCollection
var newList = await FormatListAsync();
// change displayed list with new data
myList.Clear();
foreach(var newValue in newList)
myList.Add(newValue);
}
The other option, you can implement INotifyPropertyChanged for your ViewModel and raise PropertyChanged event in the setter of Value property:
private ObservableCollection<MyClass> _value;
public ObservableCollection<MyClass> Value
{
get
{
return _value;
}
set
{
// I hope this line of code will convince you to give more clear variable name
if(value != _value)
{
_value = value;
NotifyPropertyChanged(nameof(Value));
}
}
}
Also, you'll need to assign Value directly in the PopulateListAsync():
public static async Task PopulateListAsync()
{
Value = new ObservableCollection<MyClass>(await FormatListAsync());
}

Related

Working with an ObservableCollection based on an Entity Framework one to many property

Working with WPF in MVVM. I have a ViewModel with a CurrentItem property. This is an Item object which is pulled directly from Entity Framework. Item has a collection of Property objects.
public virtual ICollection<Property> Properties { get; set; }
In the View, I need users to be able to add and remove objects from this collection. To do that, I need to create an ObservableCollection<Property>, which we'll call ItemProperties.
There are various ways to do this. The most obvious is to add a ObservableCollection<Property> property on the ViewModel. Then populate this in the constructor, like so:
ItemProperties = new ObservableCollection<Property>(CurrentItem.Properties);
It's also possible to create an ObservableCollection wrapper that sits over the top of the real collection:
public ObservableCollection<Property> ItemProperties
{
get
{
return new ObservableCollection<Property>(CurrentItem.Properties);
}
set
{
CurrentItem.Properties = value.ToList();
OnPropertyChanged("ItemProperties");
}
}
Which has its own problems. You can't just Add() to this collection, since it'll get first, meaning the collection remains unchanged. So you'd either have to spin up a new collection, add to that, and then assign its value to the property or raise the OnPropertyChanged event outside the property. Either of which also sounds like a maintenance issue.
Is there a more effective way of doing this, which allows you to access the EF property list directly?
On this you have advantage of decoupling between data layer and Presentation , No need to spin up the collection.
Try a LoadedEvent to load data from the server.
Sample event is below
private ObservableCollection<Property> _itemProperties;
public ObservableCollection<Property> ItemProperties
{
get { return _itemProperties; }
set
{
_itemProperties= value;
RaisePropertyChanged(() => ItemProperties);
}
}
The loaded event
var result= await Task.Run(() => MyBusiness.GetMyData());
//Map to the viewModel if necessary
ItemProperties = result;
Add to the collection
var isSuccess = await Task.Run(()=>MyBusiness.Insert(x));
if(isSuccess)
{
ItemProperties.Add(x);
}
If you have access to your DbContext in your ViewModel class, you can use DbSet<TEntity>.Local property which it will give you an ObservableCollection<TEntity> that contains all Unchanged, Modified and Added objects that are currently tracked by the DbContext for the given DbSet, but first you need to filter to load into memory only the PropertyItems that belong to your CurrentItem.
public class YourViewModel
{
private context=new YourContext();
public YourViewModel()
{
context.ItemProperties.Where(ip=>ip.ItemId==CurrentItem.Id).Load();
ItemProperties=context.ItemProperties.Local;
}
private ObservableCollection<Property> _itemProperties;
public ObservableCollection<Property> ItemProperties
{
get { return _itemProperties; }
set
{
_itemProperties= value;
OnPropertyChanged("ItemProperties");
}
}
public void SaveItemProperties()
{
context.SaveChanges();
}
}
To save the changes the only you need to do is create, for example, a command that calls the SaveItemProperties method. Also, it could be a good idea disable lazy loading to not load twice the ItemProperties related to your CurrentItem.
If you need to understand more about how this works you can read this article.
either way is good. But what you need to do is to define an handler to the event CollectionChanged present in the Observable Collection. Your underlying entity must have a default constructor too. So when the new item will be created in the grid, that event will be raised.
_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e){if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove)
{
}
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add
}
First of all, I don't think creating an ObservableCollection for every get is a good idea. Instead I would cache it in a field. Second, for the cached instance, you will probably want to subscribe to CollectionChanged event in which you will changes will be persisted to the underlying collection.

Make ComboBox accept only specific type

I currently have a ComboBox in my Windows Forms Application. In order to specify which values the ComboBox will contain, I set DataSource property of the ComboBox to some array so that ComboBox contains values from that array. I could also use Items.Add() to add new values to ComboBox. However, I want to make sure that ComboBox can be populated with objects of some specific type. So, if I have a class called X, then I want to make it so that only an array of type X can be used as a data source for the ComboBox. Right now, ComboBox accepts objects of type System.Object. How can I achieve it? Is there a property of ComboBox that I need to set to be equal to my data type's name? Or is there an event that will check whether an object added to my ComboBox is of the needed type and will throw an exception if not?
I was thinking of creating a new class as a subtype of ComboBox, and overriding the Add method of Items property so that Add checks whether its argument is of the needed type (not sure if and how I can do it). Even if I do that, there are still other ways to add new values into ComboBox (AddRange, CopyTo, etc.), so I think there should be a more elegant solution to this problem.
If you want to control the type of item that the ComboBox can contain, you could try creating a new class derived form ComboBox, but you'd run into the problem that it still has the ComboBox.ObjectCollection Items property which would still accept any type! And (unfortunately for your idea of overriding) the Add method isn't virtual.
The only practical solution that I could think of would be to abstract the ComboBox somehow. If this isn't shared code, I would recommend just creating a method that you would use to add items to the ComboBox. Something like:
// NOTE: All items that are added to comboBox1 need to be of type `SomeType`.
private void AddItemToComboBox(SomeType item)
{
comboBox1.Items.Add(item);
}
Any attempt to add a non-SomeType object to the ComboBox would be met with a compiler error. Unfortunately, there's no easy way to prevent someone from still adding a non-SomeType item to ComboBox.Items directly.
Again, if this isn't shared code, it shouldn't really be an issue.
You can hide Items property by your
own Items property of custom type which taking as parameter original ItemsCollection
Example class for testing
public class Order
{
public Int32 ID { get; set; }
public string Reference { get; set; }
public Order() { }
public Order(Int32 inID, string inReference)
{
this.ID = inID;
this.Reference = (inReference == null) ? string.Empty : inReference;
}
//Very important
//Because ComboBox using .ToString method for showing Items in the list
public override string ToString()
{
return this.Reference;
}
}
With next class I tried wrap ComboBox's items collection in own type.
Where adding items must be concrete type
Here you can add other methods/properties you need (Remove)
public class ComboBoxList<TCustomType>
{
private System.Windows.Forms.ComboBox.ObjectCollection _baseList;
public ComboBoxList(System.Windows.Forms.ComboBox.ObjectCollection baseItems)
{
_baseList = baseItems;
}
public TCustomType this[Int32 index]
{
get { return (TCustomType)_baseList[index]; }
set { _baseList[index] = value; }
}
public void Add(TCustomType item)
{
_baseList.Add(item);
}
public Int32 Count { get { return _baseList.Count; } }
}
Here custom combobox class derived from ComboBox
Added: generic type
public class ComboBoxCustomType<TCustomType> : System.Windows.Forms.ComboBox
{
//Hide base.Items property by our wrapping class
public new ComboBoxList<TCustomType> Items;
public ComboBoxCustomType() : base()
{
this.Items = new ComboBoxList<TCustomType>(base.Items);
}
public new TCustomType SelectedItem
{
get { return (TCustomType)base.SelectedItem; }
}
}
Next code used in the Form
private ComboBoxCustomType<Order> _cmbCustom;
//this method used in constructor of the Form
private void ComboBoxCustomType_Initialize()
{
_cmbCustom = new ComboBoxCustomType<Order>();
_cmbCustom.Location = new Point(100, 20);
_cmbCustom.Visible = true;
_cmbCustom.DropDownStyle = ComboBoxStyle.DropDownList;
_cmbCustom.Items.Add(new Order(0, " - nothing - "));
_cmbCustom.Items.Add(new Order(1, "One"));
_cmbCustom.Items.Add(new Order(2, "Three"));
_cmbCustom.Items.Add(new Order(3, "Four"));
_cmbCustom.SelectedIndex = 0;
this.Controls.Add(_cmbCustom);
}
Instead of overriding ComboBox (which wont work as stated in itsme86's answer) you could override usercontrol, add a combobox to this, and then only expose the elements that you wish to work with. Something similar to
public partial class MyComboBox<T> : UserControl where T: class
{
public MyComboBox()
{
InitializeComponent();
}
public void Add(T item)
{
comboBox1.Items.Add(item);
}
public IEnumerable<T> Items
{
get { return comboBox1.Items.Cast<T>(); }
}
}
Please note however that some pieces of automated software rely on access the the underlying controls however so this may cause some issues.
This approach never changes the Items of the combobox so they will still store as objects but when you access them, you are casting them to the correct type and only allowing them to be added of that type. You can create a new combobox via
var myCB = new MyComboBox<ItemClass>();

Creating ObservableCollection from class

I have a WPF app with an event log. I have an EventLog class I populate with saved events from an XML file when the app starts
namespace MyApp.Agent.EventLogging
{
public enum EventType
{
Infomation,
Error
}
public class EventLog
{
public String Image { get; set; }
public DateTime EventDate { get; set; }
public String EventText { get; set; }
}
}
public List<EventLog> GetSavedEvents()
{
string file = XmlUtilities.GetXmlLocation() + "\\Events.xml";
List<EventLog> elog = new List<EventLog>();
try
{
if (File.Exists(file))
{
Serialize<List<EventLog>> ser = new Serialize<List<EventLog>>();
elog = ser.DeserializeDocToObj(file);
}
}
catch
{
throw new InvalidEventLogException(ConfigurationManager.AppSettings.Get("EventLogFileInvalid"));
}
return elog;
}
I then convert this into an observable collection that a listview is bound to
List<EventLog> _evtLog = new List<EventLog>();
ObservableCollection<EventLog> _eventLog = new ObservableCollection<EventLog>();
_evtLog = logger.GetSavedEvents();
_evtLog.ForEach(x => _eventLog.Add(x));
I read that this is how it has to been done (although it seems a long winded way)
As the app is running new events are added to the observable collection. When the app closes I reverse this process to save the events.
While this works fine if there are only a few saved events as the list is getting bigger so the time it takes to do this is getting unrealistic (240K events is taking 7 secs). Ok first question you may ask is why would I want that many events anyway, truth is I don't but it does highlight that I am not doing this the best way?
So my questions are:
Do I really need to populate the observable collection? Can I not make _evtLog Observable without populating one from the other?
Can I restrict these lists to X events based on the date of the event?
You could easily change GetSavedEvents to serialize and deserialize an ObservableCollection<EventLog> instead of a List<EventLog>.
Having said that, the code to convert the list to an observable collection looks strange. Why aren't you just using the appropriate constructor?
_eventLog = new ObservableCollection<EventLog>(logger.GetSavedEvents());
The problem with using Add is that for each item being added you raise an event.
following will create ObservableCollection
var _eventLog = new ObservableCollection<EventLog>(logger.GetSavedEvents());
regarding filtering your results, yes you can do a quick LINQ select query where you define the restrictions and get the results as a List which you'll pass to ObservableCollection
some idea's on filtering results
you might need some tweaking depending on your needs
var _eventLog = new ObservableCollection<EventLog>(logger.GetSavedEvents().where(event => event.date == some date).ToList());
Is this the actual code that generates the ObservableCollection? Generating an OC from a 240k element list should NOT take 7 seconds, it should be nearly instantaneous.
Are you sure you are not adding events to a collection that is already bound to a listview, causing the listview to be updated for each item?
Try using this instead of ObservableCollection:
class MyObservableCollection<T> : ObservableCollection<T>
{
private bool _notifyCollectionChanged = true;
protected override void OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (_notifyCollectionChanged)
base.OnCollectionChanged(e);
}
public void AddRange(IEnumerable<T> collection)
{
_notifyCollectionChanged = false;
foreach (T element in collection)
Add(element);
_notifyCollectionChanged = true;
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
And add the List to the ObservableCollection using oc.AddRange(list).
If this doesn't help, then your GetSavedEvents function is probably what is running for 7 seconds, and it has nothing to do with the ObservableCollection.

Convert custom Class to List<>

Scenario:
i have a web form from where i m taking input for Item class now i want to assign values to feature that have return type of list how can i do that.
item value = new item(),
value.feature = serialtextbox.text; //error
foreach ( var item in value) //error
{
item.SerialNo= serialtextbox.text;
}
Item and Item feature classes
Class Item
{
list<Itemfeature> features;
}
class ItemFeature
{
public int SerialNo
{
get { return serialno; }
set { serialno = value; }
}
public int Weight
{
get { return weight; }
set { weight = value; }
}
}
Plz help me out
Note: No language is specified, but it looks like C#. I'm assuming C# in this answer.
It's not really clear what you're trying to do here, but I'll give it a shot. First of all, you're going to want to post the actual code you're using. This code won't even compile, it's loaded with syntax errors.
Let's take a look at your objects first:
class Item
{
List<ItemFeature> features;
}
class ItemFeature
{
public int SerialNo
{
get { return serialno; }
set { serialno = value; }
}
public int Weight
{
get { return weight; }
set { weight = value; }
}
}
You have a custom class, ItemFeature, which consists of a serial number (integer) and a weight (integer). You then have another custom class, Item, which consists of a list of ItemFeatures.
Now it looks like you're trying to add a new ItemFeature to the Item and then loop through all of them and set them again?. Something like this, perhaps?:
Item value = new Item();
value.features.Add(new ItemFeature { SerialNo = int.Parse(serialtextbox.Text) } );
foreach (var item in value.features)
{
item.SerialNo = int.Parse(serialtextbox.Text);
}
(Note that this code is probably as free-hand as your code, so I haven't tested it or anything.)
What I've changed here is:
Setting the SerialNo property, rather than trying to set the ItemFeature directly to a value. You need to dig into the object's property to set a value on that property, not just set it to the entire object.
Converting the input (a string) into the property's type (an int).
Looping through the list, not the Item object itself. The Item object contains a list as a property, but the object itself isn't a list. You can loop through the property, not through the parent object.
A few things to ask/note:
What exactly are you trying to do? You have a list of objects, but you're only setting one and then looping through that one to set it again. Why?
You may want to consider more apt class/property names. Things like "Item" can be a bit unclear.
Your Item class has a public variable, features. This is generally frowned upon. It's better to use a property. That way if you ever have to add logic behind it you won't break compatibility outside of the object itself. The ItemFeature class has properties like this, which is good. They can be additionally shortened by using automatic properties if you'd like, just to keep things clean and simple.
Note that my code isn't doing any input checking on the serialtextbox.Text value. It should be. I presented it in a simpler form as an introductory approach to something that will work under ideal conditions. But something like the following would be better:
var serialValue = 0;
if (!int.TryParse(serialtextbox.Text, out serialValue))
{
// Here you would probably present an error to the user stating that the form field failed validation.
// Maybe even throw an exception? Depends on how you handle errors.
// Mainly, exit the logic flow.
return;
}
var value = new Item();
value.features.Add(new ItemFeature { SerialNo = serialValue } );
Edit: I just noticed that my call to .Add() will actually fail. You'll want to initialize the list before trying to use it. Consider changing the Item class to something like this:
class Item
{
public List<ItemFeature> features { get; set; }
public Item()
{
features = new List<ItemFeature>();
}
}
Two things changed here:
I converted the public member to a property, as previously mentioned.
I added a constructor which initializes the list so that it can be used. Otherwise, being a reference type, it would default to null. So any call to .Add() or any other method on the list would throw a NullReferenceException because there's no object on which to call the method(s).

How to Bind Listbox in WPF to a generic list?

i'm having trouble getting a clear answer for this.
I have a Static class (DataHolder) that holds a static list with a complex type (CustomerName and CustomerID properties).
I want to bind it to a ListBox in WPF but add another item that will have the word "All" for future drag and drop capablilities.
Anyone?
Create a ViewModel Class you can databind to! The ViewModel can reference the static class and copy the items to its own collection and add the all item to it.
Like this
public class YourViewModel
{
public virtual ObservableCollection<YourComplexType> YourCollection
{
get
{
var list = new ObservableCollection<YourComplexType>(YourStaticClass.YourList);
var allEntity = new YourComplexType();
allEntity.Name = "all";
allEntity.Id = 0;
list.Insert(0, allEntity);
return list;
}
}
}
Note, sometimes, you need empty Items. Since WPF can't databind to null values you need to use the same approach to handle it. The empty business entity has been a best practice for it. Just google it.
That "All" item has to be part of the list you bind your ListBox against. Natuarally you can not add that item to the DataHolder list because it holds items of type Customer (or similar). You could of course add a "magic" Customer that always acts as the "All" item but that is for obvious reasons a serious case of design smell (it is a list of Customers after all).
What you could do, is to not bind against the DataHolder list directly but introduce a wrapper. This wrapper would be your ViewModel. You would bind your ListBox agains a list of CustomerListItemViewModel that represents either a Customer or the "All" item.
CustomerViewModel
{
string Id { get; private set; }
string Name { get; set; }
public static readonly CustomerViewModel All { get; private set; }
static CustomerViewModel()
{
// set up the one and only "All" item
All = new CustomerViewModel();
All.Name = ResourceStrings.All;
}
private CustomerViewModel()
{
}
public CustomerViewModel(Customer actualCustomer)
{
this.Name = actualCustomer.Name;
this.Id = actualCustomer.Id;
}
}
someOtherViewModel.Customers = new ObservableCollection<CustomerViewModel>();
// add all the wrapping CustomerViewModel instances to the collection
someOtherViewModel.Customers.Add(CustomerViewModel.All);
And then in your Drag&Drop code somewhere in the ViewModel:
if(tragetCustomerViewModelItem = CustomerViewModel.All)
{
// something was dropped to the "All" item
}
I might have just introduced you to the benefits of MVVM in WPF. It saves you a lot of hassle in the long run.
If you use binding than the data provided as the source has to hold all of the items, ie. you can't databind and then add another item to the list.
You should add the "All" item to the DataHolder collection, and handle the 'All' item separately in your code.

Categories