Akavache and collectionChanged event - c#

(1) I am having trouble getting the CollectionChanged event of an ObservableCollection to fire whilst using Akavache, here is the code I have (simplified)
GraphCollection = new ObservableCollection<UserData>();
_cache.GetOrFetchObject(TabType.Graph.ToString(),
async () => await _dataStore.GetAllDocuments(TabType.Graph))
.Subscribe(
GraphList =>
{
GraphCollection = new ObservableCollection<UserData>(GraphList);
//GraphCollection.Shuffle();
NoGraphItems = GraphCollection.Count == 0;
});
GraphCollection.CollectionChanged += (sender, args) =>
{
NoGraphItems = GraphCollection.Count == 0;
};
Ideally, when I Add/Delete data I want to trigger the event to check to see if the collection is empty and then assign a bool property that it is or isn't empty.
I am making simple Add/Delete like so, and then calling a RefreshCache method to invalidate the data and recreate the data, not sure if this is the most efficient way to do it as well.
var graphRecord = GraphCollection.FirstOrDefault(x => x.Id == data.Id);
GraphCollection.Remove(dreamRecord);
RefreshCache(TabType.Graphs, DreamsCollection);
private void RefreshCache(TabType tabType, ObservableCollection<UserData> collection)
{
_cache.InvalidateObject<UserData>(tabType.ToString());
_cache.InsertObject(tabType.ToString(), collection);
}
(2) I am not currently setting the DateTime offset, do I need this? Can someone give me an example of how to write it out, the docs don't clearly state this.
DateTimeOffset? absoluteExpiration

your Subscribe creates a new instance of GraphCollection so the event handler that was assigned to the original instance no longer works with the new instance
try this instead
GraphList =>
{
GraphCollection = new ObservableCollection<UserData>(GraphList);
NoGraphItems = GraphCollection.Count == 0;
GraphCollection.CollectionChanged += // handler goes here
});

Related

Using System.Reactive with events

I am developing a WPF application. User can change their Address in a form. I want to raise an event when user clicks a button (to change their address) and use the UserInfoEventArgs to process some information. I am trying to use Reactive Extensions.
MS Documentation (Subject<T> constructor)
I have two doubts. How to subscribe to mySubject and also how to add the UserInfoEventArgs to the subject.
Subject<string[]> mySubject = new Subject<string[]>();
// How to subscribe to mySubject and use the method "AddressSubscriber" as the subscriber?
private void UserDataChangedHandler (object sender, UserInfoEventArgs info)
{
string[] updatedAddress = info.NewAddress.ToArray();
if (updatedAddress.Any())
{
// How to add "updatedAddress" to mySubject so that "AddressSubscriber" can use it?
}
}
private void AddressSubscriber(string[] adrs)
{
// Do some operations with adrs
}
Use the Observable.FromEventPattern method instead of creating a superfluous Subject<T>:
Observable.FromEventPattern<RoutedEventHandler, RoutedEventArgs>(
h => btn.Click += h,
h => btn.Click -= h)
.Select(_ => new UserInfoEventArgs())
.Subscribe(args => { /* do something with the args...*/ });
How to subscribe to mySubject and use the method AddressSubscriber as the subscriber?
mySubject.Subscribe(adrs => AddressSubscriber(adrs));
How to add updatedAddress to mySubject so that AddressSubscriber can use it?
mySubject.OnNext(updatedAddress);

Attach event handler to be called only once

