Clear data from ObservableCollection - c#

I've got a class that stores an image along with it's filename. I've created an ObservableCollection of this class, and bound that to a WPF listbox. (It's an image viewer of sorts)
I load 50 meg worth of data (currently about 10 images), then I want to remove one, some or all of the images from the observable collection and replace them (so that the memory footprint doesn't get too big while scrolling through many images).
For a start, I've actually got a button on the GUI to load the "next 10 images" - which it does, but it doubles the memory footprint. I've tried calling .Clear() and .ClearItems() from the collection, and also .Remove() and .RemoveAt(n) but the memory doesn't decrease. Am I mistunderstanding how ObservableCollection works?
Here's a synopsis of my code:
public class ImageDataList : ObservableCollection
{
public static ImageDataList Load(string path,int startVal, ImageDataList images)
{
// Load 10 images from defined start position
if (startVal<0)
startVal=0;
int endVal = startVal + 10;
if (endVal > Directory.GetFiles(path).Length)
endVal = Directory.GetFiles(path).Length;
// Attempt to clear all data from collection
images.ClearItems();
images.Clear();
while (images.Count>1)
{
images.RemoveAt(0);
}
for (int i = startVal; i < endVal; i++)
{
string filename = Directory.GetFiles(path)[i];
if (filename.Contains(".tif"))
{
ImageData imgData = ImageData.Load(filename);
images.Add(imgData);
}
}
return images;
}
}
After loading the images, it's passed to the GUI via:
listBox.DataContext = images;
I hope I've been clear... let me know if I should add anything!
Edit: For now I seem to have solved the problem by overwriting an item in the ObservableCollection, rather than trying to remove/clear it then add a new one. I still don't understand the memory problem though.

It may be that the garbage collector hasn't deleted the image objects from memory yet. The reason may be that you have enough memory available on your system so there is no need to delete the objects yet.
Does the memory consumption continue to rise when you load the next 10 images and the next 10 after that?
You should also consider disposing the images as Rakesh Gunijan suggests.

Why dont you create your own view model class like ImageDataViewModel and create observable collection of it instead of inheriting from ObservableCollection.
public class ImageDataViewModel : INotifyPropertyChanged, IDisposable
{
private string _id;
private string _imagePath;
public string Id
{
get
{
return _id;
}
set
{
_id = value;
OnPropertyChanged("Id");
}
}
public string ImagePath
{
get
{
return _imagePath;
}
set
{
_imagePath = value;
OnPropertyChanged("ImagePath");
}
}
private void OnPropertyChanged(string propertyName)
{
if(PropertyChanged!=null)
{
PropertyChanged(this,new PropertyChangedEventArgs(propertyName));
}
}
public void Dispose()
{
//Do dispose of resources.
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class YourViewModel : INotifyPropertyChanged, IDisposable
{
private ObservableCollection _images;
public ObservableCollection Images
{
get
{
return _images;
}
set
{
_images = value;
OnPropertyChanged("Images");
}
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void Dispose()
{
Images = null;
}
}

Related

WPF, C#, MVVM Notify ViewModel dynamically in changes from static vars in Model

I have ViewModel that makes some functions. Funcs are initiate by button, i have command on button click.
ViewModel.cs
class WindowViewModel : INotifyPropertyChanged
{
public WindowViewModel()
{
canExecute = true;
}
public ICommand ApplyKMMCommand //command for button click, works great, tested
{
get
{
return applyKMMCommand ?? (applyKMMCommand = new Commands.CommandHandler(() =>
ApplyKMMToNewImage(), canExecute));
}
}
private bool canExecute;
private ICommand applyKMMCommand;
public void ApplyKMMToNewImage()
{
ApplyKMM.Init(); //algorithm name
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public BitmapImage DisplayedImage //displaying image that i work with
{
get { return Bitmaps.Filepath; }
set { Bitmaps.Filepath = value; NotifyPropertyChanged(nameof(DisplayedImage)); }
}
}
Now, my ApplyKMM.Init()
class ApplyKMM
{
public static void Init()
{
Bitmaps.Filepath = //do some thing with it...
}
}
And my Models.Bitmaps.cs
static public BitmapImage Filepath
{
get { return filepath; }
set { filepath = value; }
}
static private BitmapImage filepath{ get; set; }
The problem is, when i make ApplyKMM.Init the Image control that is binded to View not change their value.
Without ApplyKMM i can do in ViewModel that thing:
DisplayedImage = //do things with bitmap...
And then, Image that is presented in View change (after making things with that image).
Can you tell me, how to notify ViewModel, that somewhere in code filepath from Models changed?
EDIT:
Binding in View Looks like standard binding:
<Image Source="{Binding DisplayedImage}"/>
Button click works too, i have problem only with communication between Models->ApplyKMM->ViewModel
EDIT2:
Properties Filepath is storage in Models folder, not folder where function ApplyKMM is. Look into my edit, i try to make something like:
Models -> ApplyKMM -> ViewModel. From Models, i get Filepath. Then, i using function ApplyKMM that is in another namespace. Then, after working on bitmap with ApplyKMM func i want to somehow notify ViewModel, that work on Model is done (for example, convert to grayscale) and i want to show that grayscale image in VM. It works, when i want to do Model -> ViewModel (ApplyKMM is in VM class) but i want to move out ApplyKMM away from ViewModel. And that when staris starts for me.
It appears like you want to notify when a static property changes. For this, you may use the StaticPropertyChanged event.
class ApplyKMM
{
#region Properties
static BitmapImage _Filepath;
public static BitmapImage Filepath
{
get { return _Filepath; }
set { if (_Filepath != value) { _Filepath = value; NotifyPropertyChanged(nameof(Filepath)); } }
}
#endregion
#region Static NotifyPropertyChanged
public static void NotifyStaticPropertyChanged(string propertyName)
{
StaticPropertyChanged?.Invoke(null, new PropertyChangedEventArgs(propertyName));
}
public void NotifyAllStaticPropertyChanged()
{
NotifyStaticPropertyChanged(string.Empty);
}
public static event EventHandler<PropertyChangedEventArgs> StaticPropertyChanged;
#endregion
}
Please take note that this is available from WPF Version 4.5.
You might also find this question interesting.
Basically it is not a good practice to notify changes of a static variable to an instance.
Then, let’s look at your code:
Bitmaps class does not implement INotifyPropertyChanged, so when Filepath changes, nothing will be notified (of course, it is a static property)
In your case, you should use local variable to hold DisplayedImages. Then changes on DisplayedImage should be updated by the binding.
BitmapImage _displayedImage;
public BitmapImage DisplayedImage
{
get { return displayedImage; }
set { displayedImage = value; NotifyPropertyChanged(nameof(DisplayedImage)); }
}

Changes to collection do not update UI

I don't understand why when I update a object, my bound controls do not update.
The data displays fine initially, but when I want to refresh the data displayed in the UI nothing happens when I update the object. The object updates fine. The ViewModel does use INotifyPropertyChanged on all fields.
However if I update individual items directly, I can update my UI. As commented below.
I guess I've made a school boy error somewhere here?
UPDATE: I've added the model to the question. While I understand the answers, I don't understand how to implement it. Attempted to implement a collection changed event without success. Can I have some pointers please?
public partial class CisArrivalsPanel : UserControl
{
private ApiDataArrivalsDepartures _theArrivalsDepartures;
public CisArrivalsPanel()
{
InitializeComponent();
_theArrivalsDepartures = new ApiDataArrivalsDepartures();
_theArrivalsDepartures = MakeQuery.LiveTrainArrivals("London Kings Cross");
this.DataContext = _theArrivalsDepartures;
ListBoxArr.ItemsSource = _theArrivalsDepartures.StationMovementList;
}
void Reload()
{
//This does not update the UI**
_theArrivalsDepartures = MakeQuery.LiveTrainArrivals("London Paddington");
//However this (when uncommented, and I comment out the above line) does update the UI**
//_theArrivalsDepartures.StationMovementList[0].OriginName = "test";
//_theArrivalsDepartures.StationMovementList[0].Platform = "0";
//_theArrivalsDepartures.StationMovementList[0].BestArrivalEstimateMins = "999";
//_theArrivalsDepartures.StationName = "test";
}
private void StationHeader_OnPreviewMouseDown(object sender, MouseButtonEventArgs e)
{
Reload();
Debug.WriteLine(_theArrivalsDepartures.StationName);
foreach (var a in _theArrivalsDepartures.StationMovementList)
{
Debug.WriteLine(a.OriginName);
Debug.WriteLine(a.BestArrivalEstimateMins);
}
}
}
EDIT : Added Model
public class ApiDataArrivalsDepartures : INotifyPropertyChanged
{
private string _stationName;
[JsonProperty(PropertyName = "station_name")]
public string StationName {
get
{
return _stationName;
}
set
{
_stationName = value;
NotifyPropertyChanged("StationName");
}
}
private List<StationListOfMovements> _stationMovementList;
public List<StationListOfMovements> StationMovementList
{
get
{
return _stationMovementList;
}
set
{
_stationMovementList = value;
NotifyPropertyChanged("StationMovementList");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
public class StationListOfMovements : INotifyPropertyChanged
{
private string _originName;
[JsonProperty(PropertyName = "origin_name")]
public string OriginName {
get
{
return _originName;
}
set
{
_originName = value;
NotifyPropertyChanged("OriginName");
}
}
[JsonProperty(PropertyName = "destination_name")]
public string DestinationName { get; set; }
private string _platform;
[JsonProperty(PropertyName = "Platform")]
public string Platform {
get
{
return _platform;
}
set
{
_platform = value;
NotifyPropertyChanged("Platform");
}
}
private string _bestArrivalEstimateMins;
[JsonProperty(PropertyName = "best_arrival_estimate_mins")]
public string BestArrivalEstimateMins {
get
{
return _bestArrivalEstimateMins;
}
set
{
_bestArrivalEstimateMins = value;
NotifyPropertyChanged("BestArrivalEstimateMins");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
There are two pieces here pertaining to your collection (technically three):
If you want a new collection to propagate, the collection property has to raise PropertyChanged (sounds like it does)
If you want add/remove on the collection to propagate, you need to use a collection that implements INotifyCollectionChanged. ObservableCollection is a good choice.
If you want changes to the items in the container to propagate, then those items need to implement INotifyPropertyChanged and raise the PropertyChanged event.
Make sure all those are covered, and the changes should appear on the UI as you expect.
You should update the DataContext and ItemsSource too.
void Reload()
{
//This does not update the UI**
_theArrivalsDepartures = MakeQuery.LiveTrainArrivals("London Paddington");
DataContext = theArrivalsDepartures;
ListBoxArr.ItemsSource = _theArrivalsDepartures.StationMovementList;
}
Use for the collection ObservableCollection , this class notify the ui when change to the collection occurred
your reload function works because the there is PropertyChanged on all the fields include this one
it notify the ui and reload the correct collection

Getting a notification from another class

I have a WPF application that displays a window with various information in it. In my code I create an instance of a custom class that I created which reads information from RFID card reader. To keep it simple - every now and then someone would swipe their card using the card reader which would generate a string that I successfully capture using my custom class.
The problem that I have is that I need to return that value to the window application so that I can update the information displayed in the window based on the value read. This is not as simple as calling a function in the custom class and returning a value as I don't know when exactly someone would swipe their card.
One solution that I could think of was to make a timer and pool the custom class every second or so to check if someone swiped their card, however, I don't think that's an effective solution.
Since I'm relatively new to WPF I'm assuming that the right way to do it is using INotifyProperyChanged but I'm unsure how to do it. Open to any other suggestions as well, thank you!
Create an event on your CardReader class that you can listen to on your ViewModel.
class CardInfo
{
public string CardDetails { get; set; }
}
class CardSwipedEventArgs
: EventArgs
{
public CardInfo SwipedCard { get; set; }
}
interface ICardReader
{
event EventHandler<CardSwipedEventArgs> CardSwiped;
}
class MyViewModel : INotifyPropertyChanged
{
private ICardReader _cardReader;
private string _lastCardSwiped;
public ICardReader CardReader
{
get
{
return _cardReader;
}
set
{
_cardReader = value;
_cardReader.CardSwiped += OnCardSwiped;
}
}
private void OnCardSwiped(object sender, CardSwipedEventArgs e)
{
LastCardSwiped = e.SwipedCard.CardDetails;
}
public string LastCardSwiped
{
get
{
return _lastCardSwiped;
}
set
{
_lastCardSwiped = value;
this.OnPropertyChanged("LastCardSwiped");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Thank you all for your posts. Using events was definitely the way but it wasn't easy to understand how they worked. Your feedback definitely helped but this article helped me understand how events worked best and how to implement them so I could deal with the issue successfully:
http://www.codeproject.com/Articles/9355/Creating-advanced-C-custom-events
Create an event on the class that reads the data from RFID.
public class CardSweepedEventArgs : EventArgs {
private readonly string _data;
public string Data { get { return _data; } }
public CardSweepedEventArgs(string data) {
_data = data;
}
}
public class YourReadinClass {
public EventHandler<CardSweepedEventArgs> CardSweeped;
// rest of logic.
}
In your class then subscribe to the event and do the necessary.

Update ObservableCollection from BackgroundWorker/ProgressChanged Event

I'm writing a simple tool for troubleshooting computers. Basically its just a WPF Window with a ListBox bound to an ObservableCollection<ComputerEntry> where ComputerEntry is a simple class containing the computer host name, and Status. All the tool does is ping each compute name in the list, and if a response is received ComputerEntry.Status is updated to indicate the computer is connected to the network somewhere...
Pinging however can take some time, up to a couple seconds per computer depending on if it has to timeout or not. So I'm running the actual ping in a BackgroundWorker and using the ReportProgress method to update the UI.
Unfortunately the ObservableCollection does not seem raise the PropertyChanged event after the objects are updated. The collection does update with the new information, but the status never changes in the ListBox. Presumably because it does not know that the collection has changed.
[EDIT]
Per fantasticfix, the key here is: "The ObservableCollection fires just when the list gets changed (added, exchanged, removed)." Since I was setting the properties of the object instead of modifying it, the ObservableCollection was not notifying the list of the change -- it didn't know how. After implenting INotifyPropertyChanged everything works fine. Conversly, replacing the object in the list with a new updated instance will also fix the problem.
[/EDIT]
Btw I'm using C# 3.5 and I'm not in a position where I can add additional dependancies like TPL.
So as a simplified example [that won't compile without more work...]:
//Real one does more but hey its an example...
public class ComputerEntry
{
public string ComputerName { get; private set; }
public string Status { get; set; }
public ComputerEntr(string ComputerName)
{
this.ComptuerName = ComputerName;
}
}
//...*In Window Code*...
private ObservableCollection<ComputerEntry> ComputerList { get; set; }
private BackgroundWorker RefreshWorker;
private void Init()
{
RefreshWorker = new BackgroundWorker();
RefreshWorker.WorkerReportsProgress = true;
RefreshWorker.DoWork += new DoWorkEventHandler(RefreshWorker_DoWork);
RefreshWorker.ProgressChanged += new ProgressChangedEventHandler(RefreshWorker_ProgressChanged);
}
private void Refresh()
{
RefreshWorker.RunWorkerAsync(this.ComputerList);
}
private void RefreshWorker_DoWork(object sender, DoWorkEventArgs e)
{
List<ComputerEntry> compList = e as List<ComputerEntry>;
foreach(ComputerEntry o in compList)
{
ComputerEntry updatedValue = new ComputerEntry();
updatedValue.Status = IndicatorHelpers.PingTarget(o.ComputerName);
(sender as BackgroundWorker).ReportProgress(0, value);
}
}
private void RefreshWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
ComputerEntry updatedValue = new ComputerEntry();
if(e.UserState != null)
{
updatedValue = (ComputerEntry)e.UserState;
foreach(ComputerEntry o in this.ComputerList)
{
if (o.ComputerName == updatedValue.ComputerName)
{
o.Status = updatedValue.Status;
}
}
}
}
Sorry for the jumble but its rather long with all the support code. Anyways, void Refresh() is called from a DispatcherTimer (which isn't shown), that starts RefreshWorker.RunWorkerAsync(this.ComputerList);.
I've been fighting this for a few days so I'm now to the point where I'm not actually attempting to modify the objects referenced in the ObservableCollection directly anymore. Hence the ugly looping through the ComputerList collection and setting the properties directly.
Any idea whats going on here and how I can fix it?
The observableCollection wont fire when you change properties of items which are inside of the collection (how should it even know that). The ObservableCollection fires just when the list gets changed (added, exchanged, removed).
If you want to detect the changes of the properties of the ComputerEntry the class has to Implement the INotifyPropertyChange interface (if you know MVVM, its like a lightweight MVVM pattern)
public class ComputerEntry : INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
private void RaisePropertyChanged(String propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private String _ComputerName;
public String ComputerName
{
get
{
return _ComputerName;
}
set
{
if (_ComputerName != value)
{
_ComputerName = value;
this.RaisePropertyChanged("ComputerName");
}
}
}
}
Haven't used this in a long time, but don't you need something like INotifyPropertyChanged implemented?

how to update listbox items with INotifyPropertyChanged

I have a listbox which is databound to a collection of objects.
I want to modify the way the items are displayed to show the user which one of these objects is the START object in my program.
I tried to do this the following way, but the listbox does not automatically update.
Invalidating the control also didn't work.
The only way I can find is to completely remove the databindings and add it back again. but in my case that is not desirable.
Is there another way?
class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _name;
public string Name
{
get
{
if (PersonManager.Instance.StartPerson == this)
return _name + " (Start)";
return _name;
}
set
{
_name = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Name"));
}
}
public Person(string name)
{
Name = name;
}
}
This is the class wich manages the list and the item that is the start
class PersonManager
{
public BindingList<Person> persons { get; set; }
public Person StartPerson { get; set; }
private static PersonManager _instance;
public static PersonManager Instance
{
get
{
if (_instance == null)
{
_instance = new PersonManager();
}
return _instance;
}
}
private PersonManager()
{
persons = new BindingList<Person>();
}
}
In the form I use the following code
private void button1_Click(object sender, EventArgs e)
{
PersonManager.Instance.StartPerson = (Person)listBox1.SelectedItem;
}
I'm pretty sure that the problem is that, when you do this, you're effectively making the Person.Name properties "get" accessor change the value (and act like a set accessor as far as the UI is concerned).
However, there is nothing that's updating the bindings to say that this is happening. If PropertyChanged got called when you set start, I believe this would update.
It's clunky, but the way you have it written, I believe you could add this and make it work (NOTE: I didn't test this, so it ~may~ have issues):
private void button1_Click(object sender, EventArgs e)
{
Person newStart = (Person)listBox1.SelectedItem;
if (newStart != null)
{
PersonManager.Instance.StartPerson = newStart;
newStart.Name = newStart.Name; // Dumb, but forces a PropertyChanged event so the binding updates
}
}

Categories

Resources