Skip to content

Latest commit

History

1,716 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Gu.Reactive

LicenseNugetNugetBuild statusBuild StatusGitter

Helpers for using System.Reactive with INotifyPropertyChanged.

Table of contents

Assembly redirects.

When used from .Net 4.6 assembly redirects are needed. dotnet/reactive#299

<dependentAssembly>
<assemblyIdentityname="System.Reactive.Core"publicKeyToken="94bc3704cddfc263"culture="neutral" />
<bindingRedirectoldVersion="0.0.0.0-3.0.3000.0"newVersion="3.0.3000.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentityname="System.Reactive.PlatformServices"publicKeyToken="94bc3704cddfc263"culture="neutral" />
<bindingRedirectoldVersion="0.0.0.0-3.0.3000.0"newVersion="3.0.3000.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentityname="System.Reactive.Linq"publicKeyToken="94bc3704cddfc263"culture="neutral" />
<bindingRedirectoldVersion="0.0.0.0-3.0.3000.0"newVersion="3.0.3000.0" />
</dependentAssembly>

Nuget can generate redirects using PM> Get-Project –All | Add-BindingRedirect

Factory methods for creating observables.

ObservePropertyChangedSlim:

varsubscription=foo.ObservePropertyChangedSlim(x =>x.Bar.Baz).Subscribe(...);
  1. Return an IObservable<PropertyChangedEventArgs> so more lightweight than ObservePropertyChanged
  2. Filters change args mathing property name or string.IsNullOrEmpty

SignalInitial

Default true meaning that the observable will call OnNext on Subscribe

ObserveValue

Observe the value of a property, calls on next when the value changes. Returns an Observable<Maybe<T>>, this is due to if you observe foo.ObserveValue(x => x.Bar.Baz) and Bar is null. If so it return Maybe.None

varints=newList<int>();foo.ObserveValue(x =>x.Bar.Baz).Subscribe(ints.Add);

ObservePropertyChanged:

varsubscription=fake.ObservePropertyChanged(x =>x.Level1.Level2.Value).Subscribe(...);
  1. Create an observable from the PropertytChangedEvent for fake.
  2. Listens to nested changes. All steps in the property path must be INotifyPropertyChanged. Throws if not.
  3. When PropertyChanged is raised with string.Empty or null the observable notifies.
  4. Updates subscriptions for items in path and uses weak events.

SignalInitial

Default true meaning that the observable will call OnNext on Subscribe. The sender will be tha last node in the path that has a value, in the example above it would be the value of the property Level2 if it is not null, then Level1 if not null if the entire path is null the root item fake is used as sender for the first notifixcation. The eventags for the signal initial event is string.Empty

ObservePropertyChangedWithValue

fake.ObservePropertyChangedWithValue(x =>x.Collection).ItemPropertyChanged(x =>x.Name).Subscribe(_changes.Add);

ObserveCollectionChanged:

varsubscription=collection.ObserveCollectionChanged().Subscribe(...);
  1. Create an observable from the CollectionChangedEvent for collection.

SignalInitial

Default true meaning that the observable will call OnNext on Subscribe

ObservePropertyChangedSlim:

varsubscription=collection.ObserveCollectionChangedSlim().Subscribe(...);
  1. Return an IObservable<NotifyCollectionChangedEventArgs> so more lightweight than ObserveCollectionChanged

SignalInitial

Default true meaning that the observable will call OnNExt on Subscribe

ObserveItemPropertyChanged

varsubscription=collection.ObserveItemPropertyChanged(x =>x.Name).Subscribe(...);

An observable that signals when the collection is modified or the property that the lambda points to signals for any item in the collection. If the collection changes the collection is sender. When an element is removed null is passed as item. When an element is replaced the observable notifies twice, once for the remove of the old element and once for the add of the new element.

SignalInitial

Default true meaning that the observable will call OnNExt on Subscribe

ObserveItemPropertyChanged

varsubscription=collection.ObserveItemPropertyChangedSlim(x =>x.Name).Subscribe(...);

An observable that signals when the collection is modified or the property that the lambda points to signals for any item in the collection.

SignalInitial

Default true meaning that the observable will call OnNExt on Subscribe

Conditions:

Condition

A type that calculates IsSatisfied when any of the IObservable<object> triggers signals. Create it in code like this:

