├── 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 | 
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 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/FlatStreamToHierarchy/Properties/Resources.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 | ///
16 | /// A strongly-typed resource class, for looking up localized strings, etc.
17 | ///
18 | // This class was auto-generated by the StronglyTypedResourceBuilder
19 | // class via a tool like ResGen or Visual Studio.
20 | // To add or remove a member, edit your .ResX file then rerun ResGen
21 | // with the /str option, or rebuild your VS project.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources
26 | {
27 |
28 | private static global::System.Resources.ResourceManager resourceMan;
29 |
30 | private static global::System.Globalization.CultureInfo resourceCulture;
31 |
32 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
33 | internal Resources()
34 | {
35 | }
36 |
37 | ///
38 | /// Returns the cached ResourceManager instance used by this class.
39 | ///
40 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
41 | internal static global::System.Resources.ResourceManager ResourceManager
42 | {
43 | get
44 | {
45 | if ((resourceMan == null))
46 | {
47 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("FlatStreamToHierarchy.Properties.Resources", typeof(Resources).Assembly);
48 | resourceMan = temp;
49 | }
50 | return resourceMan;
51 | }
52 | }
53 |
54 | ///
55 | /// Overrides the current thread's CurrentUICulture property for all
56 | /// resource lookups using this strongly typed resource class.
57 | ///
58 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
59 | internal static global::System.Globalization.CultureInfo Culture
60 | {
61 | get
62 | {
63 | return resourceCulture;
64 | }
65 | set
66 | {
67 | resourceCulture = value;
68 | }
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/FlatStreamToHierarchy/Themes/Colours.xaml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/FlatStreamToHierarchy/ViewModels/EmployeeViewModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.ObjectModel;
3 | using System.Reactive.Disposables;
4 | using System.Reactive.Linq;
5 | using System.Windows.Input;
6 | using DynamicData;
7 | using DynamicData.Binding;
8 | using DynamicData.Kernel;
9 | using FlatStreamToHierarchy.Infrastructure;
10 | using FlatStreamToHierarchy.Services;
11 |
12 | namespace FlatStreamToHierarchy.ViewModels
13 | {
14 | public class EmployeeViewModel:AbstractNotifyPropertyChanged, IDisposable, IEquatable
15 | {
16 | private readonly IDisposable _cleanUp;
17 | private bool _isExpanded;
18 | private bool _isSelected;
19 | private readonly Command _promoteCommand;
20 | private readonly Command _sackCommand;
21 | private string _employeeCountText;
22 | private ReadOnlyObservableCollection _inferiors;
23 |
24 | public EmployeeViewModel(Node node, Action promoteAction, Action sackAction, EmployeeViewModel parent = null)
25 | {
26 | Id = node.Key;
27 | Name = node.Item.Name;
28 | Depth = node.Depth;
29 | Parent = parent;
30 | BossId = node.Item.BossId;
31 | Dto = node.Item;
32 |
33 | _promoteCommand = new Command(()=>promoteAction(this),()=>Parent.HasValue);
34 | _sackCommand = new Command(() => sackAction(this));
35 |
36 | //Wrap loader for the nested view model inside a lazy so we can control when it is invoked
37 | var childrenLoader = new Lazy(() => node.Children.Connect()
38 | .Transform(e => new EmployeeViewModel(e, promoteAction, sackAction, this))
39 | .Bind(out _inferiors)
40 | .DisposeMany()
41 | .Subscribe());
42 |
43 | //return true when the children should be loaded
44 | //(i.e. if current node is a root, otherwise when the parent expands)
45 | var shouldExpand = node.IsRoot
46 | ? Observable.Return(true)
47 | : Parent.Value.WhenValueChanged(This => This.IsExpanded);
48 |
49 | //wire the observable
50 | var expander =shouldExpand
51 | .Where(isExpanded => isExpanded)
52 | .Take(1)
53 | .Subscribe(_ =>
54 | {
55 | //force lazy loading
56 | var x = childrenLoader.Value;
57 | });
58 |
59 | //create some display text based on the number of employees
60 | var employeesCount = node.Children.CountChanged
61 | .Select(count =>
62 | {
63 | if (count == 0)
64 | return "I am a at rock bottom";
65 |
66 | return count == 1
67 | ? "1 person reports to me"
68 | : $"{count} people reports to me";
69 |
70 | }).Subscribe(text => EmployeeCountText = text);
71 |
72 | _cleanUp = Disposable.Create(() =>
73 | {
74 | expander.Dispose();
75 | employeesCount.Dispose();
76 | if (childrenLoader.IsValueCreated)
77 | childrenLoader.Value.Dispose();
78 | });
79 | }
80 |
81 | public int Id { get; }
82 |
83 | public string Name { get; }
84 |
85 | public int Depth { get; }
86 |
87 | public int BossId { get; }
88 |
89 | public EmployeeDto Dto { get; }
90 |
91 | public Optional Parent { get; }
92 |
93 | public ReadOnlyObservableCollection Inferiors => _inferiors;
94 |
95 | public ICommand PromoteCommand => _promoteCommand;
96 |
97 | public ICommand SackCommand => _sackCommand;
98 |
99 | public string EmployeeCountText
100 | {
101 | get => _employeeCountText;
102 | set => SetAndRaise(ref _employeeCountText, value);
103 | }
104 |
105 | public bool IsExpanded
106 | {
107 | get => _isExpanded;
108 | set => SetAndRaise(ref _isExpanded,value);
109 | }
110 |
111 | public bool IsSelected
112 | {
113 | get => _isSelected;
114 | set => SetAndRaise(ref _isSelected, value);
115 | }
116 |
117 | #region Equality Members
118 |
119 | public bool Equals(EmployeeViewModel other)
120 | {
121 | if (ReferenceEquals(null, other)) return false;
122 | if (ReferenceEquals(this, other)) return true;
123 | return Id == other.Id;
124 | }
125 |
126 | public override bool Equals(object obj)
127 | {
128 | if (ReferenceEquals(null, obj)) return false;
129 | if (ReferenceEquals(this, obj)) return true;
130 | if (obj.GetType() != this.GetType()) return false;
131 | return Equals((EmployeeViewModel) obj);
132 | }
133 |
134 | public override int GetHashCode()
135 | {
136 | return Id;
137 | }
138 |
139 | public static bool operator ==(EmployeeViewModel left, EmployeeViewModel right)
140 | {
141 | return Equals(left, right);
142 | }
143 |
144 | public static bool operator !=(EmployeeViewModel left, EmployeeViewModel right)
145 | {
146 | return !Equals(left, right);
147 | }
148 |
149 | #endregion
150 |
151 | public void Dispose()
152 | {
153 | _cleanUp.Dispose();
154 | }
155 | }
156 | }
--------------------------------------------------------------------------------
/FlatStreamToHierarchy/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 | text/microsoft-resx
107 |
108 |
109 | 2.0
110 |
111 |
112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
113 |
114 |
115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.gitignore.io/api/visualstudio
3 |
4 | ### VisualStudio ###
5 | ## Ignore Visual Studio temporary files, build results, and
6 | ## files generated by popular Visual Studio add-ons.
7 | ##
8 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
9 |
10 | # User-specific files
11 | *.suo
12 | *.user
13 | *.userosscache
14 | *.sln.docstates
15 |
16 | # User-specific files (MonoDevelop/Xamarin Studio)
17 | *.userprefs
18 |
19 | project.lock.json
20 |
21 | # Build results
22 | [Dd]ebug/
23 | [Dd]ebugPublic/
24 | [Rr]elease/
25 | [Rr]eleases/
26 | x64/
27 | x86/
28 | bld/
29 | [Bb]in/
30 | [Oo]bj/
31 | [Ll]og/
32 |
33 | # Visual Studio 2015 cache/options directory
34 | .vs/
35 | # Uncomment if you have tasks that create the project's static files in wwwroot
36 | #wwwroot/
37 |
38 | # MSTest test Results
39 | [Tt]est[Rr]esult*/
40 | [Bb]uild[Ll]og.*
41 |
42 | # NUNIT
43 | *.VisualState.xml
44 | TestResult.xml
45 |
46 | # Build Results of an ATL Project
47 | [Dd]ebugPS/
48 | [Rr]eleasePS/
49 | dlldata.c
50 |
51 | # .NET Core
52 | project.lock.json
53 | project.fragment.lock.json
54 | artifacts/
55 | **/Properties/launchSettings.json
56 |
57 | *_i.c
58 | *_p.c
59 | *_i.h
60 | *.ilk
61 | *.meta
62 | *.obj
63 | *.pch
64 | *.pdb
65 | *.pgc
66 | *.pgd
67 | *.rsp
68 | *.sbr
69 | *.tlb
70 | *.tli
71 | *.tlh
72 | *.tmp
73 | *.tmp_proj
74 | *.log
75 | *.vspscc
76 | *.vssscc
77 | .builds
78 | *.pidb
79 | *.svclog
80 | *.scc
81 |
82 | # Chutzpah Test files
83 | _Chutzpah*
84 |
85 | # Visual C++ cache files
86 | ipch/
87 | *.aps
88 | *.ncb
89 | *.opendb
90 | *.opensdf
91 | *.sdf
92 | *.cachefile
93 | *.VC.db
94 | *.VC.VC.opendb
95 |
96 | # Visual Studio profiler
97 | *.psess
98 | *.vsp
99 | *.vspx
100 | *.sap
101 |
102 | # TFS 2012 Local Workspace
103 | $tf/
104 |
105 | # Guidance Automation Toolkit
106 | *.gpState
107 |
108 | # ReSharper is a .NET coding add-in
109 | _ReSharper*/
110 | *.[Rr]e[Ss]harper
111 | *.DotSettings.user
112 |
113 | # JustCode is a .NET coding add-in
114 | .JustCode
115 |
116 | # TeamCity is a build add-in
117 | _TeamCity*
118 |
119 | # DotCover is a Code Coverage Tool
120 | *.dotCover
121 |
122 | # Visual Studio code coverage results
123 | *.coverage
124 | *.coveragexml
125 |
126 | # NCrunch
127 | _NCrunch_*
128 | .*crunch*.local.xml
129 | nCrunchTemp_*
130 |
131 | # MightyMoose
132 | *.mm.*
133 | AutoTest.Net/
134 |
135 | # Web workbench (sass)
136 | .sass-cache/
137 |
138 | # Installshield output folder
139 | [Ee]xpress/
140 |
141 | # DocProject is a documentation generator add-in
142 | DocProject/buildhelp/
143 | DocProject/Help/*.HxT
144 | DocProject/Help/*.HxC
145 | DocProject/Help/*.hhc
146 | DocProject/Help/*.hhk
147 | DocProject/Help/*.hhp
148 | DocProject/Help/Html2
149 | DocProject/Help/html
150 |
151 | # Click-Once directory
152 | publish/
153 |
154 | # Publish Web Output
155 | *.[Pp]ublish.xml
156 | *.azurePubxml
157 | # TODO: Comment the next line if you want to checkin your web deploy settings
158 | # but database connection strings (with potential passwords) will be unencrypted
159 | *.pubxml
160 | *.publishproj
161 |
162 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
163 | # checkin your Azure Web App publish settings, but sensitive information contained
164 | # in these scripts will be unencrypted
165 | PublishScripts/
166 |
167 | # NuGet Packages
168 | *.nupkg
169 | # The packages folder can be ignored because of Package Restore
170 | **/packages/*
171 | # except build/, which is used as an MSBuild target.
172 | !**/packages/build/
173 | # Uncomment if necessary however generally it will be regenerated when needed
174 | #!**/packages/repositories.config
175 | # NuGet v3's project.json files produces more ignorable files
176 | *.nuget.props
177 | *.nuget.targets
178 |
179 | # Microsoft Azure Build Output
180 | csx/
181 | *.build.csdef
182 |
183 | # Microsoft Azure Emulator
184 | ecf/
185 | rcf/
186 |
187 | # Windows Store app package directories and files
188 | AppPackages/
189 | BundleArtifacts/
190 | Package.StoreAssociation.xml
191 | _pkginfo.txt
192 |
193 | # Visual Studio cache files
194 | # files ending in .cache can be ignored
195 | *.[Cc]ache
196 | # but keep track of directories ending in .cache
197 | !*.[Cc]ache/
198 |
199 | # Others
200 | ClientBin/
201 | ~$*
202 | *~
203 | *.dbmdl
204 | *.dbproj.schemaview
205 | *.jfm
206 | *.pfx
207 | *.publishsettings
208 | orleans.codegen.cs
209 |
210 | # Since there are multiple workflows, uncomment next line to ignore bower_components
211 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
212 | #bower_components/
213 |
214 | # RIA/Silverlight projects
215 | Generated_Code/
216 |
217 | # Backup & report files from converting an old project file
218 | # to a newer Visual Studio version. Backup files are not needed,
219 | # because we have git ;-)
220 | _UpgradeReport_Files/
221 | Backup*/
222 | UpgradeLog*.XML
223 | UpgradeLog*.htm
224 |
225 | # SQL Server files
226 | *.mdf
227 | *.ldf
228 | *.ndf
229 |
230 | # Business Intelligence projects
231 | *.rdl.data
232 | *.bim.layout
233 | *.bim_*.settings
234 |
235 | # Microsoft Fakes
236 | FakesAssemblies/
237 |
238 | # GhostDoc plugin setting file
239 | *.GhostDoc.xml
240 |
241 | # Node.js Tools for Visual Studio
242 | .ntvs_analysis.dat
243 | node_modules/
244 |
245 | # Typescript v1 declaration files
246 | typings/
247 |
248 | # Visual Studio 6 build log
249 | *.plg
250 |
251 | # Visual Studio 6 workspace options file
252 | *.opt
253 |
254 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
255 | *.vbw
256 |
257 | # Visual Studio LightSwitch build output
258 | **/*.HTMLClient/GeneratedArtifacts
259 | **/*.DesktopClient/GeneratedArtifacts
260 | **/*.DesktopClient/ModelManifest.xml
261 | **/*.Server/GeneratedArtifacts
262 | **/*.Server/ModelManifest.xml
263 | _Pvt_Extensions
264 |
265 | # Paket dependency manager
266 | .paket/paket.exe
267 | paket-files/
268 |
269 | # FAKE - F# Make
270 | .fake/
271 |
272 | # JetBrains Rider
273 | .idea/
274 | *.sln.iml
275 |
276 | # CodeRush
277 | .cr/
278 |
279 | # Python Tools for Visual Studio (PTVS)
280 | __pycache__/
281 | *.pyc
282 |
283 | # Cake - Uncomment if you are using it
284 | # tools/**
285 | # !tools/packages.config
286 |
287 | # Telerik's JustMock configuration file
288 | *.jmconfig
289 |
290 | # BizTalk build output
291 | *.btp.cs
292 | *.btm.cs
293 | *.odx.cs
294 | *.xsd.cs
295 |
296 | # End of https://www.gitignore.io/api/visualstudio
297 | DynamicData/project.lock.json
--------------------------------------------------------------------------------
/FlatStreamToHierarchy/FlatStreamToHierarchy.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {BE697BE5-3261-4F60-AFE2-40EF5CC1E198}
8 | WinExe
9 | Properties
10 | FlatStreamToHierarchy
11 | FlatStreamToHierarchy
12 | v4.5
13 | 512
14 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
15 | 4
16 |
17 |
18 | AnyCPU
19 | true
20 | full
21 | false
22 | bin\Debug\
23 | DEBUG;TRACE
24 | prompt
25 | 4
26 |
27 |
28 | AnyCPU
29 | pdbonly
30 | true
31 | bin\Release\
32 | TRACE
33 | prompt
34 | 4
35 |
36 |
37 |
38 | ..\packages\DynamicData.6.1.1.2369\lib\net45\DynamicData.dll
39 |
40 |
41 | False
42 | ..\packages\MahApps.Metro.1.1.2.0\lib\net45\MahApps.Metro.dll
43 |
44 |
45 | ..\packages\MaterialDesignColors.0.0.0.0\lib\net45\MaterialDesignColors.dll
46 |
47 |
48 | ..\packages\MaterialDesignThemes.0.0.0.77\lib\net45\MaterialDesignThemes.Wpf.dll
49 |
50 |
51 |
52 |
53 | ..\packages\System.Reactive.Core.3.1.1\lib\net45\System.Reactive.Core.dll
54 |
55 |
56 | ..\packages\System.Reactive.Interfaces.3.1.1\lib\net45\System.Reactive.Interfaces.dll
57 |
58 |
59 | ..\packages\System.Reactive.Linq.3.1.1\lib\net45\System.Reactive.Linq.dll
60 |
61 |
62 | ..\packages\System.Reactive.PlatformServices.3.1.1\lib\net45\System.Reactive.PlatformServices.dll
63 |
64 |
65 | ..\packages\System.Reactive.Windows.Threading.3.1.1\lib\net45\System.Reactive.Windows.Threading.dll
66 |
67 |
68 |
69 | ..\packages\MahApps.Metro.1.1.2.0\lib\net45\System.Windows.Interactivity.dll
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | 4.0
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 | MSBuild:Compile
86 | Designer
87 |
88 |
89 |
90 |
91 |
92 |
93 | Code
94 |
95 |
96 |
97 | MSBuild:Compile
98 | Designer
99 |
100 |
101 | App.xaml
102 | Code
103 |
104 |
105 | MainWindow.xaml
106 | Code
107 |
108 |
109 | MSBuild:Compile
110 | Designer
111 |
112 |
113 | MSBuild:Compile
114 | Designer
115 |
116 |
117 | Designer
118 | MSBuild:Compile
119 |
120 |
121 |
122 |
123 | Code
124 |
125 |
126 | True
127 | True
128 | Resources.resx
129 |
130 |
131 | True
132 | Settings.settings
133 | True
134 |
135 |
136 | ResXFileCodeGenerator
137 | Resources.Designer.cs
138 |
139 |
140 |
141 | SettingsSingleFileGenerator
142 | Settings.Designer.cs
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
157 |
--------------------------------------------------------------------------------
/FlatStreamToHierarchy/MainWindow.xaml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
64 |
65 |
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 |
257 |
259 |
260 |
--------------------------------------------------------------------------------