Last active
August 6, 2021 17:00
-
-
Save RolandPheasant/81dcc207bf95f6c4cf83f0ecd48ed740 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.ObjectModel; | |
using System.Reactive; | |
using System.Reactive.Linq; | |
using DynamicData.Binding; | |
namespace DynamicData.Tests | |
{ | |
public class DeepNested | |
{ | |
public void Main() | |
{ | |
var t1 = new Type1(); // Initial call into Type1's CheckChanges happens here | |
var t2 = new Type2(); | |
var t3 = new Type3 { Name = "First" }; | |
t2.Type3s.Add(t3); | |
t1.Type2s.Add(t2); // Would also like see call to Type1's CheckChanges method | |
t3.Name = "Second"; // Would also like see call to Type1's CheckChanges method | |
var hasAnythingChanged = t1.HasAnythingChanged | |
.Subscribe(_=> Console.WriteLine("There has been a change")); | |
} | |
} | |
public class Type1 : AbstractNotifyPropertyChanged | |
{ | |
private ObservableCollection<Type2> _type2s = new ObservableCollection<Type2>(); | |
public Type1() | |
{ | |
this.WhenValueChanged(x => x.Type2s) | |
.Subscribe(_ => CheckIfHasChanges()); | |
} | |
/// <summary> | |
/// | |
/// </summary> | |
public IObservable<Unit> HasAnythingChanged => this.WhenValueChanged(t => t.Type2s) | |
.SelectMany(t => | |
{ | |
//account for the dreaded null | |
if (t == null) return Observable.Return(Unit.Default); | |
//watch Type2 collection changes | |
var collectionChanges = t.ToObservableChangeSet().Select(_ => Unit.Default); | |
//watch for changes from Type2.HasAnythingChanged | |
var propertyChanged = t.ToObservableChangeSet().MergeMany(x=>x.HasAnythingChanged).Select(_ => Unit.Default); | |
return collectionChanges.Merge(propertyChanged).StartWith(Unit.Default); | |
}); | |
private void CheckIfHasChanges() | |
{ | |
// Do something on change | |
} | |
public ObservableCollection<Type2> Type2s | |
{ | |
get => _type2s; | |
set => this.SetAndRaise(ref _type2s, value, nameof(Type2s)); | |
} | |
} | |
public class Type2 : AbstractNotifyPropertyChanged | |
{ | |
public IObservable<Unit> HasAnythingChanged => this.WhenValueChanged(t => t.Type3s) | |
.SelectMany(t => | |
{ | |
//account for the dreaded null | |
if (t == null) return Observable.Return(Unit.Default); | |
//watch the collection changes | |
var collectionChanges = t.ToObservableChangeSet().Select(_ => Unit.Default); | |
//watch the property changes - use .WhenValueChanged(t=>t.Name, false) is you do not want the initial value and only want subsequent changes | |
var propertyChanged = t.ToObservableChangeSet().WhenValueChanged(t=>t.Name).Select(_ => Unit.Default); | |
//start with Unit.Default so it always fires after Type3 is set | |
return collectionChanges.Merge(propertyChanged).StartWith(Unit.Default); | |
}); | |
private ObservableCollection<Type3> _type3s = new ObservableCollection<Type3>(); | |
public ObservableCollection<Type3> Type3s | |
{ | |
get => _type3s; | |
set => this.SetAndRaise(ref _type3s, value, nameof(Type3s)); | |
} | |
} | |
public class Type3 : AbstractNotifyPropertyChanged | |
{ | |
private string _name; | |
public string Name | |
{ | |
get => _name; | |
set => this.SetAndRaise(ref _name, value, nameof(Name)); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks again Roland, very much appreciated!
I recently completed an internal Domain Driven Design based modeling tool that provides the means to capture any business domain and then using Roslyn, code generate out respective backing microservices as well as code-generated client-side integration libraries that connect to the code-generate services, plus client-side domain-based ViewModels that bind to the client-side services.
The backing microservices follow the CQRS + EventSourcing pattern and default to using EventStore Cloud as the backing write store, and a default projection into Elastic Search Cloud as a primary read store.
I am now currently designing an internal front end designer tooling that will provide a Designer an ability to create Blazor and/or MAUI based apps that will bind to these code generated domain-based ViewModels.
My approach is to further code-generate two additional ViewModel layers which will sit above the domain-based ViewModels.
The top level will be a Client-Tech-Specific ViewModel that will handle client-specific concerns and then beneath that will be an App-Specific ViewModel.
I am planning to create a graphical UI-Behavioral editor tool to plug into the UI designer that will provide the ability to associate existing layout elements within the designer surface with Reactive type capabilities, and from that will generate the respective Client-Tech-Specific, and App-Specific Reactive ViewModels.
A simple example, might be a login screen where user name and password fields must both have x amount of characters prior to enabling the login button. As I am sure you know how simple that Reactive pipeline would be to support that scenario.
I am pretty confident I can get the UI Designer tooling stood but will likely struggle some to get the appropriate backing Reactive pipelines generated properly, at least initially.
My overall goal is to reduce the amount of code both on back-end (microservices) and front-end clients, and to be able to reuse as much Behavior between different Client types (Blazor/MAUI) for the same Application, so as to provide a rapid platform for developing the most sophisticated applications with great speed and agility.
Wish my luck!