this.isTrueCondition=newCondition(this.ObservePropertyChanged(x =>x.IsTrue),()=>this.IsTrue);

The conditions work really well when used with an IoC. Then subclasses are created and the IoC is used to build trees of nested conditions.

publicclassHasFuel:Condition{publicHasFuel(Carcar):base(car.ObservePropertyChanged(x =>x.FuelLevel),()=>car.FuelLevel>0){}}

IsSatisfied

Evaluates the criteria passed to the ctor. Recalculates when any of the observables signals and the value changes.

Name

Default is GetType.Name but the property is mutable so other names can be specified.

History

The last 100 times of change and values for IsSatisfied

Negate()

Returns a condition wrapping the instance and negating the value of IsSatisfied. Negating a negated condition returns the original condition.

OrCondition

Calculates IsSatisfied based on if any of the prerequisites are true. Listens to changes in IsSatisfied for prerequisites and notifies when value changes.

publicclassIsAnyDoorOpen:OrCondition{publicIsAnyDoorOpen(IsLeftDoorOpenisLeftDoorOpen,IsRightDoorOpenisRightDoorOpen):base(isLeftDoorOpen,isRightDoorOpen){}}

IsSatisfied

True if IsSatisfied for any prerequisites is true. False if IsSatisfied for all prerequisites are false. Null if IsSatisfied for no prerequisite is true and any prerequisite is null.

AndCondition

Calculates IsSatisfied based on if all of the prerequisites have IsSatisfied == true. Listens to changes in IsSatisfied for prerequisites and notifies when value changes.

publicclassIsAnyDoorOpen:AndCondition{publicIsAnyDoorOpen(IsLeftDoorOpenisLeftDoorOpen,IsRightDoorOpenisRightDoorOpen):base(isLeftDoorOpen.Negate(),isRightDoorOpen.Negate()){}}

IsSatisfied

True if IsSatisfied for all prerequisites are true. False if IsSatisfied for any prerequisite is false. Null if IsSatisfied for no prerequisite is false and any prerequisite is null.

Se demo for more code samples.

Collections

ReadOnlyFilteredView

A typed filtered view of a collection. Sample usage:

publicsealedclassViewModel:INotifyPropertyChanged,IDisposable{privatestringfilterText;privatebooldisposed;publicViewModel(ObservableCollection<Person>people,IWpfSchedulersschedulers){this.FilteredPeople=people.AsReadOnlyFilteredView(this.IsMatch,TimeSpan.FromMilliseconds(10),schedulers.Dispatcher,this.ObservePropertyChangedSlim(nameof(this.FilterText)));}publiceventPropertyChangedEventHandlerPropertyChanged;publicIReadOnlyObservableCollection<Person>FilteredPeople{get;}publicstringFilterText{get{returnthis.filterText;}set{if(value==this.filterText){return;}this.filterText=value;this.OnPropertyChanged();}}publicvoidDispose(){if(this.disposed){return;}this.disposed=true;this.FilteredPeople.Dispose();}privatevoidOnPropertyChanged([CallerMemberName]stringpropertyName=null){this.PropertyChanged?.Invoke(this,newPropertyChangedEventArgs(propertyName));}privateboolIsMatch(Personperson){if(string.IsNullOrEmpty(this.filterText)){returntrue;}varindexOf=CultureInfo.InvariantCulture.CompareInfo.IndexOf(person.FirstName,this.filterText,CompareOptions.OrdinalIgnoreCase);if(this.filterText.Length==1){returnindexOf==0;}returnindexOf>=0;}}

MappingView<TSource, TResult>

A view that maps from one type to another. If the source type is a reference type the same instance produces the same mapped instance if the item appears more than once in the collection.

Simple

publicsealedclassViewModel:IDisposable{privatebooldisposed;publicViewModel(ObservableCollection<Person>people){this.PersonViewModels=people.AsMappingView(x =>newPersonViewModel(x));}publicMappingView<Person,PersonViewModel>PersonViewModels{get;}publicvoidDispose(){if(this.disposed){return;}this.disposed=true;this.PersonViewModels.Dispose();}}

When creating IDisposable

Pass in an action that is invoked when the last instance is removed from the mapped collection.

