I wonder if it is necessary to check the
NotifyCollectionChangedAction enum of the NotifyCollectionChangedEventArgs, when subscribing to the CollectionChanged event. Every example I stumbled upon does it like this:
myCollection.CollectionChanged += (sender, eventArgs) =>
{
if (eventArgs.Action == NotifyCollectionChangedAction.Add)
{
foreach (SampleClass sampleObject in eventArgs.NewItems)
{
addAction(sampleObject);
}
}
else if (eventArgs.Action == NotifyCollectionChangedAction.Remove)
{
foreach (SampleClass sampleObject in eventArgs.OldItems)
{
removeAction(sampleObject);
}
}
// ...
};
Is it possible to ignore the NotifyCollectionChangedAction and just simplify the code like this:
myCollection.CollectionChanged += (sender, eventArgs) =>
{
eventArgs.NewItems?.OfType<SampleClass>()
.ToList()
.ForEach(addAction);
eventArgs.OldItems?.OfType<SampleClass>()
.ToList()
.ForEach(removeAction);
};
What are the downsides of this idea? Is there anything I have missed?
It depends on what you are trying to do, because those code samples are not equivalent. There are more action types than just Add and Remove. For example there is Replace action. If I do this:
myCollection[0] = new MyObject();
CollectionChanged will be fired with action type Replace, OldItems will contain replaced item (old myCollection[0]) and NewItems will contain new MyObject() item. First code sample will completely ignore this event. Second code sample will handle both items with addAction and removeAction. If you do:
myCollection.Move(0,1);
It will fire event with action Move where both OldItems and NewItems will contain moved item. First sample again will ignore it and second will perform addAction and removeAction on the same item being moved, which might lead to surprising results I guess.
Related
I have this code:
private void loadGENIOFileToolStripMenuItem_Click(object sender, EventArgs e)
{
OpenFileDialog dlgFile = new OpenFileDialog();
dlgFile.InitialDirectory = Properties.Settings.Default.PreviousPath;
dlgFile.Title = "Select GENIO file";
dlgFile.Filter = "GENIO files (*.txt)|*.txt";
dlgFile.FilterIndex = 0;
dlgFile.Multiselect = false;
if (dlgFile.ShowDialog() == DialogResult.OK)
{
Properties.Settings.Default.PreviousPath = Path.GetDirectoryName(dlgFile.FileName);
DeleteView();
m_oThreadServices.OnLoadingCompleted += (_sender, _e) =>
{
mruMenu.AddFile(dlgFile.FileName);
m_sUITInfo.dbDatabase = m_oThreadServices.GetDatabase();
CreateView();
};
m_oThreadServices.SetGenioFilePath(dlgFile.FileName);
m_oThreadServices.start();
}
}
But I am also trying to implement a MRU handler:
private void OnMruFile(int number, String filename)
{
if (File.Exists(filename))
{
Properties.Settings.Default.PreviousPath = Path.GetDirectoryName(filename);
DeleteView();
m_oThreadServices.OnLoadingCompleted += (_sender, _e) =>
{
mruMenu.SetFirstFile(number);
m_sUITInfo.dbDatabase = m_oThreadServices.GetDatabase();
CreateView();
};
m_oThreadServices.SetGenioFilePath(filename);
m_oThreadServices.start();
}
else
mruMenu.RemoveFile(number);
}
}
My m_oThreadServices.OnLoadingCompleted line of code seems to require that I use += and as a result, if I first load a file, it adds the first event handler. If I then go to use the MRU list to load a different file it ends up running two OnLoadingCompleted handlers.
I tried m_oThreadServices.OnLoadingCompleted = but it will not allow it. So what is the right way for me to intercept the event handler and not end up calling both sets of code? Am I going about it wrong?
Thank you.
You should make sure your event handlers are unsubscribed from the event source once the event is raised.
In order to do that, you have to modify a bit the anonymous handlers. For instance, this snippet:
m_oThreadServices.OnLoadingCompleted += (_sender, _e) =>
{
mruMenu.AddFile(dlgFile.FileName);
m_sUITInfo.dbDatabase = m_oThreadServices.GetDatabase();
CreateView();
};
should be like this:
EventHandler onLoadingCompleted = null;
onLoadingCompleted = (_sender, _e) =>
{
m_oThreadServices.OnLoadingCompleted -= onLoadingCompleted;
mruMenu.AddFile(dlgFile.FileName);
m_sUITInfo.dbDatabase = m_oThreadServices.GetDatabase();
CreateView();
};
m_oThreadServices.OnLoadingCompleted += onLoadingCompleted;
Same for the other.
The line
EventHandler onLoadingCompleted = null;
is needed to avoid using uninitialized variable compiler error here
m_oThreadServices.OnLoadingCompleted -= onLoadingCompleted;
You can remove a handler if it's a named function:
private void OnLoadingComplete_AddFile(_sender, _e)
{
mruMenu.AddFile(dlgFile.FileName);
m_sUITInfo.dbDatabase = m_oThreadServices.GetDatabase();
CreateView();
}
...
m_oThreadServices.OnLoadingCompleted += OnLoadingComplete_AddFile;
...
m_oThreadServices.OnLoadingCompleted -= OnLoadingComplete_AddFile;
Removing a handler that hasn't been added (or has already been removed) is a no-op, so you can just remove the "other" handler before you add one: this will ensure there is at most one handler.
So basically += is syntactic sugar for calling Combine on your event. Delegates are stored in an Invocation List, and the default behavior when an event is fired is for each delegate in the invocation list to get called in the order they were added. This is why you cannot simply set OnLoadingCompleted to one delegate with an = sign - an event stores a list of delegates, not one.
You could remove a delegate with -= (syntactic sugar for calling Remove). Perhaps you want to formally declare the previous delegate somewhere rather than passing it as a lambda. This would let you remove it when you are done with it.
There is no straightforward way of removing anonymous or unknown events from a handler. However, you can take a look at this forum posting on MSDN: https://social.msdn.microsoft.com/Forums/vstudio/en-US/45071852-3a61-4181-9a25-068a8698b8b6/how-do-i-determine-if-an-event-has-a-handler-already?forum=netfxbcl
There is some code and discussion about using reflection to remove delegates from your event handler.
It might be better though to understand exactly what you are wanting to accomplish. Perhaps there is a better way to get the end-result that you are looking for rather than rewire events.
It isn't usually good practice to remove established event code to change the behavior of the code you want to implement. It can lead to unintended consequences, and erratic behavior. If event code is defined, it is almost always best to keep it in place and design your application around it.
On the other hand, if this is code that is added by you, or in your code-base, you can remove it, if you have done the proper research to validate its removal and not cause the application to break elsewhere. The best way to do that would be to have the event code in a named function:
public void MyEventCode(object sender, EventArgs args)
{
// Do event stuff..
}
Then you can remove the event by name:
control.DoMyEvent -= MyEventCode;
Heres a quick question. I have an ObservableCollection<IItem> where IItem has a property called Id. Throughout the lifetime of an application items are added, removed and then re-added once again to this collection.
What I need is to track when items with certain id's are present in this collection. When all required dependencies are present, I need to do some initialization, if at least one of the required items is removed, then I need to do a cleanup. If that item is then re-added once again, then I need to do initialization again.
Any suggestions what RX operators to use to build such kind of a query?
Keeping track of the state of the collection will probably be somewhat tedious. Unless your collection is very big you can instead examine the collection on each change to determine if your criteria for initialization is fulfilled. Then you can use DistinctUntilChanged to get an observable that will fire when you need to perform initialization and cleanup
Here is an example:
var collection = new ObservableCollection<Int32>();
var observable = Observable
.FromEventPattern<NotifyCollectionChangedEventHandler, NotifyCollectionChangedEventArgs>(
handler => collection.CollectionChanged += handler,
handler => collection.CollectionChanged -= handler
);
You then need a predicate that determines if initialization is required (the collection "is ready"). This predicate can get expensive if your collection is big because it will be called on each change to the collection, but my assumption is that this is not a problem.
Boolean IsReady(IEnumerable<Int32> items, IReadOnlyList<Int32> itemsRequiredToBeReady) {
return items.Intersect(itemsRequiredToBeReady).Count() == itemsRequiredToBeReady.Count;
}
Then you can use DistinctUntilChanged to get notifications when the IsReady predicate changes from true to false and vice versa:
var isReadyObservable = observable
.Select(ep => IsReady((ObservableCollection<Int32>) ep.Sender, ItemsRequiredToBeReady))
.DistinctUntilChanged();
To initialize and cleanup you need two subscriptions:
isReadyObservable.Where(isReady => isReady).Subscribe(_ => Initialize());
isReadyObservable.Where(isReady => !isReady).Subscribe(_ => Cleanup());
ObservableCollection is not quite observable as it turns out, so first you must consider what is the strategy you are going to employ in this case. If it is just added and removed items than this code should suffice.
internal class Program
{
private static ObservableCollection<IItem> oc = new ObservableCollection<IItem>();
private static readonly long[] crossCheck = {1,2,3};
private static void Main(string[] args)
{
oc.CollectionChanged += oc_CollectionChanged;
oc.Add(new IItem {Id=1,Amount = 100});
oc.Add(new IItem {Id=2,Amount = 200});
oc.Add(new IItem {Id=3,Amount = 300});
oc.RemoveAt(1);
}
private static void oc_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
Console.WriteLine("{0} {1}", e.Action, oc.Sum(s1 => s1.Amount));
if (crossCheck.SequenceEqual(oc.Select(s1 => s1.Id).Intersect(crossCheck)))
Console.WriteLine("I have all elements I wanted!");
if (e.OldItems != null && e.Action.Equals(NotifyCollectionChangedAction.Remove) &&
e.OldItems.Cast<IItem>().Any(a1 => a1.Id.Equals(2))) Console.WriteLine("I've lost item two");
}
}
internal class IItem
{
public long Id { get; set; }
public int Amount { get; set; }
}
Produces:
Add 100
Add 300
Add 600
I have all elements I wanted!
Remove 400
I've lost item two
Press any key to continue . . .
Of course in your event handler you can process other conditions as needed, for example you probably want to fire some of those data dependent events just once, etc.
I have a listbox control that has items dynamically added to and manually removed from (due to 'remove item' button). When the number of items is changed, I would like to update other parts of the user interface - namely a caption that says 'You must choose some files.' and an item count caption.
How can an event handler or effectively an event handler be added to fire when the number of items is changed - e.g. an ItemAdded or ItemRemoved or ItemsChanged
Note: This is nothing to do with the user selecting items in the listbox.
Thanks very much!
You can try using a BindingList<> as your DataSource, and then you act on that list instead of your ListBox-- it will get the updates automatically from the BindingList.
The BindingList has a ListChanged event.
The ListChanged event has a ListChangedEventArgs that includes a ListChangedType enumerator:
BindingList<string> list = new BindingList<string>();
list.ListChanged += new ListChangedEventHandler(list_ListChanged);
void list_ListChanged(object sender, ListChangedEventArgs e) {
switch (e.ListChangedType){
case ListChangedType.ItemAdded:
break;
case ListChangedType.ItemChanged:
break;
case ListChangedType.ItemDeleted:
break;
case ListChangedType.ItemMoved:
break;
// some more minor ones, etc.
}
}
In the code for the "Remove Item" button, also update the other parts of the UI. This doesn't have to violate coding principles; you can do something like this:
void UpdatePartsOfTheUI() {
// Do something here
if(myListBox.Items.Count == 0) {
myLabel.Text = "You must choose some files!";
} else {
myLabel.Text = String.Empty;
}
}
/* ... */
void myButton_Click(object sender, EventArgs e) {
if(myListBox.SelectedIndex > -1) {
// Remove the item
myListBox.Items.RemoveAt(myListBox.SelectedIndex);
// Update the UI
UpdatePartsOfTheUI();
}
}
This works if you don't have many buttons that change the ListBox. If you do, just wrap the ListBox's Items.Add/Items.Insert/Items.Remove in methods that include your other code and call those from your button handlers instead.
Create a method to which you can do what you want with the ListBox Item.
for example:
my program receives data feeds from a server upon request or request for stream.
In my winform1.cs my control for adding data to a list box is as followed.
public void AddData(string data)
{
if (this.ResponseData.InvokeRequired)
BeginInvoke(new AddDataDelegate(AddData), new object[] { data });
else
{
this.ResponseData.Items.Insert(0, data);
DataDistro();
}
}
DataDistro Is what I called My Method for doing work with the new data. Also Note by inserting at index value 0, the new item will Always be on top.
If you are using winForm this is a lot easier, Also, Because the adding of an item is handled by a delegate, the main thread is still open. If your not using a method that is adding all the data to the listbox this will not work. And using the bindingsource method mentioned above would be the next best thing.
here is an example of my DataDistro method: My response strings look like this: [Q],ATQuoteLastPrice,value
[B],datetime,open,high,low,close,volume
private void DataDistro()
{
string data = ListBox.Items[0].ToString();
string[] split = data.Split(new string[] {","}, stringsplitoptions.None);
if(spit[0] == "[Q]")
{
//do some work
}
if(split[0] == "[B]")
{
//Do some work
}
}
In your case you would call your method at end of the remove item button click. I would also suggest to make a delegate, or backgroundWorker if the work is extensive. As Calling from a button click Event will be handled by UI Thread.
Everytime the AddData method is called upon, the DataDistro method is also called after data is added.
I have an attached behavior that used on a listbox, it should automatically select the first element in the list, if the list contains only one element.
The only way that I have found to hook the listbox when the list changes, is to use the listbox' itemcollections CollectionChanged event:
private static void ListenToItemsCollectionChange(ListBox listBox)
{
var collection = (INotifyCollectionChanged)listBox.Items;
collection.CollectionChanged += (sender, args) => SelectAndSetFocusToFirstElement(listBox);
}
The problem now, is that there is no way of unsubscribing from the event, which potentially leads to multiple invokations of SelectAndSetFocusToFirstelement( ).
The normal solution to this, is to not use lambdas. But then I would loose my listbox, which I need for selecting the first element.
Any suggestions on how this can be solved?
Full code
A Lambda is just a shortcut for a delegate, so you can rewrite the lambda as
NotifyCollectionChangedEventArgs collectionChangedDelegate = (sender, arg) =>
{SelectAndSetFocusToFirstElement(listBox)};
then you can add to the collection changed event
collection.CollectionChanged += collectionChangedDelegate
and remove
collection.CollectionChanged -= collectionChangedDelegate
I got a little bit confused what do You meand by "But then I would loose my listbox" ?
Maybe this solution will be sufficient
You could keep the event handler in temporary variable like that
EventHandler handler = (a, b) => { }; // You must use aproperiate delegate
collection.CollectionChanged += handler
and if You want to unsubscribe You could use -= handler
I'm having problems figuring out how to do this. I have two instances (source & target) that implement INotifyPropertyChanged and I'm tracking the PropertyChanged event for both. What I want to do is run an action any time source.PropertyChanged is raised until target.PropertyChanged is raised. I can do that just fine like this:
INotifyPropertyChanged source;
INotifyPropertyChanged target;
var sourcePropertyChanged = Observable
.FromEvent<PropertyChangedEventArgs>(source, "PropertyChanged")
.Where(x => x.EventArgs.PropertyName == sourcePropertyName);
var targetPropertyChanged = Observable
.FromEvent<PropertyChangedEventArgs>(target, "PropertyChanged")
.Where(x => x.EventArgs.PropertyName == targetPropertyName);
sourcePropertyChanged
.TakeUntil(targetPropertyChanged)
.ObserveOnDispatcher()
.Subscribe(_ => /*Raises target.PropertyChanged for targetPropertyName*/);
The problem I'm having is I want to ignore the PropertyChanged notifications caused by the actions and only stop taking values when the PropertyChanged event is raised by an external source. Is there a good way to get that to happen?
There's no built in way of doing what you're talking about. Here's a simple SkipWhen implementation that skips the next source value each time a value is received on the 'other' sequence:
public static IObservable<TSource> SkipWhen(this IObservable<TSource> source,
IObservable<TOther> other)
{
return Observable.Defer<TSource>(() =>
{
object lockObject = new object();
Stack<TOther> skipStack = new Stack<TOther>();
other.Subscribe(x => { lock(lockObject) { skipStack.Push(x); });
return source.Where(_ =>
{
lock(lockObject);
{
if (skipStack.Count > 0)
{
skipStack.Pop();
return false;
}
else
{
return true;
}
}
});
});
}
You're code would then be updated like so (see my note below):
INotifyPropertyChanged source;
INotifyPropertyChanged target;
// See the link at the bottom of my answer
var sourcePropertyChanged = source.GetPropertyChangeValues(x => x.SourceProperty);
// Unit is Rx's "void"
var targetChangedLocally = new Subject<Unit>();
var targetPropertyChanged = target.GetPropertyChangeValues(x => x.TargetProperty)
.SkipWhen(targetChangedLocally);
sourcePropertyChanged
.TakeUntil(targetPropertyChanged)
.ObserveOnDispatcher()
.Subscribe(_ =>
{
targetChangedLocally.OnNext();
/*Raises target.PropertyChanged for targetPropertyName*/
});
NB: I recently blogged about a strongly typed IObservable wrapper around INotifyPropertyChanged events; feel free to steal that code.
There's no built-in way but you could probably filter out events using the Where extension method for observable. The condition to filter on would be the sender of the event. I suppose that the sender of a target.PropertyChanged event is different than the sender of a PropertyChanged event raised by another source.
I'm not entirely sure if this is an approach you can use.
Using locks in Rx this way is fine. The lock is short lived and doesn't call out to user code.