Given the benefits of composable events as offered by the Reactive Extensions (Rx) framework, I'm wondering whether my classes should stop pushing .NET events, and instead expose Rx observables.
For instance, take the following class using standard .NET events:
public class Foo
{
private int progress;
public event EventHandler ProgressChanged;
public int Progress
{
get { return this.progress; }
set
{
if (this.progress != value)
{
this.progress = value;
// Raise the event while checking for no subscribers and preventing unsubscription race condition.
var progressChanged = this.ProgressChanged;
if (progressChanged != null)
{
progressChanged(this, new EventArgs());
}
}
}
}
}
Lot of monotonous plumbing.
This class could instead use some sort of observable to replace this functionality:
public class Foo
{
public Foo()
{
this.Progress = some new observable;
}
public IObservable<int> Progress { get; private set; }
}
Far less plumbing. Intention is no longer obscured by plumbing details. This seems beneficial.
My questions for you fine StackOverflow folks are:
Would it good/worthwhile to replace standard .NET events with IObservable<T> values?
If I were to use an observable, what kind would I use here? Obviously I need to push values to it (e.g. Progress.UpdateValue(...) or something).
For #2, the most straightforward way is via a Subject:
Subject<int> _Progress;
IObservable<int> Progress {
get { return _Progress; }
}
private void setProgress(int new_value) {
_Progress.OnNext(new_value);
}
private void wereDoneWithWorking() {
_Progress.OnCompleted();
}
private void somethingBadHappened(Exception ex) {
_Progress.OnError(ex);
}
With this, now your "Progress" can not only notify when the progress has changed, but when the operation has completed, and whether it was successful. Keep in mind though, that once an IObservable has completed either via OnCompleted or OnError, it's "dead" - you can't post anything further to it.
I don't recommend managing your own subscriber list when there are built in subjects that can do that for you. It also removes the need for carrying your own mutable copy of T.
Below is my (commentless) version of your solution:
public class Observable<T> : IObservable<T>, INotifyPropertyChanged
{
private readonly BehaviorSubject<T> values;
private PropertyChangedEventHandler propertyChanged;
public Observable() : this(default(T))
{
}
public Observable(T initalValue)
{
this.values = new BehaviorSubject<T>(initalValue);
values.DistinctUntilChanged().Subscribe(FirePropertyChanged);
}
public T Value
{
get { return this.values.First(); }
set { values.OnNext(value); }
}
private void FirePropertyChanged(T value)
{
var handler = this.propertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs("Value"));
}
public override string ToString()
{
return value != null ? value.ToString() : "Observable<" + typeof(T).Name + "> with null value.";
}
public static implicit operator T(Observable<T> input)
{
return input.Value;
}
public IDisposable Subscribe(IObserver<T> observer)
{
return values.Subscribe(observer);
}
event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
{
add { this.propertyChanged += value; }
remove { this.propertyChanged -= value; }
}
}
I'll keep it short and simple:
yes
BehaviorSubject
:)
Ok guys, seeing as how I think it's at least worth a shot to try this, and seeing as how RX's Subject<T> isn't quite what I'm looking for, I've created a new observable that fits my needs:
Implements IObservable<T>
Implements INotifyPropertyChange to work with WPF/Silverlight binding.
Provides easy get/set semantics.
I call the class Observable<T>.
Declaration:
/// <summary>
/// Represents a value whose changes can be observed.
/// </summary>
/// <typeparam name="T">The type of value.</typeparam>
public class Observable<T> : IObservable<T>, INotifyPropertyChanged
{
private T value;
private readonly List<AnonymousObserver> observers = new List<AnonymousObserver>(2);
private PropertyChangedEventHandler propertyChanged;
/// <summary>
/// Constructs a new observable with a default value.
/// </summary>
public Observable()
{
}
public Observable(T initalValue)
{
this.value = initialValue;
}
/// <summary>
/// Gets the underlying value of the observable.
/// </summary>
public T Value
{
get { return this.value; }
set
{
var valueHasChanged = !EqualityComparer<T>.Default.Equals(this.value, value);
this.value = value;
// Notify the observers of the value.
this.observers
.Select(o => o.Observer)
.Where(o => o != null)
.Do(o => o.OnNext(value))
.Run();
// For INotifyPropertyChange support, useful in WPF and Silverlight.
if (valueHasChanged && propertyChanged != null)
{
propertyChanged(this, new PropertyChangedEventArgs("Value"));
}
}
}
/// <summary>
/// Converts the observable to a string. If the value isn't null, this will return
/// the value string.
/// </summary>
/// <returns>The value .ToString'd, or the default string value of the observable class.</returns>
public override string ToString()
{
return value != null ? value.ToString() : "Observable<" + typeof(T).Name + "> with null value.";
}
/// <summary>
/// Implicitly converts an Observable to its underlying value.
/// </summary>
/// <param name="input">The observable.</param>
/// <returns>The observable's value.</returns>
public static implicit operator T(Observable<T> input)
{
return input.Value;
}
/// <summary>
/// Subscribes to changes in the observable.
/// </summary>
/// <param name="observer">The subscriber.</param>
/// <returns>A disposable object. When disposed, the observer will stop receiving events.</returns>
public IDisposable Subscribe(IObserver<T> observer)
{
var disposableObserver = new AnonymousObserver(observer);
this.observers.Add(disposableObserver);
return disposableObserver;
}
event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
{
add { this.propertyChanged += value; }
remove { this.propertyChanged -= value; }
}
class AnonymousObserver : IDisposable
{
public IObserver<T> Observer { get; private set; }
public AnonymousObserver(IObserver<T> observer)
{
this.Observer = observer;
}
public void Dispose()
{
this.Observer = null;
}
}
}
Usage:
Consuming is nice and easy. No plumbing!
public class Foo
{
public Foo()
{
Progress = new Observable<T>();
}
public Observable<T> Progress { get; private set; }
}
Usage is simple:
// Getting the value works just like normal, thanks to implicit conversion.
int someValue = foo.Progress;
// Setting the value is easy, too:
foo.Progress.Value = 42;
You can databind to it in WPF or Silverlight, just bind to the Value property.
<ProgressBar Value={Binding Progress.Value} />
Most importantly, you can compose, filter, project, and do all the sexy things RX lets you do with IObservables:
Filtering events:
foo.Progress
.Where(val => val == 100)
.Subscribe(_ => MyProgressFinishedHandler());
Automatic unsubscribe after N invocations:
foo.Progress
.Take(1)
.Subscribe(_ => OnProgressChangedOnce());
Composing events:
// Pretend we have an IObservable<bool> called IsClosed:
foo.Progress
.TakeUntil(IsClosed.Where(v => v == true))
.Subscribe(_ => ProgressChangedWithWindowOpened());
Nifty stuff!
Apart from the fact that your existing eventing code could be terser:
public event EventHandler ProgressChanged = delegate {};
...
set {
...
// no need for null check anymore
ProgressChanged(this, new EventArgs());
}
I think by switching to Observable<int> you are just moving complexity from the callee to the caller. What if I just want to read the int?
-Oisin
Related
I have this code.
<ParentComponent>
<ChildComponet>
#renderFragment
</ChildComponent>
<ChildComponetn>
<GridComponent Data="#dataList"/>
</ChildComponent>
</ParentComponent>
where #renderFragment is dynamically render componet and Grid componet is list of some data with actions like "add new", "edit record", "delete".
If we click "add new", form for add new record is opened dynamically in #renderFragment and we want to refresh grid data after submit form but we don't know how to share some data between two child components. Same is about edit form, when some record is edited, we need to refresh grid component to show edited data.
If need more code and data about it please comment.
You may define a class service that implements the State pattern and the Notifier pattern to handle the state of your objects, pass state to objects, and notify subscriber objects of changes.
Here's a simplified example of such service, which enables a parent component to communicate with his children.
NotifierService.cs
public class NotifierService
{
private readonly List<string> values = new List<string>();
public IReadOnlyList<string> ValuesList => values;
public NotifierService()
{
}
public async Task AddTolist(string value)
{
values.Add(value);
await Notify?.Invoke();
}
public event Func<Task> Notify;
}
Child1.razor
#inject NotifierService Notifier
#implements IDisposable
<div>User puts in something</div>
<input type="text" #bind="#value" />
<button #onclick="#AddValue">Add value</button>
#foreach (var value in Notifier.ValuesList)
{
<p>#value</p>
}
#code {
private string value { get; set; }
public async Task AddValue()
{
await Notifier.AddTolist(value);
}
public async Task OnNotify()
{
await InvokeAsync(() =>
{
StateHasChanged();
});
}
protected override void OnInitialized()
{
Notifier.Notify += OnNotify;
}
public void Dispose()
{
Notifier.Notify -= OnNotify;
}
}
Child2.razor
#inject NotifierService Notifier
<div>Displays Value from service and lets user put in new value</div>
<input type="text" #bind="#value" />
<button #onclick="#AddValue">Set Value</button>
#code {
private string value { get; set; }
public async Task AddValue()
{
await Notifier.AddTolist(value);
}
}
Usage
#page "/"
<p>
<Child1></Child1>
</p>
<p></p>
<p>
<Child2></Child2>
</p>
Startup.ConfigureServices
services.AddScoped<NotifierService>();
Hope this helps...
There are a few ways to do it, I just learned a really cool way using a Singleton class.
I have this component I use to send a message to other users in my chat called SubscriptionService, but you can use any class.
Add this inject to both of your components:
#inject Services.SubscriberService SubscriberService
#region using statements
using DataJuggler.UltimateHelper.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Transactions;
#endregion
namespace BlazorChat.Services
{
#region class SubscriberService
/// <summary>
/// This class is used to subscribe to services, so other windows get a notification a new message
/// came in.
/// </summary>
public class SubscriberService
{
#region Private Variables
private int count;
private Guid serverId;
private List<SubscriberCallback> subscribers;
#endregion
#region Constructor
/// <summary>
/// Create a new instance of a 'SubscriberService' object.
/// </summary>
public SubscriberService()
{
// Create a new Guid
this.ServerId = Guid.NewGuid();
Subscribers = new List<SubscriberCallback>();
}
#endregion
#region Methods
#region BroadcastMessage(SubscriberMessage message)
/// <summary>
/// This method Broadcasts a Message to everyone that ins't blocked.
/// Note To Self: Add Blocked Feature
/// </summary>
public void BroadcastMessage(SubscriberMessage message)
{
// if the value for HasSubscribers is true
if ((HasSubscribers) && (NullHelper.Exists(message)))
{
// Iterate the collection of SubscriberCallback objects
foreach (SubscriberCallback subscriber in Subscribers)
{
// if the Callback exists
if ((subscriber.HasCallback) && (subscriber.Id != message.FromId))
{
// to do: Add if not blocked
// send the message
subscriber.Callback(message);
}
}
}
}
#endregion
#region GetSubscriberNames()
/// <summary>
/// This method returns a list of Subscriber Names ()
/// </summary>
public List<string> GetSubscriberNames()
{
// initial value
List<string> subscriberNames = null;
// if the value for HasSubscribers is true
if (HasSubscribers)
{
// create the return value
subscriberNames = new List<string>();
// Get the SubscriberNamesl in alphabetical order
List<SubscriberCallback> sortedNames = Subscribers.OrderBy(x => x.Name).ToList();
// Iterate the collection of SubscriberService objects
foreach (SubscriberCallback subscriber in sortedNames)
{
// Add this name
subscriberNames.Add(subscriber.Name);
}
}
// return value
return subscriberNames;
}
#endregion
#region Subscribe(string subscriberName)
/// <summary>
/// method returns a message with their id
/// </summary>
public SubscriberMessage Subscribe(SubscriberCallback subscriber)
{
// initial value
SubscriberMessage message = null;
// If the subscriber object exists
if ((NullHelper.Exists(subscriber)) && (HasSubscribers))
{
// Add this item
Subscribers.Add(subscriber);
// return a test message for now
message = new SubscriberMessage();
// set the message return properties
message.FromName = "Subscriber Service";
message.FromId = ServerId;
message.ToName = subscriber.Name;
message.ToId = subscriber.Id;
message.Data = Subscribers.Count.ToString();
message.Text = "Subscribed";
}
// return value
return message;
}
#endregion
#region Unsubscribe(Guid id)
/// <summary>
/// This method Unsubscribe
/// </summary>
public void Unsubscribe(Guid id)
{
// if the value for HasSubscribers is true
if ((HasSubscribers) && (Subscribers.Count > 0))
{
// attempt to find this callback
SubscriberCallback callback = Subscribers.FirstOrDefault(x => x.Id == id);
// If the callback object exists
if (NullHelper.Exists(callback))
{
// Remove this item
Subscribers.Remove(callback);
// create a new message
SubscriberMessage message = new SubscriberMessage();
// set the message return properties
message.FromId = ServerId;
message.FromName = "Subscriber Service";
message.Text = callback.Name + " has left the conversation.";
message.ToId = Guid.Empty;
message.ToName = "Room";
// Broadcast the message to everyone
BroadcastMessage(message);
}
}
}
#endregion
#endregion
#region Properties
#region Count
/// <summary>
/// This property gets or sets the value for 'Count'.
/// </summary>
public int Count
{
get { return count; }
set { count = value; }
}
#endregion
#region HasSubscribers
/// <summary>
/// This property returns true if this object has a 'Subscribers'.
/// </summary>
public bool HasSubscribers
{
get
{
// initial value
bool hasSubscribers = (this.Subscribers != null);
// return value
return hasSubscribers;
}
}
#endregion
#region ServerId
/// <summary>
/// This property gets or sets the value for 'ServerId'.
/// </summary>
public Guid ServerId
{
get { return serverId; }
set { serverId = value; }
}
#endregion
#region Subscribers
/// <summary>
/// This property gets or sets the value for 'Subscribers'.
/// </summary>
public List<SubscriberCallback> Subscribers
{
get { return subscribers; }
set { subscribers = value; }
}
#endregion
#endregion
}
#endregion
}
For my chat application, I want it available to all instances, so in your configure services method of Startup.cs, add a Sington:
services.AddSingleton<SubscriberService>();
To make it only available to this browser instance:
services.AddScoped(SubscriberService);
Now from both components you can call a method or get to properties on your injected class:
SubscriptionService.GetSubscribers();
Or if you prefer interfaces, I wrote a blog post about that and I don't want to duplicate the text:
https://datajugglerblazor.blogspot.com/2020/01/how-to-use-interfaces-to-communicate.html
The inject way is pretty cool though, as your entire application can communicate with other user instances for chat.
I am trying to implement a collection view that allows limiting the amount of filtered out items.
I have come up with this implementation
public class LimitCollectionView : ListCollectionView
{
#region CONSTRUCTOR
public LimitCollectionView(IList source) : base(source)
{
}
#endregion
#region FIELDS
private int MAX_ITEMS = 0;
#endregion
#region PROPERTIES
/// <summary>
/// Gets or sets maximum amount of view items.
/// </summary>
public int MaxItems
{
get { return MAX_ITEMS; }
set
{
MAX_ITEMS = value;
OnPropertyChanged(new ComponentModel.PropertyChangedEventArgs(nameof(MaxItems)));
RefreshOrDefer();
}
}
#endregion
#region OVERRIDES
/// <summary>
/// Gets the estimated number of records.
/// </summary>
public override int Count
{
get
{
if (IsRefreshDeferred)
return 0;
if (MaxItems <= 0)
return base.Count;
var baseCount = base.Count;
if (MaxItems > baseCount)
return baseCount;
return Math.Min(MaxItems,baseCount);
}
}
public override bool PassesFilter(object item)
{
if (!base.PassesFilter(item))
return false;
if(item!=null)
{
return IndexOf(item) < MaxItems;
}
return true;
}
#endregion
}
In most cases the implementation works as intended BUT at some cases i get
System.InvalidOperationException: An ItemsControl is inconsistent with its items source.
So i suppose there is inconsistency with what ItemsControl expects and what my CollectionView reporting.
So the question is have i omitted something in my implementation ? Is it even possible to have an implementation that works ?
I know i could filter out items manually and recreate the collection on change but that beats the the purpose of CollectionView.
I have an observable collection in the view model that implements Bindable Base as follows Please have a look at the MoveUp and MoveDown methods where they are bound to two buttons in the view. When ever up button is pressed I want the selected row in the datagrid to move one step up in the based on the sequence column in the database and for down one step down.. Both the methods works PERFECTLY. Problem is the changes get shown in the datagrid only when the entire view is refreshed. My requirement is when the button is clicked I want the view to be automatically refreshed. I apologize for such long code. Please Help!!!!. I have some cs code as well for the both up and down buttons specified below the viewmodel. Only pointers in the code that needs to be emphasized is the ObservableCollection JobEntities, MoveUp and MoveDown commands.
ViewModel.cs:
public class JobConfigurationViewModel : BindableBase
{
public JobConfigurationLogic JobConfigurationLogic =
new JobConfigurationLogic(new JobConfigurationResultsRepository());
public SrcDestConfigurationLogic SrcDestConfigurationLogic =
new SrcDestConfigurationLogic(new SrcDestCofigurationRepository());
private string _enterprise;
public string Enterprise
{
get { return _enterprise; }
set { SetProperty(ref _enterprise, value); }
}
private int currentJobID;
private int currentSequence;
private int previousJobID;
private int previousSequence;
private string _site;
public string Site
{
get { return _site; }
set { SetProperty(ref _site, value); }
}
private int _siteID;
public int SiteID
{
get { return _siteID; }
set { SetProperty(ref _siteID, value); }
}
private ObservableCollection<JobConfigurationResults> _jobEntities;
public ObservableCollection<JobConfigurationResults> JobEntities
{
get { return _jobEntities; }
set
{
SetProperty(ref _jobEntities, value);
this.OnPropertyChanged("JobEntities");
}
}
//Source System List for Job
private List<SourceSiteSystem> _lstJobSrcSystems;
public List<SourceSiteSystem> LstJobSrcSystems
{
get { return _lstJobSrcSystems; }
set
{
//Using bindable base setproperty method instead of older inotify prop changed method
SetProperty(ref _lstJobSrcSystems, value);
}
}
//Deestination System List for Job
private List<DestinationSiteSystem> _lstJobDestSystems;
public List<DestinationSiteSystem> LstJobDestSystems
{
get { return _lstJobDestSystems; }
set
{
//Using bindable base setproperty method instead of older inotify prop changed method
SetProperty(ref _lstJobDestSystems, value);
}
}
//the Selected Source Site system ID
private int _selectedSrcSiteSystemId = 0;
public int SelectedSrcSiteSystemId
{
get { return _selectedSrcSiteSystemId; }
set
{
//Using bindable base setproperty method instead of older inotify prop changed method
SetProperty(ref _selectedSrcSiteSystemId, value);
}
}
//the Selected Source Site system from the dropdown
private SourceSiteSystem _selectedSrcSiteSystem;
public SourceSiteSystem SelectedSrcSiteSystem
{
get { return _selectedSrcSiteSystem; }
set
{
//Using bindable base setproperty method instead of older inotify prop changed method
if (value != null)
{
SetProperty(ref _selectedSrcSiteSystem, value);
SelectedSrcSiteSystemId = SelectedSrcSiteSystem.SiteSystemId;
}
}
}
//the Selected Destination Site system ID
private int _selectedDestSiteSystemId = 0;
public int SelectedDestSiteSystemId
{
get { return _selectedDestSiteSystemId; }
set
{
//Using bindable base setproperty method instead of older inotify prop changed method
SetProperty(ref _selectedDestSiteSystemId, value);
}
}
//the Selected Destination Site system from the dropdown
private DestinationSiteSystem _selectedDestSiteSystem;
public DestinationSiteSystem SelectedDestSiteSystem
{
get { return _selectedDestSiteSystem; }
set
{
//Using bindable base setproperty method instead of older inotify prop changed method
if (value != null)
{
SetProperty(ref _selectedDestSiteSystem, value);
SelectedDestSiteSystemId = SelectedDestSiteSystem.SiteSystemId;
}
}
}
private JobConfigurationResults _jeJobConfigurationResults;
public JobConfigurationResults JEJobConfigurationResults
{
get { return _jeJobConfigurationResults; }
set { _jeJobConfigurationResults = value; }
}
private List<JobTaskConfiguration> _taskSelectionList = new List<JobTaskConfiguration>();
private CancellationTokenSource _source;
private RelayCommand<object> _commandSaveInstance;
private RelayCommand<object> _hyperlinkInstance;
private RelayCommand<object> _commandRunJob;
private RelayCommand<object> _upCommand;
private RelayCommand<object> _downCommand;
private IEventAggregator _aggregator;
/// <summary>
/// This is a Subscriber to the Event published by EnterpriseViewModel
/// </summary>
/// <param name="agg"></param>
public JobConfigurationViewModel(IEventAggregator agg)
{
_aggregator = agg;
PubSubEvent<Message> evt = _aggregator.GetEvent<PubSubEvent<Message>>();
evt.Subscribe(message => Enterprise = message.Enterprise.ToString(), ThreadOption.BackgroundThread);
evt.Subscribe(message => Site = message.Site.ToString(), ThreadOption.BackgroundThread);
evt.Subscribe(message => SiteID = message.SiteID, ThreadOption.BackgroundThread);
//evt.Unsubscribe();
StartPopulate();
}
private async void StartPopulate()
{
await TaskPopulate();
}
//This is to ensure that the publisher has published the data that is needed for display in this workspace
private bool TaskProc()
{
Thread.Sleep(500);
PubSubEvent<Message> evt = _aggregator.GetEvent<PubSubEvent<Message>>();
evt.Subscribe(message => Enterprise = message.Enterprise.ToString(), ThreadOption.BackgroundThread);
evt.Subscribe(message => Site = message.Site.ToString(), ThreadOption.BackgroundThread);
evt.Subscribe(message => SiteID = message.SiteID, ThreadOption.BackgroundThread);
return DoPopulate();
}
private Task<bool> TaskPopulate()
{
_source = new CancellationTokenSource();
return Task.Factory.StartNew<bool>(TaskProc, _source.Token);
}
/// <summary>
/// This method handles the populating of the Source and Destination Dropdowns and the Job entity and Task Datagrid
/// This is mainly driven by the Site selected in the previous workspace
/// </summary>
/// <returns></returns>
private bool DoPopulate()
{
PopulateSourceDestinations(this.SiteID);
return true;
}
/// <summary>
/// this method displays all entities and tasks for the site.
/// This is done async so that the Publisher thread is not held up
/// </summary>
public void GetJobConfigurationResults()
{
if (SelectedSrcSiteSystem == null)
{
SelectedSrcSiteSystem = LstJobSrcSystems[0];
}
if (SelectedDestSiteSystem == null)
{
SelectedDestSiteSystem = LstJobDestSystems[0];
}
SelectedSrcSiteSystemId = SelectedSrcSiteSystem.SiteSystemId;
SelectedDestSiteSystemId = SelectedDestSiteSystem.SiteSystemId;
var jobConfigurationResults = new JobConfigurationResults
{
SourceId = SelectedSrcSiteSystemId,
DestinationId = SelectedDestSiteSystemId
};
JobEntities = new ObservableCollection<JobConfigurationResults>();
JobEntities = JobConfigurationLogic.GetResults(jobConfigurationResults.SourceId,
jobConfigurationResults.DestinationId);
_taskSelectionList = new List<JobTaskConfiguration>(JobEntities.Count * 3);
}
/// <summary>
/// //Adding a method to pupulate the Source and Destination dropdown lists.
/// This is done async so that the Publisher thread is not held up
/// </summary>
///
///
public async void PopulateSourceDestinations(int siteId)
{
this.LstJobSrcSystems = SrcDestConfigurationLogic.LoadSourceSiteSystems(siteId);
this.LstJobDestSystems = SrcDestConfigurationLogic.LoadDestinationSystems(siteId);
GetJobConfigurationResults();
}
public ICommand HyperlinkCommand
{
get
{
if (_hyperlinkInstance == null)
_hyperlinkInstance = new RelayCommand<object>(openDialog);
return _hyperlinkInstance;
}
}
private void openDialog(object obj)
{
JobConfigurationResults results = obj as JobConfigurationResults;
JEJobConfigurationResults = JobEntities.SingleOrDefault(x => x.JobEntityId == results.JobEntityId);
}
public ICommand CommandSave
{
get
{
if (_commandSaveInstance == null)
_commandSaveInstance = new RelayCommand<object>(saveJobConfigurationChanges);
return _commandSaveInstance;
}
}
public ICommand CommandRunJob
{
get { return _commandRunJob ?? (_commandRunJob = new RelayCommand<object>(RunJob)); }
}
/// <summary>
/// this saves all the changes in the selection made by the user
/// </summary>
/// <param name="ob"></param>
public void saveJobConfigurationChanges(object ob)
{
foreach (var job in JobEntities)
{
int jobEntityId = job.JobEntityId;
foreach (var task in job.TaskDetails)
{
int id = task.JobTask_ID;
bool isSelected = task.IsSelected;
_taskSelectionList.Add(task);
}
}
JobConfigurationLogic.UpdateTaskSelection(_taskSelectionList);
}
public ICommand UpCommand
{
get
{
if (_upCommand == null)
_upCommand = new RelayCommand<object>(MoveUp);
return _upCommand;
}
}
private void MoveUp(object obj)
{
if (obj != null)
{
JobConfigurationResults results = obj as JobConfigurationResults;
currentJobID = results.JobEntityId;
currentSequence = results.SequenceOrder - 1;
try
{
JobConfigurationResults res = _jobEntities.SingleOrDefault(n => n.SequenceOrder == currentSequence);
previousJobID = res.JobEntityId;
previousSequence = res.SequenceOrder + 1;
// JobConfigurationLogic.UpdateSequence(currentJobID, previousSequence, previousJobID, currentSequence);
JobConfigurationLogic.UpdateSequence(currentSequence, currentJobID, previousSequence, previousJobID);
OnPropertyChanged("JobEntities");
}
catch (NullReferenceException)
{
MessageBox.Show("Can't move the top record");
}
}
else
{
MessageBox.Show("Please Select a row that you want to sort");
}
}
public ICommand DownCommand
{
get
{
if (_downCommand == null)
_downCommand = new RelayCommand<object>(MoveDown);
return _downCommand;
}
}
private void MoveDown(object obj)
{
if (obj != null)
{
JobConfigurationResults results = obj as JobConfigurationResults;
currentJobID = results.JobEntityId;
currentSequence = results.SequenceOrder + 1;
try
{
JobConfigurationResults res = _jobEntities.SingleOrDefault(a => a.SequenceOrder == currentSequence);
previousJobID = res.JobEntityId;
previousSequence = res.SequenceOrder - 1;
JobConfigurationLogic.UpdateSequence(currentSequence, currentJobID, previousSequence, previousJobID);
OnPropertyChanged("JobEntities");
}
catch (NullReferenceException)
{
MessageBox.Show("You have reached the end");
}
}
else
{
MessageBox.Show("Please Select a row that you want to sort");
}
}
/// <summary>
/// Execute an etl job using the current job id
/// </summary>
private void RunJob(object obj)
{
JobEngine jobEngine = new JobEngine();
var jobId = JobEntities[0].JobId;
jobEngine.ProcessJob(jobId);
}
}
CS CODE:
private void btnup_Click(object sender, RoutedEventArgs e)
{
dgEntities.Items.Refresh();
//dgEntities.GetBindingExpression(DataGrid.ItemsSourceProperty).UpdateTarget();
}
private void btndown_Click(object sender, RoutedEventArgs e)
{
dgEntities.GetBindingExpression(DataGrid.ItemsSourceProperty).UpdateTarget();
}
An ObservableCollection will notify on change. There's no reason to do it manually, so you can remove all of the OnPropertyChanged("JobEntities");. This will get you to a cleaner solution.
MSDN
WPF provides the ObservableCollection class, which is a built-in
implementation of a data collection that implements the
INotifyCollectionChanged interface.
The next part is that an ObservableCollection will only notify on changes to the collection itself (add/remove). Any modifications to an element within the list will not have have the notify message sent. To do this, the simplest method is to implement the INotifyPropertyChanged to the elements used in the Observable Collection
I'm using PRISM 5 in the example, so it should be pretty equal to what you're doing. There's a couple of major design changes to you're code. First, I'm using a straight property for my Observable Collection. We know the framework will handle any add/remove operations to this collection. Then to notify when I change a property within the entity in an observable collection, I've used a notify property within the TestEntity class itself.
public class MainWindowViewModel : BindableBase
{
//Notice no OnPropertyChange, just a property
public ObservableCollection<TestEntity> TestEntities { get; set; }
public MainWindowViewModel()
: base()
{
this.TestEntities = new ObservableCollection<TestEntity> {
new TestEntity { Name = "Test", Count=0},
new TestEntity { Name = "Test1", Count=1},
new TestEntity { Name = "Test2", Count=2},
new TestEntity { Name = "Test3", Count=3}
};
this.UpCommand = new DelegateCommand(this.MoveUp);
}
public ICommand UpCommand { get; private set; }
private void MoveUp()
{
//Here is a dummy edit to show the modification of a element within the observable collection
var i = this.TestEntities.FirstOrDefault();
i.Count = 5;
}
}
Here's my entity, notice the BindableBase and the fact I notify on change. This allows the DataGrid or whatever you're using to be notified that the property changed.
public class TestEntity : BindableBase {
private String _name;
public String Name
{
get { return _name; }
set { SetProperty(ref _name, value); }
}
//Notice I've implemented the OnPropertyNotify (Prism uses SetProperty, but it's the same thing)
private Int32 _count;
public Int32 Count
{
get { return _count; }
set { SetProperty(ref _count, value); }
}
}
Now really all the TestEntity needs to have implemented the INotifyPropertyChanged for this to work, but I'm using the PRISM BindableBase as an example.
EDIT
I found a similar question on SO. I think yours is slightly different, but they overlap on the concepts. It may help to look over it.
Observable Collection Notify when property changed in MVVM
EDIT
If the datagrid is sorted the previous method will not update the grid. To handle this you need to refresh the grid's view, but are unable to directly access it using MVVM. So to handle this you'll want to use a CollectionViewSource.
public class MainWindowViewModel : BindableBase
{
//This will bind to the DataGrid instead of the TestEntities
public CollectionViewSource ViewSource { get; set; }
//Notice no OnPropertyChange, just a property
public ObservableCollection<TestEntity> TestEntities { get; set; }
public MainWindowViewModel()
: base()
{
this.TestEntities = new ObservableCollection<TestEntity> {
new TestEntity { Name = "Test", Count=0},
new TestEntity { Name = "Test1", Count=1},
new TestEntity { Name = "Test2", Count=2},
new TestEntity { Name = "Test3", Count=3}
};
this.UpCommand = new DelegateCommand(this.MoveUp);
//Initialize the view source and set the source to your observable collection
this.ViewSource = new CollectionViewSource();
ViewSource.Source = this.TestEntities;
}
public ICommand UpCommand { get; private set; }
private void MoveUp()
{
//Here is a dummy edit to show the modification of a element within the observable collection
var i = this.TestEntities.FirstOrDefault();
i.Count = 5;
//Now anytime you want the datagrid to refresh you can call this.
ViewSource.View.Refresh();
}
}
The TestEntity class does not change, but here's the class again:
public class TestEntity : BindableBase
{
private String _name;
public String Name
{
get { return _name; }
set { SetProperty(ref _name, value); }
}
//Notice I've implemented the OnPropertyNotify (Prism uses SetProperty, but it's the same thing)
private Int32 _count;
public Int32 Count
{
get { return _count; }
set { SetProperty(ref _count, value); }
}
}
For clarification, here's my XAML showing the binding to the new CollectionViewSource.
<DataGrid Grid.Row="1" ItemsSource="{Binding ViewSource.View}"></DataGrid>
For further reading you can refer to the MSDN article on this.
Here's another relevant question/answer - Re-sort WPF DataGrid after bounded Data has changed
The Reactive Extensions allow you to easily subscribe to an event using Observable.FromEventPattern, but I can't find anything on how you might implement an event when you have an IObservable.
My situation is this: I need to implement an interface which contains an event. That event is supposed to be called whenever a certain value of my object changes, and for thread safety reasons I need to call this event on a certain SynchronizationContext. I am also supposed to call each event handler with the current value on registration.
public interface IFooWatcher
{
event FooChangedHandler FooChanged;
}
Getting an observable that does what I want is rather easy with Rx using BehaviorSubject:
public class FooWatcher
{
private readonly BehaviorSubject<Foo> m_subject;
private readonly IObservable<Foo> m_observable;
public FooWatcher(SynchronizationContext synchronizationContext, Foo initialValue)
{
m_subject = new BehaviorSubject<Foo>(initialValue);
m_observable = m_subject
.DistinctUntilChanged()
.ObserveOn(synchronizationContext);
}
public event FooChangedHandler FooChanged
{
add { /* ??? */ }
remove { /* ??? */ }
}
}
Now I am looking for an easy way to have the add and remove functions subscribe and unsubscribe the passed FooChangedHandler as an Observer<Foo> on m_observable. My current implementation looks similar to this:
add
{
lock (m_lock)
{
IDisposable disp = m_observable.Subscribe(value);
m_registeredObservers.Add(
new KeyValuePair<FooChangedHandler, IDisposable>(
value, disp));
}
}
remove
{
lock (m_lock)
{
KeyValuePair<FooChangedHandler, IDisposable> observerDisposable =
m_registeredObservers
.First(pair => object.Equals(pair.Key, value));
m_registeredObservers.Remove(observerDisposable);
observerDisposable.Value.Dispose();
}
}
However, I hope to find an easier solution, because I need to implement several of these events (of differing handler types). I tried to roll my own generic solution but it creates some additional problems that need to be worked around (in particular, how you generically work with a delegate that takes a parameter of T), so I would prefer to find an existing solution that bridges the gap in this direction - just as FromEventPattern does the reverse.
You could do this:
public event FooChangedHandler FooChanged
{
add { m_observable.ToEvent().OnNext += value; }
remove { m_observable.ToEvent().OnNext -= value; }
}
However, on the remove, I think perhaps you just may want to dispose of the subscription ... or perhaps get the Action from ToEvent() and store that as a member. Untested.
EDIT: You'll have to use Action instead of a FooChangedHandler delegate, however.
EDIT 2: Here's a tested version. I suppose you need to use FooChangedHandler, however, since you have a bunch of these pre-existing handlers?
void Main()
{
IObservable<Foo> foos = new [] { new Foo { X = 1 }, new Foo { X = 2 } }.ToObservable();
var watcher = new FooWatcher(SynchronizationContext.Current, new Foo { X = 12 });
watcher.FooChanged += o => o.X.Dump();
foos.Subscribe(watcher.Subject.OnNext);
}
// Define other methods and classes here
//public delegate void FooChangedHandler(Foo foo);
public interface IFooWatcher
{
event Action<Foo> FooChanged;
}
public class Foo {
public int X { get; set; }
}
public class FooWatcher
{
private readonly BehaviorSubject<Foo> m_subject;
public BehaviorSubject<Foo> Subject { get { return m_subject; } }
private readonly IObservable<Foo> m_observable;
public FooWatcher(SynchronizationContext synchronizationContext, Foo initialValue)
{
m_subject = new BehaviorSubject<Foo>(initialValue);
m_observable = m_subject
.DistinctUntilChanged();
}
public event Action<Foo> FooChanged
{
add { m_observable.ToEvent().OnNext += value; }
remove { m_observable.ToEvent().OnNext -= value; }
}
}
Given that you are already mixing the boundaries between reactive and more normal code, you could do a less reactive version. To start simply declare a normal event pattern
public event FooChangedHandler FooChanged;
protected void OnFooChanged(Foo)
{
var temp = FooChanged;
if (temp != null)
{
temp(new FooChangedEventArgs(Foo));
}
}
and then simply connect the observable to it in the constructor
m_Observable.Subscribe(foo => OnFooChanged(foo));
It's not very Rx but it is incredibly simple.
I have a class that takes an observable in its constructor, then subscribes to it and does some stuff, sets properties etc. The class itself is observable.
I want to subscribe to my source observable only if someone is subscribed to my class, but I can't figure out how to do it.
public MyClass : IObservable<MyResult>
{
private readonly Subject<MyResult> _subject = new Subject<MyResult>();
private readonly IConnectableObservable<MySource> _source;
public MyClass(IObservable<MySource> source)
{
_source = source
//All my logic to set properties and such
//goes here as a side effect, instead of in a subscription...
.Do(...)
//I hope that by publishing, side effects will happen only once...
.Publish();
}
public IDisposable Subscribe(IObserver<MyResult> observer)
{
return new CompositeDisposable(
_source.Subscribe(/*
don't have anything to do here,
just subscribing to make sure I'm subscribed to source...
(this can't be the right way to do it)
*/),
_subject.Subscribe(observer));
}
}
UPDATE
#Scott: I can see why implementing IObservable would be an anti-pattern. My Class needs to consume a single observable, and exposes 3 as properties (originally the most commonly used observable was going to be returned by MyClass itself, but I think that having it as a property might be better.
What I'm trying to write is an observable ICommand. I know some exist, but this is more of a way to learn Rx...
public class ObservableCommand<T> : ICommand
{
private readonly ISubject<T> _executeRequests = new Subject<T>();
private readonly ISubject<T> _canExecuteRequests = new Subject<T>();
public IObservable<bool> CanExecuteChanges { get; private set; }
public IObservable<T> CanExecuteRequests { get; private set; }
public IObservable<T> ExecuteRequests { get; private set; }
public ObservableCommand(IObservable<bool> canExecute)
{
var source = canExecute.DistinctUntilChanged()
//How do I dispose of subscription later?
//I have this fear that I'm going to have a chain of references,
//and my entire app will never get GC'd!
var subscription = source.Subscribe(
o => {
if (CanExecuteChanged != null)
CanExecuteChanged(this, EventArgs.Empty);
});
CanExecuteChanges = source;
CanExecuteRequests = _canExecuteRequests.AsObservable();
ExecuteRequests = _executeRequests.AsObservable();
}
#region ICommand Members
public bool CanExecute(object parameter)
{
_canExecuteRequests.OnNext(parameter is T ? (T)parameter : default(T));
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_executeRequests.OnNext(parameter is T ? (T)parameter : default(T));
}
#endregion
}
How about just not Doing or Publishing in the constructor, but rather in the Subscribe method?
It should be said, explicitly implementing IObservable<T> is something of an Rx anti-pattern.
You can make Subscriptions dependent on other subscribers with Defer and Create, something like
IObservable<MySource> source;
IObservable<MySource> sourceWithSubSideEffect = Observable.Defer(() =>
{
// Do something interesting on Subscription
// ....
return source;
});
I've prepared a snipped for you. MyClass implements IObservable<T> and has also methods of IObserver<T> but they are all private. With additional OnInitialize and OnSubscribe you should be able to do whatever you want on any event you want to response to.
If you want to make this snipped reusable you could define all methods as partial as they all return void. Then you could create definition to whatever you want.
public class MyClass<T> : IObservable<T>
{
private readonly IObservable<T> m_Source;
public MyClass(IObservable<T> source)
{
if (source == null) throw new ArgumentNullException("source");
m_Source = source.Do(OnNext, OnError, OnCompleted);
OnInitialize();
}
public IDisposable Subscribe(IObserver<T> observer)
{
OnSubscribe();
return m_Source.Subscribe(observer);
}
private void OnInitialize()
{
Console.WriteLine("OnInitialize");
}
private void OnSubscribe()
{
Console.WriteLine("OnSubscribe");
}
private void OnNext(T value)
{
Console.WriteLine("OnNext: {0}", value);
}
private void OnError(Exception error)
{
Console.WriteLine("OnError: {0}", error.Message);
}
private void OnCompleted()
{
Console.WriteLine("OnCompleted");
}
}