publicsealedclassViewModel:IDisposable{privatebooldisposed;publicViewModel(ObservableCollection<Person>people){this.PersonViewModels=people.AsMappingView(
x =>newPersonViewModel(x),
x =>x.Dispose());}publicIReadOnlyObservableCollection<PersonViewModel>PersonViewModels{get;}publicvoidDispose(){if(this.disposed){return;}this.disposed=true;(this.PersonViewModelsasIDisposable)?.Dispose();}}

ReadOnlyThrottledView

A view that buffers changes. If there are many changes within the buffer time one reset event is raised instead of on event per change.

publicsealedclassViewModel:IDisposable{privatebooldisposed;publicViewModel(ObservableCollection<Person>people){this.PersonViewModels=people.AsReadOnlyThrottledView(TimeSpan.FromMilliseconds(100));}publicIReadOnlyObservableCollection<Person>PersonViewModels{get;}publicvoidDispose(){if(this.disposed){return;}this.disposed=true;this.PersonViewModels.Dispose();}}

ReadOnlyDispatchingView

A view that notifies on the dispatcher. Useful if the collection is updated on another thread.

publicsealedclassViewModel:IDisposable{privatebooldisposed;publicViewModel(ObservableCollection<Person>people){this.PersonViewModels=people.AsReadOnlyDispatchingView();}publicIReadOnlyObservableCollection<Person>PersonViewModels{get;}publicvoidDispose(){if(this.disposed){return;}this.disposed=true;this.PersonViewModels.Dispose();}}

ReadOnlySerialView

A view that can have source set to different collections. Useful when composing with for example MappedView

publicsealedclassViewModel:IDisposable{privatebooldisposed;publicViewModel(ObservableCollection<Person>people){this.Update(people);}publicReadOnlySerialView<Person>People{get;}=newReadOnlySerialView<Person>();publicvoidUpdate(ObservableCollection<Person>people){this.People.SetSource(people);}publicvoidDispose(){if(this.disposed){return;}this.disposed=true;this.People.Dispose();}}

Sample

The following sample filters a list and then maps it to another type. This happens on the task pool. Then changhes are notified to the view on the dispatcher.

publicsealedclassViewModel:INotifyPropertyChanged,IDisposable{privatestringfilterText;privatebooldisposed;publicViewModel(ObservableCollection<Person>people,IWpfSchedulersschedulers){this.FilteredPeople=people.AsReadOnlyFilteredView(this.IsMatch,TimeSpan.FromMilliseconds(10),schedulers.TaskPool,this.ObservePropertyChangedSlim(nameof(this.FilterText))).AsMappingView(
x =>newPersonViewModel(x),
x =>x.Dispose()).AsReadOnlyDispatchingView();}publiceventPropertyChangedEventHandlerPropertyChanged;publicIReadOnlyObservableCollection<PersonViewModel>FilteredPeople{get;}publicstringFilterText{get{returnthis.filterText;}set{if(value==this.filterText){return;}this.filterText=value;this.OnPropertyChanged();}}publicvoidDispose(){if(this.disposed){return;}this.disposed=true;(this.FilteredPeopleasIDisposable)?.Dispose();}privatevoidOnPropertyChanged([CallerMemberName]stringpropertyName=null){this.PropertyChanged?.Invoke(this,newPropertyChangedEventArgs(propertyName));}privateboolIsMatch(Personperson){if(string.IsNullOrEmpty(this.filterText)){returntrue;}varindexOf=CultureInfo.InvariantCulture.CompareInfo.IndexOf(person.FirstName,this.filterText,CompareOptions.OrdinalIgnoreCase);if(this.filterText.Length==1){returnindexOf==0;}returnindexOf>=0;}}

Gu.Wpf.Reactive

Helpers for using System.Reactive with INotifyPropertyChanged in WPF applications.

Commands

A set of relay commands. The generic versions take a command parameter of the generic type. The non-generic version does not use the command parameter.

AsyncCommand

For executing tasks. If the overload that takes a CancellationToken is used the CancelCommand cancels the execution. By default the command is disabled while running. If no condition is passed in IsEnabled is true when not running.

