├── FlatStreamToHierarchy ├── Screenshot.gif ├── App.config ├── Properties │ ├── Settings.settings │ ├── Settings.Designer.cs │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ └── Resources.resx ├── App.xaml.cs ├── ViewModels │ ├── MainWindowViewModel.cs │ ├── EmployeesViewModel.cs │ └── EmployeeViewModel.cs ├── App.xaml ├── packages.config ├── MainWindow.xaml.cs ├── Infrastructure │ └── Command.cs ├── Services │ ├── EmployeeDto.cs │ └── EmployeeService.cs ├── Themes │ ├── MaterialDesign.xaml │ ├── Colours.xaml │ └── TreeView.xaml ├── FlatStreamToHierarchy.csproj └── MainWindow.xaml ├── README.md ├── FlatStreamToHierarchy.sln ├── .gitattributes └── .gitignore /FlatStreamToHierarchy/Screenshot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RolandPheasant/FlatStreamToHierarchy/HEAD/FlatStreamToHierarchy/Screenshot.gif -------------------------------------------------------------------------------- /FlatStreamToHierarchy/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /FlatStreamToHierarchy/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /FlatStreamToHierarchy/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Data; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | 9 | namespace FlatStreamToHierarchy 10 | { 11 | /// 12 | /// Interaction logic for App.xaml 13 | /// 14 | public partial class App : Application 15 | { 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /FlatStreamToHierarchy/ViewModels/MainWindowViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Windows.Input; 3 | using FlatStreamToHierarchy.Infrastructure; 4 | using FlatStreamToHierarchy.Services; 5 | 6 | namespace FlatStreamToHierarchy.ViewModels 7 | { 8 | public class MainWindowViewModel 9 | { 10 | public MainWindowViewModel() 11 | { 12 | ShowInGitHubCommand = new Command(() => Process.Start("https://github.com/RolandPheasant")); 13 | Employees = new EmployeesViewModel(new EmployeeService()); 14 | } 15 | 16 | public EmployeesViewModel Employees { get; } 17 | public ICommand ShowInGitHubCommand { get; } 18 | 19 | } 20 | } -------------------------------------------------------------------------------- /FlatStreamToHierarchy/App.xaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FlatStreamToHierarchy 2 | 3 | Dynamic data is a portable class library which brings the power of reactive (rx) to collections. It is open source and the code base lives here [Dynamic Data on GitHub](https://github.com/RolandPheasant/DynamicData). 4 | 5 | This is an example of how to create a hierachal reactive tree from a flat observable stream. 6 | 7 | The demo illustrates how the following code: 8 | 9 | ```csharp 10 | var loader = employeeService.Employees.Connect() 11 | .TransformToTree(employee => employee.BossId) 12 | .Transform(node => new EmployeeViewModel(node, Promote,Sack)) 13 | .Bind(_employeeViewModels) 14 | .DisposeMany() 15 | .Subscribe(); 16 | ``` 17 | 18 | together with some view model and xaml magic produces this 19 | 20 | ![Observable tree example](https://github.com/RolandPheasant/FlatStreamToHierarchy/blob/master/FlatStreamToHierarchy/Screenshot.gif "Observable Tree View") 21 | 22 | 23 | -------------------------------------------------------------------------------- /FlatStreamToHierarchy/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /FlatStreamToHierarchy/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | using System.Windows.Controls; 9 | using System.Windows.Data; 10 | using System.Windows.Documents; 11 | using System.Windows.Input; 12 | using System.Windows.Media; 13 | using System.Windows.Media.Imaging; 14 | using System.Windows.Navigation; 15 | using System.Windows.Shapes; 16 | using FlatStreamToHierarchy.Infrastructure; 17 | using FlatStreamToHierarchy.Services; 18 | using FlatStreamToHierarchy.ViewModels; 19 | 20 | namespace FlatStreamToHierarchy 21 | { 22 | 23 | /// 24 | /// Interaction logic for MainWindow.xaml 25 | /// 26 | public partial class MainWindow 27 | { 28 | private readonly MainWindowViewModel _viewModel = new MainWindowViewModel(); 29 | 30 | 31 | public MainWindow() 32 | { 33 | InitializeComponent(); 34 | 35 | DataContext = _viewModel; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /FlatStreamToHierarchy/Infrastructure/Command.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Input; 3 | 4 | namespace FlatStreamToHierarchy.Infrastructure 5 | { 6 | public class Command : ICommand 7 | { 8 | private readonly Action _execute; 9 | private readonly Func _canExecute; 10 | 11 | 12 | public Command(Action execute, Func canExecute = null) 13 | { 14 | _execute = execute ?? throw new ArgumentNullException(nameof(execute)); 15 | _canExecute = canExecute ?? (() => true); 16 | } 17 | 18 | 19 | public bool CanExecute(object parameter) 20 | { 21 | return _canExecute(); 22 | } 23 | 24 | public void Execute(object parameter) 25 | { 26 | _execute(); 27 | } 28 | 29 | public event EventHandler CanExecuteChanged 30 | { 31 | add => CommandManager.RequerySuggested += value; 32 | remove => CommandManager.RequerySuggested -= value; 33 | } 34 | 35 | public void Refresh() 36 | { 37 | CommandManager.InvalidateRequerySuggested(); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /FlatStreamToHierarchy/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.34014 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace FlatStreamToHierarchy.Properties 12 | { 13 | 14 | 15 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 16 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] 17 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase 18 | { 19 | 20 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 21 | 22 | public static Settings Default 23 | { 24 | get 25 | { 26 | return defaultInstance; 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /FlatStreamToHierarchy.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.31101.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FlatStreamToHierarchy", "FlatStreamToHierarchy\FlatStreamToHierarchy.csproj", "{BE697BE5-3261-4F60-AFE2-40EF5CC1E198}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{52FA11EA-235E-4BBE-8CD3-E999C6FC1272}" 9 | ProjectSection(SolutionItems) = preProject 10 | README.md = README.md 11 | EndProjectSection 12 | EndProject 13 | Global 14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 15 | Debug|Any CPU = Debug|Any CPU 16 | Release|Any CPU = Release|Any CPU 17 | EndGlobalSection 18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 19 | {BE697BE5-3261-4F60-AFE2-40EF5CC1E198}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 20 | {BE697BE5-3261-4F60-AFE2-40EF5CC1E198}.Debug|Any CPU.Build.0 = Debug|Any CPU 21 | {BE697BE5-3261-4F60-AFE2-40EF5CC1E198}.Release|Any CPU.ActiveCfg = Release|Any CPU 22 | {BE697BE5-3261-4F60-AFE2-40EF5CC1E198}.Release|Any CPU.Build.0 = Release|Any CPU 23 | EndGlobalSection 24 | GlobalSection(SolutionProperties) = preSolution 25 | HideSolutionNode = FALSE 26 | EndGlobalSection 27 | EndGlobal 28 | -------------------------------------------------------------------------------- /FlatStreamToHierarchy/Services/EmployeeDto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace FlatStreamToHierarchy.Services 4 | { 5 | public class EmployeeDto : IEquatable 6 | { 7 | public EmployeeDto(int id, string name, int boss) 8 | { 9 | Id = id; 10 | Name = name; 11 | BossId = boss; 12 | } 13 | 14 | public int Id { get; } 15 | public int BossId { get; } 16 | public string Name { get; } 17 | 18 | #region Equality Members 19 | 20 | public bool Equals(EmployeeDto other) 21 | { 22 | if (ReferenceEquals(null, other)) return false; 23 | if (ReferenceEquals(this, other)) return true; 24 | return Id == other.Id; 25 | } 26 | 27 | public override bool Equals(object obj) 28 | { 29 | if (ReferenceEquals(null, obj)) return false; 30 | if (ReferenceEquals(this, obj)) return true; 31 | if (obj.GetType() != this.GetType()) return false; 32 | return Equals((EmployeeDto) obj); 33 | } 34 | 35 | public override int GetHashCode() 36 | { 37 | return Id; 38 | } 39 | 40 | public static bool operator ==(EmployeeDto left, EmployeeDto right) 41 | { 42 | return Equals(left, right); 43 | } 44 | 45 | public static bool operator !=(EmployeeDto left, EmployeeDto right) 46 | { 47 | return !Equals(left, right); 48 | } 49 | 50 | #endregion 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /FlatStreamToHierarchy/ViewModels/EmployeesViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.ObjectModel; 3 | using System.Reactive.Linq; 4 | using DynamicData; 5 | using FlatStreamToHierarchy.Services; 6 | 7 | namespace FlatStreamToHierarchy.ViewModels 8 | { 9 | public class EmployeesViewModel : IDisposable 10 | { 11 | private readonly EmployeeService _employeeService; 12 | private readonly ReadOnlyObservableCollection _employeeViewModels; 13 | private readonly IDisposable _cleanUp; 14 | 15 | public EmployeesViewModel(EmployeeService employeeService) 16 | { 17 | _employeeService = employeeService; 18 | 19 | bool DefaultPredicate(Node node) => node.IsRoot; 20 | 21 | //transform the data to a full nested tree 22 | //then transform into a fully recursive view model 23 | _cleanUp = employeeService.Employees.Connect() 24 | .TransformToTree(employee => employee.BossId, Observable.Return((Func, bool>) DefaultPredicate)) 25 | .Transform(node => new EmployeeViewModel(node, Promote, Sack)) 26 | .Bind(out _employeeViewModels) 27 | .DisposeMany() 28 | .Subscribe(); 29 | } 30 | 31 | private void Promote(EmployeeViewModel viewModel) 32 | { 33 | if (!viewModel.Parent.HasValue) return; 34 | _employeeService.Promote(viewModel.Dto,viewModel.Parent.Value.BossId); 35 | } 36 | 37 | private void Sack(EmployeeViewModel viewModel) 38 | { 39 | _employeeService.Sack(viewModel.Dto); 40 | } 41 | 42 | public ReadOnlyObservableCollection EmployeeViewModels => _employeeViewModels; 43 | 44 | public void Dispose() 45 | { 46 | _cleanUp.Dispose(); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /FlatStreamToHierarchy/Services/EmployeeService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using DynamicData; 5 | 6 | namespace FlatStreamToHierarchy.Services 7 | { 8 | public class EmployeeService 9 | { 10 | private readonly SourceCache _employees = new SourceCache(x => x.Id); 11 | 12 | public EmployeeService() 13 | { 14 | _employees.AddOrUpdate(CreateEmployees(25000)); 15 | } 16 | 17 | public IObservableCache Employees => _employees.AsObservableCache(); 18 | 19 | public void Promote(EmployeeDto promtedDto, int newBoss) 20 | { 21 | //in the real world, go to service then update the cache 22 | 23 | //update the cache with the emploee, 24 | _employees.AddOrUpdate(new EmployeeDto(promtedDto.Id,promtedDto.Name,newBoss)); 25 | } 26 | 27 | 28 | public void Sack(EmployeeDto sackEmp) 29 | { 30 | //in the real world, go to service then updated the cache 31 | 32 | _employees.Edit(updater => 33 | { 34 | //assign new boss to the workers of the sacked employee 35 | var workersWithNewBoss = updater.Items 36 | .Where(emp => emp.BossId == sackEmp.Id) 37 | .Select(dto => new EmployeeDto(dto.Id, dto.Name, sackEmp.BossId)) 38 | .ToArray(); 39 | 40 | updater.AddOrUpdate(workersWithNewBoss); 41 | 42 | //get rid of the existing person 43 | updater.Remove(sackEmp.Id); 44 | }); 45 | 46 | 47 | } 48 | 49 | private IEnumerable CreateEmployees(int numberToLoad) 50 | { 51 | var random = new Random(); 52 | 53 | return Enumerable.Range(1, numberToLoad) 54 | .Select(i => 55 | { 56 | var boss = i%1000 == 0 ? 0 : random.Next(0, i); 57 | return new EmployeeDto(i, $"Person {i}", boss); 58 | }); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /FlatStreamToHierarchy/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Resources; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | using System.Windows; 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [assembly: AssemblyTitle("FlatStreamToHierarchy")] 11 | [assembly: AssemblyDescription("")] 12 | [assembly: AssemblyConfiguration("")] 13 | [assembly: AssemblyCompany("")] 14 | [assembly: AssemblyProduct("FlatStreamToHierarchy")] 15 | [assembly: AssemblyCopyright("Copyright © 2015")] 16 | [assembly: AssemblyTrademark("")] 17 | [assembly: AssemblyCulture("")] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [assembly: ComVisible(false)] 23 | 24 | //In order to begin building localizable applications, set 25 | //CultureYouAreCodingWith in your .csproj file 26 | //inside a . For example, if you are using US english 27 | //in your source files, set the to en-US. Then uncomment 28 | //the NeutralResourceLanguage attribute below. Update the "en-US" in 29 | //the line below to match the UICulture setting in the project file. 30 | 31 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] 32 | 33 | 34 | [assembly: ThemeInfo( 35 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 36 | //(used if a resource is not found in the page, 37 | // or application resource dictionaries) 38 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 39 | //(used if a resource is not found in the page, 40 | // app, or any theme specific resource dictionaries) 41 | )] 42 | 43 | 44 | // Version information for an assembly consists of the following four values: 45 | // 46 | // Major Version 47 | // Minor Version 48 | // Build Number 49 | // Revision 50 | // 51 | // You can specify all the values or you can default the Build and Revision Numbers 52 | // by using the '*' as shown below: 53 | // [assembly: AssemblyVersion("1.0.*")] 54 | [assembly: AssemblyVersion("1.0.0.0")] 55 | [assembly: AssemblyFileVersion("1.0.0.0")] 56 | -------------------------------------------------------------------------------- /FlatStreamToHierarchy/Themes/MaterialDesign.xaml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 86 | 87 | 91 | 92 | 96 | 97 | 98 | 103 | 104 | 105 | 116 | 117 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /FlatStreamToHierarchy/Themes/TreeView.xaml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 7 | 8 | 23 | 24 | 25 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 119 | 120 | 121 | 127 | 134 | 135 | 136 | 137 | 138 | 140 | 143 | 144 | 145 | 147 | 150 | 151 | 152 | 155 | 158 | 160 | 161 | 163 | 166 | 168 | 169 | 171 | 173 | 174 | 175 | 176 | 178 | 180 | 181 | 182 | 185 | 187 | 188 | 189 | 190 | 191 | 193 | 195 | 196 | 199 | 200 | 201 | 202 | 203 | 217 | 218 | 254 | 255 |