Get a reference to the ObservableCollection that fired the change event - c#

ObservableCollection<> exposes the CollectionChanged event. From the handler of that event is there any way to get a reference to the ObservableCollection that fired the event? I would have thought the sender argument would be the ObservableCollection but it's not.
This code sample illustrates that 10 ObservableCollections all have their CollectionChanged event registered to one method. From that one method I'd like to get the reference to the ObservableCollection that changed:
internal class Program
{
private static void Main(string[] args)
{
List<ObservableCollection<int>> collections = new List<ObservableCollection<int>>();
for (int i = 0; i < 10; i++)
{
ObservableCollection<int> collection = new ObservableCollection<int>();
collection.CollectionChanged += CollectionOnCollectionChanged;
collections.Add(collection);
}
collections[5].Add(1234);
}
private static void CollectionOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
{
if (notifyCollectionChangedEventArgs.Action == NotifyCollectionChangedAction.Add)
{
// Before proceeding I need to get a reference to the ObservableCollection<int> where the change occured which fired this event.
}
}
}
Looking at the arguments passed into the event handler, I don't see a reference to the ObservableCollection so I'm assuming I can't get it.

The sender object is the instance of the ObservableCollection that fired the event. You can cast it.
private static void CollectionOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
{
if (notifyCollectionChangedEventArgs.Action == NotifyCollectionChangedAction.Add)
{
ObservableCollection<int> myCollection = sender as ObservableCollection<int>;
if(myCollection != null){
//do whatever you want
}
}
}

One other option you have is to use an in-line delegate and form a closure over the collection like this:
private static void Main(string[] args)
{
List<ObservableCollection<int>> collections = new List<ObservableCollection<int>>();
for (int i = 0; i < 10; i++)
{
ObservableCollection<int> collection = new ObservableCollection<int>();
collection.CollectionChanged += (s, e) =>
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
// Can reference `collection` directly here
}
};
collections.Add(collection);
}
collections[5].Add(1234);
}
The saves casting from the sender object (which I find messy.)
A further way to write this could would be:
private static void Main(string[] args)
{
List<ObservableCollection<int>> collections =
Enumerable
.Range(0, 10)
.Select(n => new ObservableCollection<int>())
.ToList();
collections
.ForEach(collection =>
{
collection.CollectionChanged += (s, e) =>
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
// Can reference `collection` directly here
}
};
});
collections[5].Add(1234);
}

Related

Raise an event if observable list count is not updating

I have a project in windows form application,in which i have created an event.This event is raised when i click on any page in browser(i.e. FiddlerApplication.AfterSessionComplete). Now, i am adding the data into an ObservableCollection whenever the event is firing.To check whether list count is increasing i am using CollectionChanged event of ObservableCollection.What i want is to fire an event if list count is not increasing.
ObservableCollection<Session> Sessions = new ObservableCollection<Session>();
public void FetchSession() {
#region AttachEventListeners
FiddlerApplication.AfterSessionComplete += new SessionStateHandler(FiddlerApplication_AfterSessionComplete);
Sessions.CollectionChanged += Sessions_CollectionChanged;
}
void Sessions_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
//this fires when Collection count changes
}
void FiddlerApplication_AfterSessionComplete(Session oSession)
{
Monitor.Enter(Sessions);
Sessions.Add(oSession);
Monitor.Exit(Sessions);
}
Their is nothing available in ObservableCollection class that can tell me that Collection count is not increasing. How can i do this?
You can create a subclass of ObservableCollecton<T> and add your own custom event when the Count hasn't changed between calls to CollectionChanged events. So you basically keep a private field that stores the Count of the collection and on the next CollectionChanged event you compare the current Count with the previous count. If they are equal you raise your custom event.
A possible implementation looks like this:
public delegate void CountEqual(object sender, EventArgs e);
public class ObservableCountCollection<T>: ObservableCollection<T>
{
public CountEqual CountUnchanged;
private int _previousCount;
private object locker = new object();
public ObservableCountCollection()
{
this.CollectionChanged += ObservableCountCollection_CollectionChanged;
_previousCount = 0;
}
void ObservableCountCollection_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
bool raiseEvent = false;
var unchanged = CountUnchanged;
lock(locker)
{
raiseEvent = _previousCount == this.Count;
_previousCount = this.Count;
}
if (raiseEvent && unchanged!=null)
{
unchanged(this, new EventArgs());
}
}
}
Notice that the NotifyCollectionChangedEventArgs type does have an Action property. For this collection I think you could also do
raiseEvent = (e.Action == NotifyCollectionChangedAction.Replace ||
e.Action == NotifyCollectionChangedAction.Move);
so you wouldn't need to keep the count and lock construct but I didn't test how that works if you're able to call AddRange on a collection. I leave that for any follow-up questions.