publicViewModel(){varcanExecute=newCondition(this.ObservePropertyChanged(x =>x.CanExecute),()=>this.CanExecute);this.SimpleTaskCommand=newAsyncCommand(this.SimpleTask,canExecute);this.CancelableTaskCommand=newAsyncCommand(this.CancelableTask,canExecute);this.ParameterTaskCommand=newAsyncCommand<string>(this.ParameterTask,canExecute);this.CancelableParameterTaskCommand=newAsyncCommand<string>(this.CancelableParameterTask,canExecute);}publicAsyncCommandSimpleTaskCommand{get;}publicAsyncCommandCancelableTaskCommand{get;}publicAsyncCommandParameterTaskCommand{get;}publicAsyncCommandCancelableParameterTaskCommand{get;}privateasyncTaskSimpleTask(){awaitTask.Delay(500).ConfigureAwait(false);}privateasyncTaskCancelableTask(CancellationTokentoken){this.Count=0;for(inti=0;i<5;i++){token.ThrowIfCancellationRequested();this.Count++;awaitTask.Delay(this.Delay,token).ConfigureAwait(false);}}privateTaskParameterTask(stringarg){returnthis.SimpleTask();}privateTaskCancelableParameterTask(stringarg,CancellationTokentoken){returnthis.CancelableTask(token);}

ConditionRelayCommand

A relay command where CanExecute is controlled by a ICondition

publicViewModel(){varcanExecute=newCondition(this.ObservePropertyChanged(x =>x.CanExecute),()=>this.CanExecute);this.ConditionRelayCommand=newConditionRelayCommand(()=> ...,canExecute);this.ConditionRelayCommandWithParameter=newConditionRelayCommand<string>(parameter => ...,canExecute);}publicConditionRelayCommandConditionRelayCommand{get;}publicConditionRelayCommandConditionRelayCommandWithParameter{get;}

ManualRelayCommand

A command where you need to manually call RaiseCanExecuteChanged.

publicViewModel(){this.ManualRelayCommand=newManualRelayCommand(()=> ...,()=>this.CanExecute);this.ManualRelayCommandWithParameter=newManualRelayCommand<string>(parameter => ...,()=>this.CanExecute);}publicManualRelayCommandManualRelayCommand{get;}publicManualRelayCommandManualRelayCommandWithParameter{get;}

ObservingRelayCommand

A command where an observable is passed in for raising CanExecuteChanged.

publicViewModel(){varonCanExecute=this.ObservePropertyChanged(x =>x.CanExecute)
this.ObservingRelayCommand=newObservingRelayCommand(()=> ...,onCanExecute,()=>this.CanExecute);this.ObservingRelayCommandWithParameter=newObservingRelayCommand<string>(parameter => ...,onCanExecute,()=>this.CanExecute);}publicObservingRelayCommandObservingRelayCommand{get;}publicObservingRelayCommandObservingRelayCommandWithParameter{get;}

RelayCommand

A command that uses the CommandManager.RequerySuggested event. It exposes a RaiseCanExecuteChanged method for forcing notification.

publicViewModel(){this.RelayCommand=newRelayCommand(()=> ...,()=>this.CanExecute);this.RelayCommandWithParameter=newRelayCommand<string>(parameter => ...,()=>this.CanExecute);}publicRelayCommandManualRelayCommand{get;}publicRelayCommandManualRelayCommandWithParameter{get;}

MarkupExtensions

EnumValuesForExtension

Markupextension for getting enum values for a type.

Sample code:

xmlns:reactive="http://Gu.com/Reactive"
...
<ComboBoxItemsSource="{reactive:EnumValuesFor {x:Type Visibility}}" />

NinjaBinding

Markupextension for binding when not in the visual tree.

Sample code:

xmlns:reactive="http://Gu.com/Reactive"
...
<CheckBoxx:Name="CheckBox"IsChecked="{Binding Visible}" />
...
<DataGridAutoGenerateColumns="False">
<DataGrid.Columns>
<!--Here the viewmodel has a Visibility property-->
<DataGridTextColumnHeader="Binding"Visibility="{reactive:NinjaBinding {Binding Visibility}}" />
<DataGridTextColumnHeader="ElementName"Visibility="{reactive:NinjaBinding Binding={Binding IsChecked,  ElementName=CheckBox,  Converter={StaticResource BooleanToVisibilityConverter}}}" />
</DataGrid.Columns>
</DataGrid>

About

Helpers for using System.Reactive with INotifyPropertyChanged and INotifyCollectionChanged

Resources

Stars

22 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages