Creating ObservableCollection from class - c#

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.

Related

ObservableCollection not showing up in UI

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());
}

Does an "Atomic Move" operation exist for C# collections?

Lets say I have a ViewModel in a WPF application with an ObservableCollection. This collection is synchronized with a Model collection so that updating either one updates the other. The model has a business rule that states it must contain 2 Thing's at all times, but the order in the collection can be changed. The user will see a list that has two Thing's in it, and has buttons they can click to move a Thing up or down. The model class subscribes to it's collection's change handler and throws an exception if there are ever less than 2 Things in the collection.
Something like this (very simplified, edge cases not handled etc)
public class ViewModel
{
private Model _model;
public ObservableCollection<Thing> Things { get; set; }
public Thing SelectedThing { get; set; }
public ViewModel(Model model)
{
_model = model;
Things = new ObservableCollection<Thing>(_model.Things);
// Some other code that ensures the model and viewmodel collections stay in sync
// ...
}
public void MoveUp()
{
var selected = SelectedThing;
int i = Things.IndexOf(selected);
Things.Remove(selected); // Throws exception
Things.Insert(i - 1, selected);
}
}
public class Model
{
public ObservableCollection<Thing> Things { get; set; }
public Model()
{
Things = new ObservableCollection
{
new Thing(),
new Thing()
};
Things.CollectionChanged += CollectionChangedHandler;
}
private void CollectionChangedHandler(object sender, CollectionChangedEventArgs e)
{
if(e.Action == CollectionChangedAction.Remove)
{
if(Things.Count < 2)
{
throw new YouCantDoThatException();
}
}
}
}
Basically, when a user clicks "Move Up", the selected item is temporarily removed from the collection and inserted above where it was before. This is an invalid state for the model though, and I can't change the model because it's an API for a lot of other things. Is there a way in C# to do an "Atomic Move" operation that will allow me to move the item without first removing it?
I see two options:
Use the "Move" function provided by ObservableCollection. The disadvantage is that your code is in a state with just one item (albeit for a very short amount of time), but your exception will not throw since CollectionChanged isn't raised until the move is complete.
Reverse your logic, and perform the insert before the remove. You could do this with the InsertItem(index, item) function, followed by the RemoveAt(index) command.
In general, there is no such thing as an "Atomic" move because of how "swap" operations work. You need to assign the "first" value to a temp value, assign the second to the first, and then assign the temp value to the second. Certain assembly operations can make this "Atomic" in the purest sense, but that won't help with NotifyCollectionChanged.
As an aside, you can derive from ObservableCollection and implement your own move algorithm, which you shouldn't need to do unless you need Move to have additional functionality. (MSDN).

Is it safe to make a List public for using it as a DataGridView's datasource?

i am creating a C# class library that reads data from a socket and store some data in a list. This list will change more the once per second during execution time.
In need this list to be the content of a dataGridView of a winforms application, but i'm wondering how can i expose it out of my library.
Is it safe to declare the List as public in the classLibrary like:
public class THRManager
{
public List <GaugeItem> outSource;
...
and then on the WinForm side:
public TMRMainForm()
{
THRManager thrC = new THRManager();
dataGridView1.DataSource = thrC.outSource;
...
Is this safe? If not, what's the best way?
Thx!
==================EDIT
Should i use DataTable or BindingSource ?
Use a ReadOnlyCollection but create it once in your constructor or where ever else you need them, in order not to do new ReadOnlyCollection... every time you access it.
public class THRManager
{
private List<GaugeItem> outsource;
private ReadOnlyCollection<GaugeItem> outSourceReadOnly;
public THRManager()
{
outSource = new List<GaugeItem>();
outSourceReadOnly = new ReadOnlyCollection<GaugeItem>(outSource);
}
public ReadOnlyCollection<GaugeItem> OutSource
{
get { return outSourceReadOnly; }
}
}
Hope code works without syntax errors :)
This is a safer option when publishing an inner collection.
private List<GaugeItem> outSource;
public ReadOnlyCollection<GaugeItem> OutSource
{
get { return new ReadOnlyCollection<GaugeItem>(outSource); }
}

Implementing ICollectionViewLiveShaping

How is ICollectionViewLiveShaping implemented for the purpose of filtering? Is it something like:
public ICollectionView WorkersEmployed { get; set; }
WorkersEmployed = new CollectionViewSource { Source = GameContainer.Game.Workers }.View;
I'm not using GetDefaultView because I need multiple instances of filters on this collection. If it matters, GameContainer.Game.Workers is an ObservableCollection.
ApplyFilter(WorkersEmployed);
private void ApplyFilter(ICollectionView collectionView)
{
collectionView.Filter = IsWorkerEmployed;
}
public bool IsWorkerEmployed(object item)
{
Worker w = item as Worker;
return w.EmployerID == this.ID;
}
This all works, but of course it must be manually refreshed, which is why I'm trying to use ICollectionViewLiveShaping. How does live filtering working?
Update: It appears that the only way to add a property to ICollectionViewLiveShaping's LiveFilteringProperties collection is via a string. Given that limitation, is it even possible to filter by properties in another class (Workers' EmployerID in this case)?
Is what I'm trying to do in this situation is even a viable option?
All you need to do is add a property in LiveFilteringProperties for which you want the filter to call on property change and set IsLiveFiltering to true for your collection to enable live filtering.
Make sure PropertyChanged event gets raised whenever EmployerID property changes i.e. your Worker class should implement INotifyPropertyChangedEvent.
This will work then -
public ICollectionViewLiveShaping WorkersEmployed { get; set; }
ICollectionView workersCV = new CollectionViewSource
{ Source = GameContainer.Game.Workers }.View;
ApplyFilter(workersCV);
WorkersEmployed = workersCV as ICollectionViewLiveShaping;
if (WorkersEmployed.CanChangeLiveFiltering)
{
WorkersEmployed.LiveFilteringProperties.Add("EmployerID");
WorkersEmployed.IsLiveFiltering = true;
}
We are using WPF + MVVM + Visual Studio 2017.
We want to convert this to add live filtering:
public ObservableCollection<RowViewModel> Rows { get; set; }
The method below has two key advantages:
It's designed to work efficiently with the WPF runtime to minimise on-screen rendering using bulk updates.
So it's fast.
And because the boilerplate code is listed below, it's easier to follow compared to any other docs you will find on the web.
Please let me know if this worked for you, any issues and I'll update the instructions to make easier.
And the steps:
Step 1: Non-notifying Collection Wrapper
Create a special ObservableCollection that does not fire update events. This is a one-off. We want to fire the update bulk update event ourselves, which is faster.
public class NonNotifyingObservableCollection<T> : ObservableCollection<T>
{
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { /* Do nothing */ }
}
Step 2: Convert to NonNotifyingObservableCollection
Convert to a private variable which uses this new collection.
private NonNotifyingObservableCollection<RowViewModel> rows;
// ... and in constructor
rows = new NonNotifyingObservableCollection<RowViewModel>();
Step 3: Add Wrapper
Add these variables:
private ICollectionView rowsView;
public ICollectionViewLiveShaping RowsLiveView { get; set; }
And in the Initialise() call after the ViewModel is constructed (or perhaps in the constructor):
// Call on the dispatcher.
dispatcher.InvokeAsync(() =>
{
this.rowsView = CollectionViewSource.GetDefaultView(this.rows);
this.rowsView.Filter = o =>
{
// This condition must be true for the row to be visible on the grid.
return ((RowViewModel)o).IsVisible == true;
};
this.RowsLiveView = (ICollectionViewLiveShaping)this.rowsView;
this.RowsLiveView.IsLiveFiltering = true;
// For completeness. Changing these properties fires a change notification (although
// we bypass this and manually call a bulk update using Refresh() for speed).
this.RowsLiveView.LiveFilteringProperties.Add("IsVisible");
});
Step 4: Add items
Now we add items to the backing collection, then call .Refresh() to refresh the view:
this.rowsView.Add(new RowViewModel( /* Set properties here. */ ));
We then bind the grid to RowsLiveView, (instead of binding to Rows in the original code).
Step 5: Update live filtering
Now we can update the IsVisible property, then call .Refresh() to redraw the grid.
rows[0].IsVisible=false;
this.rowsView.Refresh(); // Hides the first row.
Update
Update: This answer could be simplified. The whole point of ICollectionViewLiveShaping is to autorefresh without the need to call .Refresh(). Given that we have a NonNotifyingObservableCollection and we are manually controlling everything with a .Refresh(), could remove public ICollectionViewLiveShaping RowsLiveView { get; set; } and, directly to RowsView (make it a property with { get; set; }, and use normal ObservableCollection<>. In other words - ICollectionViewLiveShaping is great for a small amount of rows (e.g. <100), but for anything more, ICollectionView in combination with a bulk update and a manual Refresh() is better from a speed point of view.
I experimented with this and it looks like it is not designed for what you (and me) want: Automatic filtering when you change filtering conditions. It filters automatically when some properties of DataGrid's item source changes, but not when filter conditions change - you must call ICollectionViewSource.Refresh manually.

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