I am currently trying to write an extension function to be able to easily attach an action that is only used once when the event is fired, then unsubscribed.
I am trying something like this:
public static void AttachOnce<TEventArgs>([NotNull] this EventHandler<TEventArgs> me, [NotNull] Action<object, TEventArgs> action)
where TEventArgs : System.EventArgs
{
var handler = me;
EventHandler<TEventArgs> wrappedAction = null;
wrappedAction = (sender, args) =>
{
action(sender, args);
handler -= wrappedAction;
};
handler += wrappedAction;
}
But ReSharper complains on the unsubscribe that handler is "Access to modified closure".
I know what this means, so I made the local variable for the closure already, but it doesn't seem to resolve it. What is failing here?
The direct hard-coded code works. Something like this:
var file = new FileSystemWatcher("path/to/file");
FileSystemEventHandler handler = null;
handler = (sender, args) =>
{
// My action code here
file.Changed -= handler;
};
file.Changed += handler;
EDIT 1 (2018-10-09 11:43 CET):
I may just have been too fast, asking a question before thoroughly thinking it through.
You can't create extension methods on Events. At all. It's just not possible in C#. So I can't even test why ReSharper is complaining and if it is right, because a call like file.Changed.AttachOnce(action) is not valid. It says "The event 'Changed' can only appear on the left hand side of += or -=".
I have found some more sources for similar requests/questions:
http://www.hardkoded.com/blog/csharp-wishlist-extension-for-events
One time generic event call?
I've been thinking about a different but much simpler approach, using a "self-detaching" inline handler which would be used like this:
obj.Event += (s, e) =>
{
Detach(obj, nameof(obj.Event));
// ..do stuff..
};
The Detach method would look like this and could be put anywhere you like (most likely a static helper class):
public static void Detach(object obj, string eventName)
{
var caller = new StackTrace().GetFrame(1).GetMethod();
var type = obj.GetType();
foreach (var field in type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic))
{
if (typeof(Delegate).IsAssignableFrom(field.FieldType))
{
var handler = (field.GetValue(obj) as Delegate)?.GetInvocationList().FirstOrDefault(m => m.Method.Equals(caller));
if (handler != null)
{
type.GetEvent(eventName).RemoveEventHandler(obj, handler);
return;
}
}
}
}
So for your example the code would look like this:
file.Changed += (s, e) =>
{
Detach(file, nameof(file.Changed));
// My action code here
};
In this case, it's okay.
ReSharper simply warns that handler is different between the time you declare and the time it is executed.
Not sure how exactly you want to design your extension method, but maybe this will get you started:
public static void OnChangedOnce(this FileSystemWatcher instance, Action<object, FileSystemEventArgs> action)
{
var file = instance;
var wrappedAction = action;
FileSystemEventHandler handler = null;
handler = (object sender, FileSystemEventArgs args) =>
{
wrappedAction(sender, args);
file.Changed -= handler;
};
file.Changed += handler;
file.EnableRaisingEvents = true; // mandatory
}
var file = new FileSystemWatcher("path/to/file");
file.OnChangedOnce((sender, args)) =>
{
// your action here
});

subscribing to different events on Property changed if different properties