DependencyProperty Object not updating to UI

I am struggling with a dependencyproperty in a control. My dependencyproperty is an object which looks like this:
public class ChartGroupCollection : ObservableCollection<ChartGroup>, INotifyCollectionChanged
{
public void ClearDirty()
{
foreach (var grp in base.Items)
{
foreach(var run in grp.ChartRuns.Where(x=>x.IsDirty))
{
run.IsDirty = false;
}
grp.IsDirty = false;
}
}
[XmlIgnore]
public bool IsDirty //dirty flag for save prompt
{
get
{
....
}
}
}
[Serializable]
public class ChartGroup : INotifyPropertyChanged
{ ... //various properties }
The DependencyProperty is set up as here (named Tree, which is instance of a ChartGroupCollection):
public static readonly DependencyProperty TreeProperty = DependencyProperty.Register("Tree", typeof(ChartGroupCollection), typeof(ChartsControl), new PropertyMetadata(OnTreeChanged));
public ChartGroupCollection Tree
{
get { return (ChartGroupCollection)GetValue(TreeProperty); }
set { SetValue(TreeProperty, value); }
}
private static void OnTreeChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var Treee = sender as ChartsControl;
if (e.OldValue != null)
{
var coll = (INotifyCollectionChanged)e.OldValue;
coll.CollectionChanged -= Tree_CollectionChanged;
}
if (e.NewValue != null)
{
var coll = (ObservableCollection<ChartGroup>)e.NewValue;
coll.CollectionChanged += Tree_CollectionChanged;
}
}
private static void Tree_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
(sender as ChartsControl).OnTreeChanged();
}
void OnTreeChanged()
{
MessageBox.Show("Do something..."); //RefreshCharts();
}
I seem to be getting to the OnTreeChanged event only at creation of the object, but once i do other work (adding to lists inside ChartGroup or changing properties of ChartGroup objects or even deleting elements of the observablecollection it never seems to trigger a refresh event. I have tried several other methods of getting the dependencyproperty found online but no solution worked for me. I wonder if it is down to the intrinsic nature of my dependencyproperty object or an error from my side
The sender argument in your Tree_CollectionChanged handler is not the ChartsControl instance, but the collection that raised the CollectionChanged event.
The Tree_CollectionChanged method should not be static
private void Tree_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
OnTreeChanged();
}
and it should be attached and removed like this:
private static void OnTreeChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var control = sender as ChartsControl;
var oldCollection = e.OldValue as INotifyCollectionChanged;
var newCollection = e.NewValue as INotifyCollectionChanged;
if (oldCollection != null)
{
oldCollection.CollectionChanged -= control.Tree_CollectionChanged;
}
if (newCollection != null)
{
newCollection.CollectionChanged += control.Tree_CollectionChanged;
}
}
Note also that adding a CollectionChanged handler does not care for subscribing to PropertyChanged events of the collection elements. You would also have to attach or remove a PropertyChanged handler whenever an element is added to or removed from the collection. Take a look at the NotifyCollectionChangedEventArgs.Action property.

Form Validation based on both Combo boxes and TextBoxes

Currently I am using a class as follows to check if yhe TextBoxes on the form that I register to it, all have a non-blank text or not and it work fine, But now I want to also add a ComboBox to this validation so that validation should be done when none of the registered textboxes AND Combobxes on the form are blank.
So if I want to add a Combobx to this class, how should it look like? what is the best pracitce to do it?
public class InputValidator
{
public delegate void ValidationDoneDelegate(bool enable);
public event ValidationDoneDelegate ValidationDone;
public void RegisterTextBox(TextBox tb)
{
tb.TextChanged += (s, e) => this.Validate(s);
}
private void Validate(object sender)
{
var t = sender as TextBox;
if (t == null)
{
return;
}
var validationDone = ValidationDone;
if (validationDone != null)
{
validationDone(!string.IsNullOrEmpty(t.Text));
}
}
}
I have two lists setup which will hold all the TextBox and ComboBox references. When it is time to validate, we will check all of the registered controls and if ANY of them are empty, we will be invalid. I think you will also be able to see how this can easily be extended to support additional control types.
public class InputValidator
{
public delegate void ValidationDoneDelegate(bool enable);
public event ValidationDoneDelegate ValidationDone;
private List<TextBox> textBoxes = new List<TextBox>();
private List<ComboBox> comboBoxes = new List<ComboBox>();
public void RegisterTextBox(TextBox tb)
{
tb.TextChanged += (s, e) => this.Validate();
textBoxes.Add(tb);
}
public void RegisterComboBox(ComboBox cb)
{
cb.SelectedValueChanged += (s, e) => this.Validate();
comboBoxes.Add(cb);
}
private void Validate()
{
bool isValid = true;
foreach (var tb in textBoxes)
{
if (string.IsNullOrEmpty(tb.Text))
isValid = false;
}
if (isValid)
{
foreach (var cb in comboBoxes)
{
if (cb.SelectedItem == null)
isValid = false;
}
}
var validationDone = ValidationDone;
if (validationDone != null)
{
validationDone(isValid);
}
}
}
Now I'm not sure exactly what you consider to be invalid input for the ComboBox. So you may need to tweak this line to meet your needs: isValid = cb.SelectedItem != null;. I have assemed that as long as something is selected that the selection is valid.
EDIT: I had forgotten to switch the last line to validationDone(isValid);

ListViewCollection Move* methods dont fire Changed event

I am using ListViewCollection class with my dataGrid. The underlying collection is an observable collection.
Whenever i call Move methods in the collection ( which is in ViewModel), the CurrentChanged Event doesnt fire.
However when UI calls the same method on it ( i can see it in the call stack), the event does fire.
this.EmailTemplates = new ListCollectionView(templateVmList);
this.EmailTemplates.CurrentChanging += (o, e) => EmailTemplates_CurrentChanging(o, e);
this.EmailTemplates.CurrentChanged += (o, e) => { this.SelectedEmailTemplate = (EmailTemplateViewModel)this.EmailTemplates.CurrentItem; };
if (this.EmailTemplates.Count > 0)
{
if (!this.EmailTemplates.MoveCurrentToFirst())
throw new ArgumentException("Element not found in collection");
}
What should i do in code to make sure the events fire no matter who is changing the collection.
Try using CollectionViewSource.GetDefaultView instead of creating a new ListCollectionView.
This test code worked fine for me
public class LcViewModel : BaseItemsViewModel
{
public LcViewModel()
{
MoveCommand = new RelayCommand(Move);
var view = CollectionViewSource.GetDefaultView(Items);
view.CurrentChanged += (sender, args) => Debug.WriteLine("CurrentChanged");
view.CurrentChanging += (sender, args) => Debug.WriteLine("CurrentChanging");
}
public ICommand MoveCommand { get; private set; }
private void Move()
{
var view = CollectionViewSource.GetDefaultView(Items);
view.MoveCurrentToFirst();
}
}

How to add validation to PropertyGrid's CollectionEditor?