I have class Step which has a collection of Task i.e List .
Step has properties Status , Time . Task also has the same properties. The values of Status and Time for Step need to be updated whenver anyone of the Tasks get their Time or Status changed.
For this , I am adding handlers to each task in the Step class.
private void AddHandlers()
{
foreach (Task tsk in Tasks)
{
tsk.PropertyChanged += HandleStatusChanged;
tsk.PropertyChanged += HandleTimeChanged;
}
}
private void HandleStatusChanged(object sender, EventArgs e)
{
UpdateStepStatusFromTasks();
}
private void HandleTimeChanged(object sender, EventArgs e)
{
UpdateStepTimesFromTasks();
}
private void UpdateStepTimesFromTasks()
{
// logic for calculating Time for Step
}
private void UpdateStepStatusFromTasks()
{
// logic for calculating Status for Step
}
Here is the Property changed event handler in Task
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
My issue is that even if I change only Task Time , it calls both the handlers Status and time as they are subscribed to the same property changed event on task.
How can i bifurcate the Property changed event based on Property called from and ensure that only the respective handlers get called and not both together ?
Sorry if this sounds silly , but I am somewhat a beginner to WPF.
Regards,
P
You need to check the parameter of the args that are passed in to get the name of the property.
First get rid of your double subscription.
private void AddHandlers()
{
foreach (Task tsk in Tasks)
{
tsk.PropertyChanged += HandlePropertyChanged;
}
}
Then use the correct signature for your event so you get the correct type of event args.
private void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
{
Now that we have PropertyChangedEventArgs instead of just EventArgs we can check the PropertyName property and call the needed method.
private void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch(e.PropertyName)
{
case "Status":
UpdateStepStatusFromTasks();
break;
case "Time":
UpdateStepTimesFromTasks();
break;
}
}
As you need more properties handled you can just add them to the switch statement.
P.S. Instead of manually subscribing to each Task you can use a BindingList<Task> as the collection that holds the tasks, you can then subscribe to the ListChanged event, that event will be raised if any of the items in the list raise PropertyChanged (be sure to enable RaiseListChangedEvents and check ListChangedEventArgs.ListChangedType is equal to ListChangedType.ItemChanged).
Every event has "accessors" add or remove. Something similar like get/set for properties. This accessors can show you the nature of the event. Every event has an InvocationList, which represents a collection of object that it will notify when the event is raised. Using this accessors you can you can have more control over what get notified and what not. When you subscribe to the event, the subscribed object get inserted into the Invocation list.
Since you are subscribing the same object for both events, you will have it triggered twice.
Only thing you can do is to check the name of the property that got updated
public void ChangedHandler(object sender, PropertyChangedEventArgs e)
{
if(e.PropertyName=="Time"){//do something}
else if (e.PropertyName == "Date") {doSomething}
}
Since you are dealing with WPF, I see a strange pattern here. You are raising the events from various methods. You should be raising the event from a property for which you want the notification to happen, which is bound to a control.
public class MyVM
{
private string _status = "status1";
public string Status
{
get
{
return _status;
}
set
{
if(_status!=value)
{
_status =value
OnPropertyChanged("Status");
}
}
}
}
You can improve on this using various things like "nameof", baseClasses, or MethorVeawers like FODY
So, the obvious thing here is that you are attaching two handlers to the `` event so everything is being processed twice. It needs be only subscribed to once.
But rather than making a lot of complicated methods with code bouncing around all over the place, I prefer to using Microsoft's Reactive Extensions (Rx) - NuGet "Rx-Main" - to do anything with events. After learning a few basic operators it really makes working with events much much easier.
Rx is, in overly simplistic terms, LINQ for Events. It lets you work with queries to handle events rather than enumerables. It creates observables.
First, I would create this observable:
var tpns = // IObservable<{anonymous}>
from t in Tasks.ToObservable()
from ep in Observable.FromEventPattern<
PropertyChangedEventHandler, PropertyChangedEventArgs>(
h => t.PropertyChanged += h,
h => t.PropertyChanged -= h)
select new { Task = t, ep.EventArgs.PropertyName };
This query basically takes the list of Tasks and converts all of the PropertyChanged events of each task in a single observable that returns each Task when that task had a property change and the PropertyName of the task that changed.
Now it's easy to create a couple more observables that filter by PropertyName and return the Task:
IObservable<Task> statusChanges =
from tpn in tpns
where tpn.PropertyName == "Status"
select tpn.Task;
IObservable<Task> timeChanges =
from tpn in tpns
where tpn.PropertyName == "Time"
select tpn.Task;
Those should be really simple to understand.
Now subscribe to each (basically like attaching to events):
IDisposable statusSubscription =
statusChanges
.Subscribe(task => UpdateStepStatusFromTasks());
IDisposable timeSubscription =
timeChanges
.Subscribe(task => UpdateStepTimesFromTasks());
You'll notice each subscription is an IDisposable. Instead of detaching from events using the -= operator you simply call .Dispose() on the subscription and all of the underlying event handlers are detached for you.
Now I would recommend changing the AddHandlers method to return an IDisposable. Then the code that calls AddHandlers can dispose of the handlers - if needed - to make sure you can clean up before exiting.
So the complete code would look like this:
private IDisposable AddHandlers()
{
var tpns = // IObservable<{anonymous}>
from t in Tasks.ToObservable()
from ep in Observable.FromEventPattern<
PropertyChangedEventHandler, PropertyChangedEventArgs>(
h => t.PropertyChanged += h,
h => t.PropertyChanged -= h)
select new { Task = t, ep.EventArgs.PropertyName };
IObservable<Task> statusChanges =
from tpn in tpns
where tpn.PropertyName == "Status"
select tpn.Task;
IObservable<Task> timeChanges =
from tpn in tpns
where tpn.PropertyName == "Time"
select tpn.Task;
IDisposable statusSubscription =
statusChanges
.Subscribe(task => UpdateStepStatusFromTasks());
IDisposable timeSubscription =
timeChanges
.Subscribe(task => UpdateStepTimesFromTasks());
return new CompositeDisposable(statusSubscription, timeSubscription);
}
The only new thing there is the CompositeDisposable which joins the two IDiposable subscriptions into a single IDisposable.
The very nice thing about this approach is that most of the code now sits nicely in a single method. It makes it easy to understand and maintain when done this way - at least after a small learning curve. :-)

Mixed use of Task and Dispatcher halts the task

Explanation
I'm creating my own search control in WPF. This control is a UserControl that contains an area with search parameters (eg.: search on specific ID, name,...) and a GridView that shows the result.
In my control I have a dependency property of type ICommand where I bind the command to execute my search query.
public static readonly DependencyProperty SearchCommandProperty =
DependencyProperty.Register("SearchCommand", typeof(ICommand), typeof(SearchControl));
Use of my control in a certain window:
<customControls:SearchControl SearchCommand="{Binding SearchItemsCommand}"
SearchResult="{Binding SearchResult}" />
SearchItemsCommand is a Command in my ViewModel where I can find my search query.
In this command you can find my query to retrieve the result.
SearchResult is my ICollection that contains the result of the search query.
Code of the commands
Viewmodel
private DelegateCommand searchItemsCommand;
public DelegateCommand SearchItemsCommand
{
get
{
if (this.searchItemsCommand== null)
this.searchItemsCommand= new DelegateCommand(this.SearchItemsCommandExecuted);
return this.searchItemsCommand;
}
}
private ICollection<VoucherOverviewModel> voucherResults;
private void SearchItemsCommandExecuted()
{
using (DbContext context = new DbContext())
{
var query = (from v in context.Vouchers
join vt in context.VoucherTransactions on new
{
voucherID = v.VoucherID,
type = VoucherTransactionType.Out
} equals new
{
voucherID = vt.VoucherID,
type = vt.Type
}
join vtype in context.VoucherTypes on v.VoucherTypeID equals vtype.VoucherTypeID
join c in context.Customers on vt.CustomerID equals c.CustomerID
join pos in context.PointOfSales on v.PointOfSaleID equals pos.PointOfSaleID
select new VoucherOverviewModel()
{
PointOfSaleID = v.PointOfSaleID,
PointOfSaleName = pos.Name,
VoucherID = v.VoucherID,
VoucherCode = v.Code,
VoucherTypeID = v.VoucherTypeID,
VoucherTypeDescription = vtype.Code,
CustomerID = c.CustomerID,
CustomerName = c.Name,
Value = vt.Value,
UsedValue = context.VoucherTransactions
.Where(x => x.VoucherID == v.VoucherID &&
x.Type == VoucherTransactionType.In)
.Sum(x => x.Value),
CreateDate = vt.Date,
ValidFrom = v.ValidFrom,
ValidUntil = v.ValidUntil,
ParentVoucherID = v.ParentVoucherID,
Comment = v.Comment,
});
foreach (ISearchParameter searchParameter in this.SearchParameters)
{
if (!searchParameter.Value.IsNullOrDefault())
{
switch ((FilterVoucherParameterKey)searchParameter.Key)
{
case FilterVoucherParameterKey.CustomerID:
query = query.Where(x => x.CustomerID == (int)searchParameter.Value);
break;
case FilterVoucherParameterKey.VoucherID:
query = query.Where(x => x.VoucherCode.Contains((string)searchParameter.Value));
break;
case FilterVoucherParameterKey.PointOfSale:
query = query.Where(x => x.PointOfSaleID == (byte)searchParameter.Value);
break;
case FilterVoucherParameterKey.Type:
query = query.Where(x => x.VoucherTypeID == (byte)searchParameter.Value);
break;
}
}
}
this.voucherResults = query.ToList();
}
}
Custom control
public static readonly DependencyProperty SearchCommandProperty =
DependencyProperty.Register("SearchCommand", typeof(ICommand), typeof(SearchControl));
public ICommand SearchCommand
{
get
{
return (ICommand)this.GetValue(SearchCommandProperty);
}
set
{
this.SetValue(SearchCommandProperty, value);
}
}
This is my dependency property so that I can bind the SearchItemsCommand to my Custom control.
Then I have another ICommand to execute the binded command and show the loading element in my custom control.
This LocalSearchCommand will be executed when you click on a button.
private DelegateCommand localSearchCommand;
public DelegateCommand LocalSearchCommand
{
get
{
if (this.localSearchCommand == null)
this.localSearchCommand = new DelegateCommand(this.LocalSearchCommandExecuted);
return this.localSearchCommand;
}
}
private void LocalSearchCommandExecuted()
{
loadingElement.Visible = true;
Task.Factory.StartNew(() =>
{
this.Dispatcher.Invoke((Action)(() => this.SearchCommand.Execute(null)));
})
.ContinueWith(t =>
{
if (t.IsCompleted)
{
t.Dispose();
}
});
}
The problem
I want to show a Loading element when the query is executing to interact with the user. To show this element, I have to set it visible.
The problem now is, when I set it visible and want to execute the search command, my whole UI freezes. After the result is fetched from the database and generated in the GridView, then, and only then, it shows my loading element. I do understand why this happens and I tried to solve it using a Task.
loadingElement.Visible = true;
Task.Factory.StartNew(() =>
{
this.Dispatcher.Invoke((Action)(() => this.SearchCommand.Execute(null)));
})
.ContinueWith(t =>
{
if (t.IsCompleted)
{
t.Dispose();
}
});
I have to use the Dispatcher in my Task to execute the SearchCommand, because it is owned by the UI-thread.
But because of the use of the Dispatcher class, I have the same problem as before. My loading element is only shown when the query is already executed, because the Dispatcher executes the search command back on the UI-thread.
Without the use of the Dispatcher class, it gives me the following error:
The calling thread cannot access this object because a different thread owns it.
I get this error on the line:
return (ICommand)this.GetValue(SearchCommandProperty);
Even with an empty SearchItemsCommandExecuted method, the error occurs.
What I already tried
I tried setting the TaskScheduler of the Task to
TaskScheduler.FromCurrentSynchronizationContext()
I used a lot of combinations of BeginInvoke and Invoke.
I tried to set the Visibility of the loading element in the Task.
But none of the above did work.
How can I solve my problem, so that the loading element is shown when the query is executing. Did I miss something obvious?
Thanks in advance!
Loetn
The problem is that you are creating a new Task with a ThreadPool thread, but using Dispatcher.Invoke, which runs your command on the UI Thread, hence why your UI is freezing.
You need to offload your SearchCommand work to a background thread, then update your UI with a Continuation on the UI Thread (Dont try updating your UI inside SearchCommand):
then, it shows my loading element. I do understand why this happens and I tried to solve it using a Task.
loadingElement.Visible = true;
Task.Factory.StartNew(() =>
{
return this.SearchCommand.Execute(null);
})
.ContinueWith(t =>
{
MyUIElement = t.Result; // Update your UI here.
}, TaskScheduler.FromCurrentSynchronizationContext());
Edit: Did not catch your binding of the first command to the second. So the following will proably not work. Looking into it...
EDIT 2: I assumed you want to start the background operation from your viewmodel. In the moment i can't think of another way than to make your loadingItem.Visible property a dependency property, move the background operation to your viewmodel, assign a property which is bound to loadingItem.Visible from there and remove the asynchronus stuff from your usercontrol.
You want to start your query on the background thread and assign the result to your ui thread:
private void LocalSearchCommandExecuted(object obj)
{
//can also be your loadingItem.
VisibleElement.Visibility = Visibility.Collapsed;
//get the ui context
var scheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() =>
{
//do your query on the background thread
LongRunningOperation();
})
//this happens on the ui thread because of the second parameter scheduler
.ContinueWith(t =>
{
if (t.IsCompleted)
{
VisibleElement.Visibility = Visibility.Visible;
//assign the result from the LongRunningOperation to your ui list
_list = new List<string>(_tempList);
//if you need to...
RaisePropertyChanged("SearchResults");
}
}, scheduler );
}
private void LongRunningOperation()
{
//assign your result to a temporary collection
//if you do not do that you will get an exception: An ItemsControl is inconsistent with its items source
_tempList = new List<string>();
for (int i = 0; i < 100; i++)
{
_tempList.Add("Test" + i);
Thread.Sleep(10);
}
}
I solved my problem with the help of this blog.
What I had to do is to edit the getter of my Dependency property SearchCommand so that it uses the Dispatcher.
public ICommand SearchCommand
{
get
{
return (ICommand)this.Dispatcher.Invoke(
System.Windows.Threading.DispatcherPriority.Background,
(DispatcherOperationCallback)delegate { return this.GetValue(SearchCommandProperty); },
SearchCommandProperty);
// Instead of this
// return this.GetValue(SearchCommandProperty);
}
set
{
this.SetValue(SearchCommandProperty, value);
}
}
And this is my Command method:
private void LocalSearchCommandExecuted()
{
this.loadingElement.Visible = true;
Task.Factory.StartNew(() =>
{
this.SearchCommand.Execute(null);
})
.ContinueWith(t =>
{
if (t.IsCompleted)
{
this.Dispatcher.BeginInvoke((Action)(() => this.loadingElement.Visible= false));
t.Dispose();
}
});
}
Thanks for all the help!

How use Observable.ToAsync with IEnumerable

I have run into some problem with Rx. I need that after processing each item in a new thread send result to main thread. I did it with other way. How can i solve this task with Rx? Here is code:
Observable.ToAsync<string, IEnumerable<UserState>>(Run)(path)
.ObserveOnDispatcher<IEnumerable<UserState>>()
.Subscribe(
(o) =>
{ // need to run in Main Thread
foreach (var item in o)
{
WriteLog(item.StatusCode, item.Url);
}
},
(ex) =>{ },
() =>{ } );
// need to run in New Thread
private IEnumerable<UserState> Run(string sitemap)
{
....
foreach (var url in urls)
{
var result = new UserState
{
Url = url.Value,
TotalItems = urls.Count
};
....
yield return result;
}
}
You want to generate the IEnumerable on some other background thread and then Process each users object in this enumerable on Main UI thread, if this understanding is correct then you can do something like this:
var users = Run(path); //NOTE: This doesn't execute your run method yet, Run will only execute when you start enumerating the users values
users.ToObservable(System.Concurrency.Scheduler.ThreadPool) //The enumerator will be scheduled on separate thread
.ObserveOn(frm) //Observe on UI thread of win form
.Subscribe(s => {}) //This will run in UI thread for each user object
It would be good if you could describe the issue that you have.
What I can tell you at the moment is that you have a couple of potential issues.
First, using ObserveOnDispatcher is meant to work with the System.Windows.Threading.Dispatcher (usually created with WPF). If you're running your code outside of WPF it effectively means "the current thread" and this could lead to your subscription not being able to run if the current thread is busy. In other words, you might create a dead-lock.
I ran your code in both WPF and LINQPad and it worked fine in WPF, but dead-locked in LINQPad. If I observed on another thread then it worked fine in LINQPad and failed in WPF.
Secondly, you're turning an iterator method into an async observable and that won't work as you expect. An iterator doesn't actually run any code until you actually iterate through the enumerable. Essentially you return from Run almost instantly and you only execute the body of you Run method in the Subscribe code - and that's the wrong thread!
What you need to do is force immediate execution of the enumerable - at the very least - change your code to look like this:
private UserState[] Run(string sitemap)
{
...
Func</* url type */, UserState> create = url =>
{
var result = new UserState
{
Url = url.Value,
TotalItems = urls.Count
};
....
return result;
};
return (from url in urls select create(url)).ToArray();
}
Your main code needs to have a little clean up:
Observable.ToAsync<string, UserState[]>(Run)(path)
.ObserveOnDispatcher()
.Subscribe(o =>
{
foreach (var item in o)
{
WriteLog(item.StatusCode, item.Url);
}
});
Let me know if any of this helps.
EDIT: Added sample FromEventPattern code as per OP request in the comments.
Here's an example Windows Forms use of FromEventPattern. The first part creates a way to clean up subscriptions when the form closes.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
// Create a collection of IDisposable
// to allow clean-up of subscriptions
var subscriptions =
new System.Reactive.Disposables.CompositeDisposable();
var formClosings = Observable
.FromEventPattern<FormClosingEventHandler, FormClosingEventArgs>(
h => this.FormClosing += h,
h => this.FormClosing -= h);
// Add a subscription that cleans up subscriptions
// when the form closes
subscriptions.Add(
formClosings
.Subscribe(ea => subscriptions.Dispose()));
This next part watches for mouse drags on a picture box and creates messages to let the user know how far they've dragged.
var pictureBox1MouseDowns = Observable
.FromEventPattern<MouseEventHandler, MouseEventArgs>(
h => pictureBox1.MouseDown += h,
h => pictureBox1.MouseDown -= h);
var pictureBox1MouseMoves = Observable
.FromEventPattern<MouseEventHandler, MouseEventArgs>(
h => pictureBox1.MouseMove += h,
h => pictureBox1.MouseMove -= h);
var pictureBox1MouseUps = Observable
.FromEventPattern<MouseEventHandler, MouseEventArgs>(
h => pictureBox1.MouseUp += h,
h => pictureBox1.MouseUp -= h);
var pictureBox1MouseDrags =
from md in pictureBox1MouseDowns
from mm in pictureBox1MouseMoves.TakeUntil(pictureBox1MouseUps)
let dx = mm.EventArgs.Location.X - md.EventArgs.Location.X
let dy = mm.EventArgs.Location.Y - md.EventArgs.Location.Y
select new Point(dx, dy);
var pictureBox1MouseDragMessages =
from md in pictureBox1MouseDrags
let f = "You've dragged ({0}, {1}) from your starting point"
select String.Format(f, md.X, md.Y);
The next part tracks the number of times a button is clicked and creates a messages to display to the user.
var button1ClickCount = 0;
var button1Clicks = Observable
.FromEventPattern(
h => button1.Click += h,
h => button1.Click -= h);
var button1ClickCounts =
from c in button1Clicks
select ++button1ClickCount;
var button1ClickMessages =
from cc in button1ClickCounts
let f = "You clicked the button {0} time{1}"
select String.Format(f, cc, cc == 1 ? "" : "s");
Finally the two message obervables are merged together and are subscribed to, placing the message in a label.
var messages = pictureBox1MouseDragMessages
.Merge(button1ClickMessages);
// Add a subscription to display the
// merged messages in the label
subscriptions.Add(
messages
.Subscribe(m => label1.Text = m));
}
}
Keep in mind that all of this resides in the form's constructor and no module level fields or properties are used and all the events handlers are removed when the form closes. Very neat stuff.

Categories

Resources