I'm using PropertyGrid to edit an object containing a collection.
Collection is edited using the CollectionEditor.
I have to make sure elements in collection are unique.
How can I add validation to CollectionEditor:
By either overloading CollectionEditor's OnFormClosing
Or adding validation for creating/editing items?
You can create your own collection editor, and hook into events on the default editor's controls. You can use these events to, say, disable the OK button. Something like:
public class MyCollectionEditor : CollectionEditor
{
private static Dictionary<CollectionForm, Button> okayButtons
= new Dictionary<CollectionForm, Button>();
// Inherit the default constructor from CollectionEditor
public MyCollectionEditor(Type type)
: base(type)
{
}
// Override this method in order to access the containing user controls
// from the default Collection Editor form or to add new ones...
protected override CollectionForm CreateCollectionForm()
{
CollectionForm collectionForm = base.CreateCollectionForm();
collectionForm.FormClosed +=
new FormClosedEventHandler(collectionForm_FormClosed);
collectionForm.Load += new EventHandler(collectionForm_Load);
if (collectionForm.Controls.Count > 0)
{
TableLayoutPanel mainPanel = collectionForm.Controls[0]
as TableLayoutPanel;
if ((mainPanel != null) && (mainPanel.Controls.Count > 7))
{
// Get a reference to the inner PropertyGrid and hook
// an event handler to it.
PropertyGrid propertyGrid = mainPanel.Controls[5]
as PropertyGrid;
if (propertyGrid != null)
{
propertyGrid.PropertyValueChanged +=
new PropertyValueChangedEventHandler(
propertyGrid_PropertyValueChanged);
}
// Also hook to the Add/Remove
TableLayoutPanel buttonPanel = mainPanel.Controls[1]
as TableLayoutPanel;
if ((buttonPanel != null) && (buttonPanel.Controls.Count > 1))
{
Button addButton = buttonPanel.Controls[0] as Button;
if (addButton != null)
{
addButton.Click += new EventHandler(addButton_Click);
}
Button removeButton = buttonPanel.Controls[1] as Button;
if (removeButton != null)
{
removeButton.Click +=
new EventHandler(removeButton_Click);
}
}
// Find the OK button, and hold onto it.
buttonPanel = mainPanel.Controls[6] as TableLayoutPanel;
if ((buttonPanel != null) && (buttonPanel.Controls.Count > 1))
{
Button okayButton = buttonPanel.Controls[0] as Button;
if (okayButton != null)
{
okayButtons[collectionForm] = okayButton;
}
}
}
}
return collectionForm;
}
private static void collectionForm_FormClosed(object sender,
FormClosedEventArgs e)
{
CollectionForm collectionForm = (CollectionForm)sender;
if (okayButtons.ContainsKey(collectionForm))
{
okayButtons.Remove(collectionForm);
}
}
private static void collectionForm_Load(object sender, EventArgs e)
{
ValidateEditValue((CollectionForm)sender);
}
private static void propertyGrid_PropertyValueChanged(object sender,
PropertyValueChangedEventArgs e)
{
ValidateEditValue((CollectionForm)sender);
}
private static void addButton_Click(object sender, EventArgs e)
{
Button addButton = (Button)sender;
ValidateEditValue((CollectionForm)addButton.Parent.Parent.Parent);
}
private static void removeButton_Click(object sender, EventArgs e)
{
Button removeButton = (Button)sender;
ValidateEditValue((CollectionForm)removeButton.Parent.Parent.Parent);
}
private static void ValidateEditValue(CollectionForm collectionForm)
{
if (okayButtons.ContainsKey(collectionForm))
{
Button okayButton = okayButtons[collectionForm];
IList<MyClass> items = collectionForm.EditValue as IList<MyClass>;
okayButton.Enabled = MyCollectionIsValid(items);
}
}
private static bool MyCollectionIsValid(IList<MyClass> items)
{
// Perform validation here.
return (items.Count == 2);
}
}
You will also need to add an Editor attribute to you collection:
class MyClass
{
[Editor(typeof(MyCollectionEditor),
typeof(System.Drawing.Design.UITypeEditor))]
List<Foo> MyCollection
{
get; set;
}
}
NOTE: I found that the value of items in removeButton_Click was not correct - so some tweaking may need to take place.
Try collectionForm.Context.Instance and typecast it to your data type this should do the trick.

Categories

Resources