├── .gitattributes ├── .gitignore ├── LiteTreeView ├── HaveChildrenCollectionWrapper.cs ├── IHaveChildren.cs ├── LiteTreeView.csproj ├── LiteTreeViewControl.cs ├── LiteTreeViewItem.cs ├── LiteTreeViewItemViewModel.cs ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ ├── Resources.resx │ ├── Settings.Designer.cs │ └── Settings.settings └── Themes │ └── Generic.xaml ├── LiteTreeViewTestApp ├── App.config ├── App.xaml ├── App.xaml.cs ├── DummyObject.cs ├── LiteTreeViewTestApp.csproj ├── MainWindow.xaml ├── MainWindow.xaml.cs └── Properties │ ├── Annotations.cs │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ ├── Resources.resx │ ├── Settings.Designer.cs │ └── Settings.settings ├── README.md └── WPFLiteTree.sln /.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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | 9 | # Build results 10 | 11 | [Dd]ebug/ 12 | [Rr]elease/ 13 | x64/ 14 | build/ 15 | [Bb]in/ 16 | [Oo]bj/ 17 | 18 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets 19 | !packages/*/build/ 20 | 21 | # MSTest test Results 22 | [Tt]est[Rr]esult*/ 23 | [Bb]uild[Ll]og.* 24 | 25 | *_i.c 26 | *_p.c 27 | *.ilk 28 | *.meta 29 | *.obj 30 | *.pch 31 | *.pdb 32 | *.pgc 33 | *.pgd 34 | *.rsp 35 | *.sbr 36 | *.tlb 37 | *.tli 38 | *.tlh 39 | *.tmp 40 | *.tmp_proj 41 | *.log 42 | *.vspscc 43 | *.vssscc 44 | .builds 45 | *.pidb 46 | *.log 47 | *.scc 48 | 49 | # Visual C++ cache files 50 | ipch/ 51 | *.aps 52 | *.ncb 53 | *.opensdf 54 | *.sdf 55 | *.cachefile 56 | 57 | # Visual Studio profiler 58 | *.psess 59 | *.vsp 60 | *.vspx 61 | 62 | # Guidance Automation Toolkit 63 | *.gpState 64 | 65 | # ReSharper is a .NET coding add-in 66 | _ReSharper*/ 67 | *.[Rr]e[Ss]harper 68 | 69 | # TeamCity is a build add-in 70 | _TeamCity* 71 | 72 | # DotCover is a Code Coverage Tool 73 | *.dotCover 74 | 75 | # NCrunch 76 | *.ncrunch* 77 | .*crunch*.local.xml 78 | 79 | # Installshield output folder 80 | [Ee]xpress/ 81 | 82 | # DocProject is a documentation generator add-in 83 | DocProject/buildhelp/ 84 | DocProject/Help/*.HxT 85 | DocProject/Help/*.HxC 86 | DocProject/Help/*.hhc 87 | DocProject/Help/*.hhk 88 | DocProject/Help/*.hhp 89 | DocProject/Help/Html2 90 | DocProject/Help/html 91 | 92 | # Click-Once directory 93 | publish/ 94 | 95 | # Publish Web Output 96 | *.Publish.xml 97 | 98 | # NuGet Packages Directory 99 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 100 | #packages/ 101 | 102 | # Windows Azure Build Output 103 | csx 104 | *.build.csdef 105 | 106 | # Windows Store app package directory 107 | AppPackages/ 108 | 109 | # Others 110 | sql/ 111 | *.Cache 112 | ClientBin/ 113 | [Ss]tyle[Cc]op.* 114 | ~$* 115 | *~ 116 | *.dbmdl 117 | *.[Pp]ublish.xml 118 | *.pfx 119 | *.publishsettings 120 | 121 | # RIA/Silverlight projects 122 | Generated_Code/ 123 | 124 | # Backup & report files from converting an old project file to a newer 125 | # Visual Studio version. Backup files are not needed, because we have git ;-) 126 | _UpgradeReport_Files/ 127 | Backup*/ 128 | UpgradeLog*.XML 129 | UpgradeLog*.htm 130 | 131 | # SQL Server files 132 | App_Data/*.mdf 133 | App_Data/*.ldf 134 | 135 | 136 | #LightSwitch generated files 137 | GeneratedArtifacts/ 138 | _Pvt_Extensions/ 139 | ModelManifest.xml 140 | 141 | # ========================= 142 | # Windows detritus 143 | # ========================= 144 | 145 | # Windows image file caches 146 | Thumbs.db 147 | ehthumbs.db 148 | 149 | # Folder config file 150 | Desktop.ini 151 | 152 | # Recycle Bin used on file shares 153 | $RECYCLE.BIN/ 154 | 155 | # Mac desktop service store files 156 | .DS_Store 157 | -------------------------------------------------------------------------------- /LiteTreeView/HaveChildrenCollectionWrapper.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | 3 | namespace LiteTreeView 4 | { 5 | class HaveChildrenCollectionWrapper : IHaveChildren 6 | { 7 | public HaveChildrenCollectionWrapper(IEnumerable collection) 8 | { 9 | Children = collection; 10 | } 11 | public IEnumerable Children { get; private set; } 12 | } 13 | } -------------------------------------------------------------------------------- /LiteTreeView/IHaveChildren.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | 3 | namespace LiteTreeView 4 | { 5 | public interface IHaveChildren 6 | { 7 | IEnumerable Children { get; } 8 | } 9 | } -------------------------------------------------------------------------------- /LiteTreeView/LiteTreeView.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {2034C20A-4BE1-479D-B1A5-223A6CFC4D7C} 8 | library 9 | Properties 10 | LiteTreeView 11 | LiteTreeView 12 | v4.5 13 | 512 14 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 15 | 4 16 | 17 | 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 4.0 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | MSBuild:Compile 52 | Designer 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | Code 63 | 64 | 65 | True 66 | True 67 | Resources.resx 68 | 69 | 70 | True 71 | Settings.settings 72 | True 73 | 74 | 75 | ResXFileCodeGenerator 76 | Resources.Designer.cs 77 | 78 | 79 | SettingsSingleFileGenerator 80 | Settings.Designer.cs 81 | 82 | 83 | 84 | 85 | 92 | -------------------------------------------------------------------------------- /LiteTreeView/LiteTreeViewControl.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.ObjectModel; 3 | using System.Windows; 4 | using System.Windows.Controls; 5 | 6 | namespace LiteTreeView 7 | { 8 | public class LiteTreeViewControl : ListBox 9 | { 10 | #region HaveChildrenCollectionWrapper 11 | 12 | class HaveChildrenCollectionWrapper : IHaveChildren 13 | { 14 | public HaveChildrenCollectionWrapper(IEnumerable collection) 15 | { 16 | Children = collection; 17 | } 18 | 19 | public IEnumerable Children { get; private set; } 20 | } 21 | 22 | #endregion 23 | 24 | public LiteTreeViewControl() 25 | { 26 | SetResourceReference(StyleProperty, typeof(ListBox)); 27 | } 28 | static LiteTreeViewControl() 29 | { 30 | //var oldMetadata=(FrameworkPropertyMetadata)ListBox.SelectedItemProperty.GetMetadata(typeof (ListBox)); 31 | //) 32 | 33 | ListBox.SelectedItemProperty.OverrideMetadata(typeof(LiteTreeViewControl),new FrameworkPropertyMetadata(null,OnSelecteItemChanged )); 34 | } 35 | 36 | private object _lastSelectedItem; 37 | private static void OnSelecteItemChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs) 38 | { 39 | var listBox = (dependencyObject as LiteTreeViewControl); 40 | 41 | if (dependencyPropertyChangedEventArgs.NewValue!=null) 42 | { 43 | listBox._lastSelectedItem = dependencyPropertyChangedEventArgs.NewValue; 44 | } 45 | } 46 | 47 | public ObservableCollection InternalCollection { get; set; } 48 | 49 | public IEnumerable MyItemsSource 50 | { 51 | get { return (IEnumerable)GetValue(MyItemsSourceProperty); } 52 | set { SetValue(MyItemsSourceProperty, value); } 53 | } 54 | 55 | // Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc... 56 | public static readonly DependencyProperty MyItemsSourceProperty = 57 | DependencyProperty.Register("MyItemsSource", typeof(IEnumerable), typeof(LiteTreeViewControl), new PropertyMetadata(null, MyItemsSourceChanged)); 58 | 59 | private static void MyItemsSourceChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs) 60 | { 61 | var treeListBox = dependencyObject as LiteTreeViewControl; 62 | treeListBox.MyItemsSourceChanged(eventArgs); 63 | } 64 | 65 | void MyItemsSourceChanged(DependencyPropertyChangedEventArgs eventArgs) 66 | { 67 | var observableCollectionAdv = new ObservableCollection(); 68 | if (Root != null) 69 | { 70 | Root.Dispose(); 71 | } 72 | if (eventArgs.NewValue != null) 73 | { 74 | var inputCollection = eventArgs.NewValue as IEnumerable; 75 | Root = new LiteTreeViewItemViewModel { IsOpen = true, Level = -1 }; 76 | Root.InnerObject = new HaveChildrenCollectionWrapper(inputCollection); 77 | Root.FillTreeList(observableCollectionAdv); 78 | } 79 | InternalCollection = observableCollectionAdv; 80 | ItemsSource = InternalCollection; 81 | } 82 | 83 | LiteTreeViewItemViewModel Root { get; set; } 84 | 85 | protected override DependencyObject GetContainerForItemOverride() 86 | { 87 | return new LiteTreeViewItem(); 88 | } 89 | 90 | protected override bool IsItemItsOwnContainerOverride(object item) 91 | { 92 | return item is LiteTreeViewItem; 93 | } 94 | 95 | protected override void PrepareContainerForItemOverride(DependencyObject element, object item) 96 | { 97 | var ti = element as LiteTreeViewItem; 98 | var vm = item as LiteTreeViewItemViewModel; 99 | 100 | if (ti != null&&vm!=null) 101 | { 102 | if (this.ItemContainerStyle != null && ti.Style == null) 103 | { 104 | ti.SetValue(FrameworkElement.StyleProperty, this.ItemContainerStyle); 105 | } 106 | if (this.ItemTemplate != null && ti.ContentTemplate == null) 107 | { 108 | ti.SetValue(ContentControl.ContentTemplateProperty, this.ItemTemplate); 109 | } 110 | if (this.ItemTemplateSelector != null && ti.ContentTemplateSelector == null) 111 | { 112 | ti.SetValue(ContentControl.ContentTemplateSelectorProperty, this.ItemTemplateSelector); 113 | } 114 | ti.Content = vm.InnerObject; 115 | ti.DataContext = vm.InnerObject; 116 | 117 | ti.VM = vm; 118 | } 119 | //base.PrepareContainerForItemOverride(element, item); 120 | 121 | 122 | } 123 | 124 | internal void RefreshSelectedItem() 125 | { 126 | if (SelectedItem!=_lastSelectedItem) 127 | { 128 | SelectedItem = _lastSelectedItem; 129 | } 130 | } 131 | } 132 | } -------------------------------------------------------------------------------- /LiteTreeView/LiteTreeViewItem.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.ComponentModel; 3 | using System.Windows; 4 | using System.Windows.Controls; 5 | using System.Windows.Data; 6 | 7 | namespace LiteTreeView 8 | { 9 | public class LiteTreeViewItem : ListBoxItem, INotifyPropertyChanged 10 | { 11 | static class CollectionExtensions 12 | { 13 | public static void RemoveRange(IList source, int index, int count) 14 | { 15 | while (count > 0 && source.Count > index) 16 | { 17 | source.RemoveAt(index); 18 | count--; 19 | } 20 | } 21 | 22 | public static void InsertRange(IList source, int index, IEnumerable collection) 23 | { 24 | int i = 0; 25 | foreach (var item in collection) 26 | { 27 | source.Insert(index + i, item); 28 | 29 | i++; 30 | } 31 | } 32 | } 33 | 34 | 35 | 36 | private LiteTreeViewItemViewModel _vm; 37 | 38 | public LiteTreeViewItem() 39 | { 40 | } 41 | 42 | static LiteTreeViewItem() 43 | { 44 | DefaultStyleKeyProperty.OverrideMetadata(typeof(LiteTreeViewItem), new FrameworkPropertyMetadata(typeof(LiteTreeViewItem))); 45 | DataContextProperty.OverrideMetadata(typeof(LiteTreeViewItem),new FrameworkPropertyMetadata((o,e)=>{},DataContextCoerceValueCallback)); 46 | } 47 | 48 | private static object DataContextCoerceValueCallback(DependencyObject dependencyObject, object baseValue) 49 | { 50 | if (baseValue!=null) 51 | { 52 | var vm=baseValue as LiteTreeViewItemViewModel; 53 | if (vm!=null) 54 | { 55 | return vm.InnerObject; 56 | } 57 | } 58 | return baseValue; 59 | } 60 | 61 | public Thickness LevelMargin 62 | { 63 | get { return VM.LevelMargin; } 64 | } 65 | 66 | 67 | 68 | public int Level 69 | { 70 | get { return VM.Level; } 71 | 72 | } 73 | 74 | //public IList Children { get; set; } 75 | 76 | public bool HasChildren 77 | { 78 | get { return VM!=null&&VM.HasChildren; } 79 | } 80 | 81 | public Visibility HasChildrenVisibility 82 | { 83 | get { return VM != null? VM.HasChildrenVisibility:Visibility.Hidden; } 84 | } 85 | 86 | public bool IsOpen 87 | { 88 | get { return (bool)GetValue(IsOpenProperty); } 89 | set { SetValue(IsOpenProperty, value); } 90 | } 91 | 92 | // Using a DependencyProperty as the backing store for IsOpen. This enables animation, styling, binding, etc... 93 | public static readonly DependencyProperty IsOpenProperty = 94 | DependencyProperty.Register("IsOpen", typeof(bool), typeof(LiteTreeViewItem), new PropertyMetadata(false, IsOpenChanged)); 95 | 96 | public LiteTreeViewItemViewModel VM 97 | { 98 | get { return _vm; } 99 | set 100 | { 101 | if (_vm!=null) 102 | { 103 | BindingOperations.ClearBinding(_vm, LiteTreeViewItemViewModel.IsOpenProperty); 104 | _vm.PropertyChanged -= VmOnPropertyChanged; 105 | } 106 | _vm = value; 107 | 108 | if (_vm!=null) 109 | { 110 | Binding myBinding = new Binding("IsOpen") { Mode = BindingMode.TwoWay }; 111 | myBinding.Source = this; 112 | BindingOperations.SetBinding(_vm, LiteTreeViewItemViewModel.IsOpenProperty, myBinding); 113 | 114 | _vm.PropertyChanged += VmOnPropertyChanged; 115 | 116 | OnPropertyChanged("HasChildren"); 117 | OnPropertyChanged("HasChildrenVisibility"); 118 | OnPropertyChanged("IsOpen"); 119 | OnPropertyChanged("Level"); 120 | OnPropertyChanged("LevelMargin"); 121 | } 122 | 123 | } 124 | } 125 | 126 | private void VmOnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs) 127 | { 128 | OnPropertyChanged(propertyChangedEventArgs.PropertyName); 129 | } 130 | 131 | private static void IsOpenChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs) 132 | { 133 | var liteTreeViewControl = (ItemsControl.ItemsControlFromItemContainer(dependencyObject) as LiteTreeViewControl); 134 | if (liteTreeViewControl != null) 135 | { 136 | liteTreeViewControl.RefreshSelectedItem(); 137 | 138 | } 139 | } 140 | 141 | 142 | 143 | 144 | public event PropertyChangedEventHandler PropertyChanged; 145 | 146 | protected virtual void OnPropertyChanged(string propertyName) 147 | { 148 | var handler = PropertyChanged; 149 | if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); 150 | } 151 | 152 | 153 | 154 | } 155 | } -------------------------------------------------------------------------------- /LiteTreeView/LiteTreeViewItemViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.Collections.Specialized; 5 | using System.ComponentModel; 6 | using System.Linq; 7 | using System.Windows; 8 | using System.Windows.Controls; 9 | 10 | namespace LiteTreeView 11 | { 12 | public class LiteTreeViewItemViewModel : DependencyObject, INotifyPropertyChanged, IDisposable, IWeakEventListener 13 | { 14 | static class CollectionExtensions 15 | { 16 | public static void RemoveRange(IList source, int index, int count) 17 | { 18 | while (count > 0 && source.Count > index) 19 | { 20 | source.RemoveAt(index); 21 | count--; 22 | } 23 | } 24 | 25 | public static void InsertRange(IList source, int index, IEnumerable collection) 26 | { 27 | int i = 0; 28 | foreach (var item in collection) 29 | { 30 | source.Insert(index + i, item); 31 | 32 | i++; 33 | } 34 | } 35 | } 36 | 37 | private const int LEVEL_LEFT_MARGIN = 15; 38 | 39 | private int _level; 40 | private object _innerObject; 41 | private bool _isDisposed; 42 | IDictionary _treeItemsToItems = new Dictionary(); 43 | IDictionary _itemsToTreeItems = new Dictionary(); 44 | 45 | public LiteTreeViewItemViewModel() 46 | { 47 | Children = new List(); 48 | } 49 | 50 | static LiteTreeViewItemViewModel() 51 | { 52 | //DefaultStyleKeyProperty.OverrideMetadata(typeof(LiteTreeViewItemViewModel), new FrameworkPropertyMetadata(typeof(LiteTreeViewItemViewModel))); 53 | } 54 | 55 | public Thickness LevelMargin 56 | { 57 | get { return new Thickness(LEVEL_LEFT_MARGIN * Level, 0, 0, 0); } 58 | } 59 | 60 | 61 | public int Level 62 | { 63 | get { return _level; } 64 | set 65 | { 66 | if (value == _level) return; 67 | _level = value; 68 | OnPropertyChanged("Level"); 69 | OnPropertyChanged("LevelMargin"); 70 | } 71 | } 72 | 73 | public IList Children { get; set; } 74 | 75 | public bool HasChildren 76 | { 77 | get { return Children != null && Children.Count > 0; } 78 | } 79 | 80 | public Visibility HasChildrenVisibility 81 | { 82 | get { return HasChildren ? Visibility.Visible : Visibility.Hidden; } 83 | } 84 | 85 | public bool IsOpen 86 | { 87 | get { return (bool)GetValue(IsOpenProperty); } 88 | set { SetValue(IsOpenProperty, value); } 89 | } 90 | 91 | // Using a DependencyProperty as the backing store for IsOpen. This enables animation, styling, binding, etc... 92 | public static readonly DependencyProperty IsOpenProperty = 93 | DependencyProperty.Register("IsOpen", typeof(bool), typeof(LiteTreeViewItemViewModel), new PropertyMetadata(false, IsOpenChanged)); 94 | 95 | private static void IsOpenChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs) 96 | { 97 | var newVal = (bool)dependencyPropertyChangedEventArgs.NewValue; 98 | 99 | var myTreeItem = dependencyObject as LiteTreeViewItemViewModel; 100 | myTreeItem.IsOpenChanged(newVal); 101 | } 102 | 103 | 104 | void IsOpenChanged(bool newValue) 105 | { 106 | if (newValue) 107 | { 108 | if (IsTopLevel() || MyParent.IsOpen) 109 | { 110 | FillTreeList(ContainerList); 111 | } 112 | 113 | } 114 | else 115 | { 116 | RemoveFromTreeList(); 117 | } 118 | var liteTreeViewControl = (ItemsControl.ItemsControlFromItemContainer(this) as LiteTreeViewControl); 119 | if (liteTreeViewControl!=null) 120 | { 121 | liteTreeViewControl.RefreshSelectedItem(); 122 | 123 | } 124 | } 125 | 126 | public LiteTreeViewItemViewModel MyParent { get; set; } 127 | 128 | private bool IsTopLevel() 129 | { 130 | return MyParent == null; 131 | } 132 | 133 | public void Add(LiteTreeViewItemViewModel child, int index = -1) 134 | { 135 | if (Children == null) 136 | { 137 | Children = new List(); 138 | } 139 | if (index == -1) 140 | { 141 | Children.Add(child); 142 | 143 | } 144 | else 145 | { 146 | Children.Insert(index, child); 147 | } 148 | child.MyParent = this; 149 | child.Level = this.Level + 1; 150 | } 151 | public void InvalidateVisibility(bool isRootChange = true) 152 | { 153 | OnPropertyChanged("IsVisible"); 154 | OnPropertyChanged("Visibility"); 155 | if (HasChildren) 156 | { 157 | if (IsOpen || isRootChange) 158 | { 159 | foreach (var child in Children) 160 | { 161 | child.InvalidateVisibility(false); 162 | } 163 | } 164 | 165 | } 166 | } 167 | public event PropertyChangedEventHandler PropertyChanged; 168 | 169 | protected virtual void OnPropertyChanged(string propertyName) 170 | { 171 | var handler = PropertyChanged; 172 | if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); 173 | } 174 | 175 | public void FillTreeList(ObservableCollection items) 176 | { 177 | int index = -1; 178 | FillTreeList(items, ref index); 179 | } 180 | 181 | public void FillTreeList(ObservableCollection items, ref int myIndex) 182 | { 183 | if (items == null) 184 | { 185 | return; 186 | } 187 | ContainerList = items; 188 | if (HasChildren) 189 | { 190 | FillTreeList(items, Children, ref myIndex); 191 | } 192 | } 193 | 194 | void FillTreeList(ObservableCollection items, IList children, ref int myIndex) 195 | { 196 | if (items == null) 197 | { 198 | return; 199 | } 200 | ContainerList = items; 201 | if (children != null && children.Any()) 202 | { 203 | if (myIndex == -1) 204 | { 205 | myIndex = items.IndexOf(this); 206 | } 207 | if (IsOpen) 208 | { 209 | CollectionExtensions.InsertRange(items, myIndex + 1, children); 210 | 211 | foreach (var child in children) 212 | { 213 | ++myIndex; 214 | //items.Insert(myIndex, child); 215 | child.FillTreeList(items, ref myIndex); 216 | } 217 | } 218 | } 219 | } 220 | 221 | public ObservableCollection ContainerList { get; set; } 222 | 223 | public object InnerObject 224 | { 225 | get { return _innerObject; } 226 | set 227 | { 228 | Clear(); 229 | DetachFromCollectionChanged(_innerObject); 230 | _innerObject = value; 231 | 232 | var parent = _innerObject as IHaveChildren; 233 | if (parent != null) 234 | { 235 | foreach (var child in parent.Children) 236 | { 237 | AddChild(child); 238 | } 239 | 240 | var notifyCollectionChanged = parent.Children as INotifyCollectionChanged; 241 | if (notifyCollectionChanged != null) 242 | { 243 | CollectionChangedEventManager.AddListener(notifyCollectionChanged, this); 244 | } 245 | } 246 | } 247 | } 248 | 249 | private void DetachFromCollectionChanged(object innerObj) 250 | { 251 | var oldInnerObject = innerObj as IHaveChildren; 252 | if (oldInnerObject != null) 253 | { 254 | var notifyCollectionChanged = oldInnerObject.Children as INotifyCollectionChanged; 255 | if (notifyCollectionChanged != null) 256 | { 257 | CollectionChangedEventManager.RemoveListener(notifyCollectionChanged, this); 258 | } 259 | } 260 | } 261 | 262 | private LiteTreeViewItemViewModel AddChild(object child, int index = -1) 263 | { 264 | var myTreeItem = new LiteTreeViewItemViewModel(); 265 | Add(myTreeItem, index); 266 | myTreeItem.InnerObject = child; 267 | _itemsToTreeItems[child] = myTreeItem; 268 | _treeItemsToItems[myTreeItem] = child; 269 | return myTreeItem; 270 | } 271 | 272 | void RemoveChild(object child) 273 | { 274 | var treeItem = _itemsToTreeItems[child]; 275 | treeItem.RemoveFromTreeList(false); 276 | Children.Remove(treeItem); 277 | _itemsToTreeItems.Remove(child); 278 | _treeItemsToItems.Remove(treeItem); 279 | if (IsOpen) 280 | { 281 | ContainerList.Remove(treeItem); 282 | } 283 | treeItem.Dispose(); 284 | } 285 | 286 | private void Dispose(bool disposing) 287 | { 288 | if (_isDisposed) 289 | { 290 | return; 291 | } 292 | if (disposing) 293 | { 294 | GC.SuppressFinalize(this); 295 | } 296 | DetachFromCollectionChanged(_innerObject); 297 | foreach (var child in Children) 298 | { 299 | child.Dispose(); 300 | } 301 | _isDisposed = true; 302 | } 303 | 304 | public void Dispose() 305 | { 306 | Dispose(true); 307 | } 308 | 309 | ~LiteTreeViewItemViewModel() 310 | { 311 | Dispose(false); 312 | } 313 | 314 | public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e) 315 | { 316 | var args = e as NotifyCollectionChangedEventArgs; 317 | 318 | if (args == null) return true; 319 | 320 | CollectionChanged(sender, args); 321 | 322 | return true; 323 | } 324 | 325 | private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) 326 | { 327 | switch (e.Action) 328 | { 329 | case NotifyCollectionChangedAction.Add: 330 | InsertAddedItems(e); 331 | break; 332 | 333 | case NotifyCollectionChangedAction.Remove: 334 | foreach (var item in e.OldItems) 335 | { 336 | RemoveChild(item); 337 | } 338 | break; 339 | 340 | case NotifyCollectionChangedAction.Move: 341 | case NotifyCollectionChangedAction.Replace: 342 | case NotifyCollectionChangedAction.Reset: 343 | InnerObject = InnerObject;//simulate change of inner object to reset all 344 | if (IsOpen) 345 | { 346 | IsOpenChanged(true); 347 | } 348 | break; 349 | } 350 | 351 | OnPropertyChanged("HasChildren"); 352 | OnPropertyChanged("HasChildrenVisibility"); 353 | } 354 | 355 | private void Clear() 356 | { 357 | var myTreeItems = Children.ToArray(); 358 | foreach (var myTreeItem in myTreeItems) 359 | { 360 | RemoveChild(myTreeItem.InnerObject); 361 | } 362 | } 363 | 364 | private void InsertAddedItems(NotifyCollectionChangedEventArgs e) 365 | { 366 | if (e.NewItems != null) 367 | { 368 | int index = e.NewStartingIndex; 369 | int nextItemIndex = index; 370 | 371 | if (!HasChildren) 372 | { 373 | //first child, should be in the treeitem place 374 | nextItemIndex = -1; 375 | } 376 | else if (index == 0) 377 | { 378 | nextItemIndex = -1; 379 | } 380 | else if (index == Children.Count) 381 | { 382 | nextItemIndex = ContainerList.IndexOf(this) + CountVisibleDescendatns(); 383 | } 384 | else 385 | { 386 | var nextItem = Children[index]; 387 | nextItemIndex = ContainerList.IndexOf(nextItem) - 1; 388 | } 389 | 390 | var newChildren = new List(); 391 | foreach (object obj in e.NewItems) 392 | { 393 | newChildren.Add(AddChild(obj, index)); 394 | index++; 395 | } 396 | FillTreeList(ContainerList, newChildren, ref nextItemIndex); 397 | } 398 | } 399 | 400 | public void RemoveFromTreeList(bool isRoot = true) 401 | { 402 | if (HasChildren) 403 | { 404 | if (IsOpen || isRoot) 405 | { 406 | //var myIndex = ContainerList.IndexOf(this); 407 | //ContainerList.RemoveRange(myIndex+1,CountVisibleDescendatns()); 408 | foreach (var child in Children) 409 | { 410 | 411 | ContainerList.Remove(child); 412 | child.RemoveFromTreeList(false); 413 | } 414 | } 415 | } 416 | } 417 | 418 | int CountVisibleDescendatns() 419 | { 420 | int count = 0; 421 | if (HasChildren && IsOpen) 422 | { 423 | count += Children.Count; 424 | foreach (var child in Children.Where(c => c.HasChildren)) 425 | { 426 | count += child.CountVisibleDescendatns(); 427 | } 428 | } 429 | return count; 430 | } 431 | 432 | public void ExpandAll() 433 | { 434 | if (HasChildren) 435 | { 436 | foreach (var child in Children) 437 | { 438 | child.ExpandAll(); 439 | } 440 | } 441 | IsOpen = true; 442 | } 443 | 444 | 445 | 446 | 447 | } 448 | } -------------------------------------------------------------------------------- /LiteTreeView/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("LiteTreeView")] 11 | [assembly: AssemblyDescription("")] 12 | [assembly: AssemblyConfiguration("")] 13 | [assembly: AssemblyCompany("")] 14 | [assembly: AssemblyProduct("LiteTreeView")] 15 | [assembly: AssemblyCopyright("Copyright © 2014")] 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 | -------------------------------------------------------------------------------- /LiteTreeView/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.35317 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 LiteTreeView.Properties { 12 | 13 | 14 | /// 15 | /// A strongly-typed resource class, for looking up localized strings, etc. 16 | /// 17 | // This class was auto-generated by the StronglyTypedResourceBuilder 18 | // class via a tool like ResGen or Visual Studio. 19 | // To add or remove a member, edit your .ResX file then rerun ResGen 20 | // with the /str option, or rebuild your VS project. 21 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 22 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 23 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 24 | internal class Resources { 25 | 26 | private static global::System.Resources.ResourceManager resourceMan; 27 | 28 | private static global::System.Globalization.CultureInfo resourceCulture; 29 | 30 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 31 | internal Resources() { 32 | } 33 | 34 | /// 35 | /// Returns the cached ResourceManager instance used by this class. 36 | /// 37 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 38 | internal static global::System.Resources.ResourceManager ResourceManager { 39 | get { 40 | if ((resourceMan == null)) { 41 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("LiteTreeView.Properties.Resources", typeof(Resources).Assembly); 42 | resourceMan = temp; 43 | } 44 | return resourceMan; 45 | } 46 | } 47 | 48 | /// 49 | /// Overrides the current thread's CurrentUICulture property for all 50 | /// resource lookups using this strongly typed resource class. 51 | /// 52 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 53 | internal static global::System.Globalization.CultureInfo Culture { 54 | get { 55 | return resourceCulture; 56 | } 57 | set { 58 | resourceCulture = value; 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /LiteTreeView/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 | -------------------------------------------------------------------------------- /LiteTreeView/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.35317 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 LiteTreeView.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 | -------------------------------------------------------------------------------- /LiteTreeView/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /LiteTreeView/Themes/Generic.xaml: -------------------------------------------------------------------------------- 1 |  5 | 43 | 45 | 47 | 48 | 49 | 88 | -------------------------------------------------------------------------------- /LiteTreeViewTestApp/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /LiteTreeViewTestApp/App.xaml: -------------------------------------------------------------------------------- 1 |  5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /LiteTreeViewTestApp/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 TreeAsListBox 10 | { 11 | /// 12 | /// Interaction logic for App.xaml 13 | /// 14 | public partial class App : Application 15 | { 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /LiteTreeViewTestApp/DummyObject.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.ComponentModel; 5 | using System.Runtime.CompilerServices; 6 | using LiteTreeView; 7 | using TreeAsListBox.Annotations; 8 | 9 | namespace TreeAsListBox 10 | { 11 | public class DummyObject : IHaveChildren,INotifyPropertyChanged 12 | { 13 | private ObservableCollection _children; 14 | private bool _isItOpen; 15 | 16 | public DummyObject() 17 | { 18 | ChildrenList = new ObservableCollection(); 19 | } 20 | public string Name { get; set; } 21 | 22 | public bool IsItOpen 23 | { 24 | get { return _isItOpen; } 25 | set 26 | { 27 | if (value.Equals(_isItOpen)) return; 28 | _isItOpen = value; 29 | OnPropertyChanged(); 30 | } 31 | } 32 | 33 | public IEnumerable Children 34 | { 35 | get { return ChildrenList; } 36 | } 37 | 38 | public ObservableCollection ChildrenList 39 | { 40 | get { return _children; } 41 | set { _children = value; } 42 | } 43 | 44 | public void Add(DummyObject child) 45 | { 46 | ChildrenList.Add(child); 47 | } 48 | 49 | public void ExpandAll() 50 | { 51 | 52 | foreach (var child in ChildrenList) 53 | { 54 | child.ExpandAll(); 55 | } 56 | 57 | IsItOpen = true; 58 | } 59 | 60 | public void CollapseAll() 61 | { 62 | 63 | foreach (var child in ChildrenList) 64 | { 65 | child.CollapseAll(); 66 | } 67 | 68 | IsItOpen = false; 69 | } 70 | 71 | public event PropertyChangedEventHandler PropertyChanged; 72 | 73 | [NotifyPropertyChangedInvocator] 74 | protected virtual void OnPropertyChanged( string propertyName = null) 75 | { 76 | var handler = PropertyChanged; 77 | if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /LiteTreeViewTestApp/LiteTreeViewTestApp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {3E2D8A4D-0402-4A9A-839D-A6E0E6027649} 8 | WinExe 9 | Properties 10 | TreeAsListBox 11 | TreeAsListBox 12 | v4.5 13 | 512 14 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 15 | 4 16 | 17 | 18 | 19 | AnyCPU 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | false 28 | 29 | 30 | AnyCPU 31 | pdbonly 32 | true 33 | bin\Release\ 34 | TRACE 35 | prompt 36 | 4 37 | false 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 4.0 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | MSBuild:Compile 57 | Designer 58 | 59 | 60 | 61 | MSBuild:Compile 62 | Designer 63 | 64 | 65 | App.xaml 66 | Code 67 | 68 | 69 | MainWindow.xaml 70 | Code 71 | 72 | 73 | 74 | 75 | 76 | Code 77 | 78 | 79 | True 80 | True 81 | Resources.resx 82 | 83 | 84 | True 85 | Settings.settings 86 | True 87 | 88 | 89 | ResXFileCodeGenerator 90 | Resources.Designer.cs 91 | 92 | 93 | SettingsSingleFileGenerator 94 | Settings.Designer.cs 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | {2034c20a-4be1-479d-b1a5-223a6cfc4d7c} 104 | LiteTreeView 105 | 106 | 107 | 108 | 115 | -------------------------------------------------------------------------------- /LiteTreeViewTestApp/MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 35 | 36 | 37 | 39 | 40 | 41 | 42 | 43 | 46 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /LiteTreeViewTestApp/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.ObjectModel; 2 | using System.ComponentModel; 3 | using System.Linq; 4 | using System.Windows; 5 | using TreeAsListBox; 6 | using TreeAsListBox.Annotations; 7 | 8 | namespace LiteTreeViewTestApp 9 | { 10 | /// 11 | /// Interaction logic for MainWindow.xaml 12 | /// 13 | public partial class MainWindow : Window,INotifyPropertyChanged 14 | { 15 | private ObservableCollection _items; 16 | 17 | public ObservableCollection Items 18 | { 19 | get { return _items; } 20 | set 21 | { 22 | if (Equals(value, _items)) return; 23 | _items = value; 24 | OnPropertyChanged(); 25 | } 26 | } 27 | 28 | public ObservableCollection DummyObjects 29 | { 30 | get { return _dummyObjects; } 31 | set { _dummyObjects = value; } 32 | } 33 | 34 | public ObservableCollection DummyObjects2 35 | { 36 | get { return _dummyObjects2; } 37 | set { _dummyObjects2 = value; } 38 | } 39 | 40 | private ObservableCollection _dummyObjects = new ObservableCollection(); 41 | private ObservableCollection _dummyObjects2 = new ObservableCollection(); 42 | public MainWindow() 43 | { 44 | FillItems(_dummyObjects); 45 | FillItems(_dummyObjects2); 46 | Items = _dummyObjects; 47 | InitializeComponent(); 48 | } 49 | 50 | private void FillItems(ObservableCollection collection) 51 | { 52 | for (int i = 0; i < 15; i++) 53 | { 54 | collection.Add(new DummyObject() { Name = "item" + i }); 55 | var parent = new DummyObject() 56 | { 57 | Name = "parent item" + i, 58 | }; 59 | collection.Add(parent); 60 | for (int j = 0; j < 2000; j++) 61 | { 62 | var child = new DummyObject() {Name = "child item " + j}; 63 | parent.Add(child); 64 | for (int k = 0; k < 4; k++) 65 | { 66 | var subChild = new DummyObject() {Name = "sub child item " + k}; 67 | child.Add(subChild); 68 | } 69 | } 70 | } 71 | } 72 | 73 | 74 | private void ButtonBase_OnClick(object sender, RoutedEventArgs e) 75 | { 76 | 77 | var items = Items.ToArray(); 78 | foreach (var item in items) 79 | { 80 | item.ExpandAll(); 81 | // Dispatcher.Invoke(() => item.ExpandAll(), DispatcherPriority.Background); 82 | 83 | 84 | } 85 | 86 | } 87 | 88 | private void AddItem(object sender, RoutedEventArgs e) 89 | { 90 | Items[1].Add(new DummyObject() { Name = "newone" }); 91 | } 92 | 93 | private void AddItem2(object sender, RoutedEventArgs e) 94 | { 95 | var newdummyObject = new DummyObject() { Name = "newone1" }; 96 | Items.First(x => x.ChildrenList.Any()).ChildrenList.Insert(2, newdummyObject); 97 | //dummyObject.Add(new DummyObject(){Name = "another new"}); 98 | foreach (DummyObject dummyObject in Items[1].Children) 99 | { 100 | //dummyObject.Add(new DummyObject() { Name = "another new" }); 101 | dummyObject.ChildrenList.Insert(0, new DummyObject() { Name = "another new" }); 102 | } 103 | } 104 | 105 | 106 | private void RemoveChild(object sender, RoutedEventArgs e) 107 | { 108 | Items[1].ChildrenList.Remove(Items[1].ChildrenList[1]); 109 | } 110 | 111 | //private void AddRange(object sender, RoutedEventArgs e) 112 | //{ 113 | // Items[1].ChildrenList.InsertRange(0, new[] 114 | // { 115 | // new DummyObject() {Name = "item1"}, 116 | // new DummyObject() {Name = "item2"} 117 | // }); 118 | //} 119 | 120 | private void CloseAll(object sender, RoutedEventArgs e) 121 | { 122 | var items = Items.ToArray(); 123 | foreach (var item in items) 124 | { 125 | item.CollapseAll(); 126 | // Dispatcher.Invoke(() => item.ExpandAll(), DispatcherPriority.Background); 127 | 128 | 129 | } 130 | } 131 | 132 | private void AddToRoot(object sender, RoutedEventArgs e) 133 | { 134 | Items.Insert(0,new DummyObject(){Name = "newroot"}); 135 | } 136 | 137 | private void ChangeSource(object sender, RoutedEventArgs e) 138 | { 139 | Items = Items==_dummyObjects ? _dummyObjects2 : _dummyObjects; 140 | } 141 | 142 | public event PropertyChangedEventHandler PropertyChanged; 143 | 144 | [NotifyPropertyChangedInvocator] 145 | protected virtual void OnPropertyChanged( string propertyName = null) 146 | { 147 | var handler = PropertyChanged; 148 | if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /LiteTreeViewTestApp/Properties/Annotations.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | #pragma warning disable 1591 4 | // ReSharper disable UnusedMember.Global 5 | // ReSharper disable UnusedParameter.Local 6 | // ReSharper disable MemberCanBePrivate.Global 7 | // ReSharper disable UnusedAutoPropertyAccessor.Global 8 | // ReSharper disable IntroduceOptionalParameters.Global 9 | // ReSharper disable MemberCanBeProtected.Global 10 | // ReSharper disable InconsistentNaming 11 | 12 | namespace TreeAsListBox.Annotations 13 | { 14 | /// 15 | /// Indicates that the value of the marked element could be null sometimes, 16 | /// so the check for null is necessary before its usage 17 | /// 18 | /// 19 | /// [CanBeNull] public object Test() { return null; } 20 | /// public void UseTest() { 21 | /// var p = Test(); 22 | /// var s = p.ToString(); // Warning: Possible 'System.NullReferenceException' 23 | /// } 24 | /// 25 | [AttributeUsage( 26 | AttributeTargets.Method | AttributeTargets.Parameter | 27 | AttributeTargets.Property | AttributeTargets.Delegate | 28 | AttributeTargets.Field, AllowMultiple = false, Inherited = true)] 29 | public sealed class CanBeNullAttribute : Attribute { } 30 | 31 | /// 32 | /// Indicates that the value of the marked element could never be null 33 | /// 34 | /// 35 | /// [NotNull] public object Foo() { 36 | /// return null; // Warning: Possible 'null' assignment 37 | /// } 38 | /// 39 | [AttributeUsage( 40 | AttributeTargets.Method | AttributeTargets.Parameter | 41 | AttributeTargets.Property | AttributeTargets.Delegate | 42 | AttributeTargets.Field, AllowMultiple = false, Inherited = true)] 43 | public sealed class NotNullAttribute : Attribute { } 44 | 45 | /// 46 | /// Indicates that the marked method builds string by format pattern and (optional) arguments. 47 | /// Parameter, which contains format string, should be given in constructor. The format string 48 | /// should be in -like form 49 | /// 50 | /// 51 | /// [StringFormatMethod("message")] 52 | /// public void ShowError(string message, params object[] args) { /* do something */ } 53 | /// public void Foo() { 54 | /// ShowError("Failed: {0}"); // Warning: Non-existing argument in format string 55 | /// } 56 | /// 57 | [AttributeUsage( 58 | AttributeTargets.Constructor | AttributeTargets.Method, 59 | AllowMultiple = false, Inherited = true)] 60 | public sealed class StringFormatMethodAttribute : Attribute 61 | { 62 | /// 63 | /// Specifies which parameter of an annotated method should be treated as format-string 64 | /// 65 | public StringFormatMethodAttribute(string formatParameterName) 66 | { 67 | FormatParameterName = formatParameterName; 68 | } 69 | 70 | public string FormatParameterName { get; private set; } 71 | } 72 | 73 | /// 74 | /// Indicates that the function argument should be string literal and match one 75 | /// of the parameters of the caller function. For example, ReSharper annotates 76 | /// the parameter of 77 | /// 78 | /// 79 | /// public void Foo(string param) { 80 | /// if (param == null) 81 | /// throw new ArgumentNullException("par"); // Warning: Cannot resolve symbol 82 | /// } 83 | /// 84 | [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] 85 | public sealed class InvokerParameterNameAttribute : Attribute { } 86 | 87 | /// 88 | /// Indicates that the method is contained in a type that implements 89 | /// interface 90 | /// and this method is used to notify that some property value changed 91 | /// 92 | /// 93 | /// The method should be non-static and conform to one of the supported signatures: 94 | /// 95 | /// NotifyChanged(string) 96 | /// NotifyChanged(params string[]) 97 | /// NotifyChanged{T}(Expression{Func{T}}) 98 | /// NotifyChanged{T,U}(Expression{Func{T,U}}) 99 | /// SetProperty{T}(ref T, T, string) 100 | /// 101 | /// 102 | /// 103 | /// public class Foo : INotifyPropertyChanged { 104 | /// public event PropertyChangedEventHandler PropertyChanged; 105 | /// [NotifyPropertyChangedInvocator] 106 | /// protected virtual void NotifyChanged(string propertyName) { ... } 107 | /// 108 | /// private string _name; 109 | /// public string Name { 110 | /// get { return _name; } 111 | /// set { _name = value; NotifyChanged("LastName"); /* Warning */ } 112 | /// } 113 | /// } 114 | /// 115 | /// Examples of generated notifications: 116 | /// 117 | /// NotifyChanged("Property") 118 | /// NotifyChanged(() => Property) 119 | /// NotifyChanged((VM x) => x.Property) 120 | /// SetProperty(ref myField, value, "Property") 121 | /// 122 | /// 123 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] 124 | public sealed class NotifyPropertyChangedInvocatorAttribute : Attribute 125 | { 126 | public NotifyPropertyChangedInvocatorAttribute() { } 127 | public NotifyPropertyChangedInvocatorAttribute(string parameterName) 128 | { 129 | ParameterName = parameterName; 130 | } 131 | 132 | public string ParameterName { get; private set; } 133 | } 134 | 135 | /// 136 | /// Describes dependency between method input and output 137 | /// 138 | /// 139 | ///

Function Definition Table syntax:

140 | /// 141 | /// FDT ::= FDTRow [;FDTRow]* 142 | /// FDTRow ::= Input => Output | Output <= Input 143 | /// Input ::= ParameterName: Value [, Input]* 144 | /// Output ::= [ParameterName: Value]* {halt|stop|void|nothing|Value} 145 | /// Value ::= true | false | null | notnull | canbenull 146 | /// 147 | /// If method has single input parameter, it's name could be omitted.
148 | /// Using halt (or void/nothing, which is the same) 149 | /// for method output means that the methos doesn't return normally.
150 | /// canbenull annotation is only applicable for output parameters.
151 | /// You can use multiple [ContractAnnotation] for each FDT row, 152 | /// or use single attribute with rows separated by semicolon.
153 | ///
154 | /// 155 | /// 156 | /// [ContractAnnotation("=> halt")] 157 | /// public void TerminationMethod() 158 | /// 159 | /// 160 | /// [ContractAnnotation("halt <= condition: false")] 161 | /// public void Assert(bool condition, string text) // regular assertion method 162 | /// 163 | /// 164 | /// [ContractAnnotation("s:null => true")] 165 | /// public bool IsNullOrEmpty(string s) // string.IsNullOrEmpty() 166 | /// 167 | /// 168 | /// // A method that returns null if the parameter is null, and not null if the parameter is not null 169 | /// [ContractAnnotation("null => null; notnull => notnull")] 170 | /// public object Transform(object data) 171 | /// 172 | /// 173 | /// [ContractAnnotation("s:null=>false; =>true,result:notnull; =>false, result:null")] 174 | /// public bool TryParse(string s, out Person result) 175 | /// 176 | /// 177 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)] 178 | public sealed class ContractAnnotationAttribute : Attribute 179 | { 180 | public ContractAnnotationAttribute([NotNull] string contract) 181 | : this(contract, false) { } 182 | 183 | public ContractAnnotationAttribute([NotNull] string contract, bool forceFullStates) 184 | { 185 | Contract = contract; 186 | ForceFullStates = forceFullStates; 187 | } 188 | 189 | public string Contract { get; private set; } 190 | public bool ForceFullStates { get; private set; } 191 | } 192 | 193 | /// 194 | /// Indicates that marked element should be localized or not 195 | /// 196 | /// 197 | /// [LocalizationRequiredAttribute(true)] 198 | /// public class Foo { 199 | /// private string str = "my string"; // Warning: Localizable string 200 | /// } 201 | /// 202 | [AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = true)] 203 | public sealed class LocalizationRequiredAttribute : Attribute 204 | { 205 | public LocalizationRequiredAttribute() : this(true) { } 206 | public LocalizationRequiredAttribute(bool required) 207 | { 208 | Required = required; 209 | } 210 | 211 | public bool Required { get; private set; } 212 | } 213 | 214 | /// 215 | /// Indicates that the value of the marked type (or its derivatives) 216 | /// cannot be compared using '==' or '!=' operators and Equals() 217 | /// should be used instead. However, using '==' or '!=' for comparison 218 | /// with null is always permitted. 219 | /// 220 | /// 221 | /// [CannotApplyEqualityOperator] 222 | /// class NoEquality { } 223 | /// class UsesNoEquality { 224 | /// public void Test() { 225 | /// var ca1 = new NoEquality(); 226 | /// var ca2 = new NoEquality(); 227 | /// if (ca1 != null) { // OK 228 | /// bool condition = ca1 == ca2; // Warning 229 | /// } 230 | /// } 231 | /// } 232 | /// 233 | [AttributeUsage( 234 | AttributeTargets.Interface | AttributeTargets.Class | 235 | AttributeTargets.Struct, AllowMultiple = false, Inherited = true)] 236 | public sealed class CannotApplyEqualityOperatorAttribute : Attribute { } 237 | 238 | /// 239 | /// When applied to a target attribute, specifies a requirement for any type marked 240 | /// with the target attribute to implement or inherit specific type or types. 241 | /// 242 | /// 243 | /// [BaseTypeRequired(typeof(IComponent)] // Specify requirement 244 | /// public class ComponentAttribute : Attribute { } 245 | /// [Component] // ComponentAttribute requires implementing IComponent interface 246 | /// public class MyComponent : IComponent { } 247 | /// 248 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)] 249 | [BaseTypeRequired(typeof(Attribute))] 250 | public sealed class BaseTypeRequiredAttribute : Attribute 251 | { 252 | public BaseTypeRequiredAttribute([NotNull] Type baseType) 253 | { 254 | BaseType = baseType; 255 | } 256 | 257 | [NotNull] public Type BaseType { get; private set; } 258 | } 259 | 260 | /// 261 | /// Indicates that the marked symbol is used implicitly 262 | /// (e.g. via reflection, in external library), so this symbol 263 | /// will not be marked as unused (as well as by other usage inspections) 264 | /// 265 | [AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = true)] 266 | public sealed class UsedImplicitlyAttribute : Attribute 267 | { 268 | public UsedImplicitlyAttribute() 269 | : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { } 270 | 271 | public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags) 272 | : this(useKindFlags, ImplicitUseTargetFlags.Default) { } 273 | 274 | public UsedImplicitlyAttribute(ImplicitUseTargetFlags targetFlags) 275 | : this(ImplicitUseKindFlags.Default, targetFlags) { } 276 | 277 | public UsedImplicitlyAttribute( 278 | ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) 279 | { 280 | UseKindFlags = useKindFlags; 281 | TargetFlags = targetFlags; 282 | } 283 | 284 | public ImplicitUseKindFlags UseKindFlags { get; private set; } 285 | public ImplicitUseTargetFlags TargetFlags { get; private set; } 286 | } 287 | 288 | /// 289 | /// Should be used on attributes and causes ReSharper 290 | /// to not mark symbols marked with such attributes as unused 291 | /// (as well as by other usage inspections) 292 | /// 293 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] 294 | public sealed class MeansImplicitUseAttribute : Attribute 295 | { 296 | public MeansImplicitUseAttribute() 297 | : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { } 298 | 299 | public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags) 300 | : this(useKindFlags, ImplicitUseTargetFlags.Default) { } 301 | 302 | public MeansImplicitUseAttribute(ImplicitUseTargetFlags targetFlags) 303 | : this(ImplicitUseKindFlags.Default, targetFlags) { } 304 | 305 | public MeansImplicitUseAttribute( 306 | ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) 307 | { 308 | UseKindFlags = useKindFlags; 309 | TargetFlags = targetFlags; 310 | } 311 | 312 | [UsedImplicitly] public ImplicitUseKindFlags UseKindFlags { get; private set; } 313 | [UsedImplicitly] public ImplicitUseTargetFlags TargetFlags { get; private set; } 314 | } 315 | 316 | [Flags] 317 | public enum ImplicitUseKindFlags 318 | { 319 | Default = Access | Assign | InstantiatedWithFixedConstructorSignature, 320 | /// Only entity marked with attribute considered used 321 | Access = 1, 322 | /// Indicates implicit assignment to a member 323 | Assign = 2, 324 | /// 325 | /// Indicates implicit instantiation of a type with fixed constructor signature. 326 | /// That means any unused constructor parameters won't be reported as such. 327 | /// 328 | InstantiatedWithFixedConstructorSignature = 4, 329 | /// Indicates implicit instantiation of a type 330 | InstantiatedNoFixedConstructorSignature = 8, 331 | } 332 | 333 | /// 334 | /// Specify what is considered used implicitly 335 | /// when marked with 336 | /// or 337 | /// 338 | [Flags] 339 | public enum ImplicitUseTargetFlags 340 | { 341 | Default = Itself, 342 | Itself = 1, 343 | /// Members of entity marked with attribute are considered used 344 | Members = 2, 345 | /// Entity marked with attribute and all its members considered used 346 | WithMembers = Itself | Members 347 | } 348 | 349 | /// 350 | /// This attribute is intended to mark publicly available API 351 | /// which should not be removed and so is treated as used 352 | /// 353 | [MeansImplicitUse] 354 | public sealed class PublicAPIAttribute : Attribute 355 | { 356 | public PublicAPIAttribute() { } 357 | public PublicAPIAttribute([NotNull] string comment) 358 | { 359 | Comment = comment; 360 | } 361 | 362 | [NotNull] public string Comment { get; private set; } 363 | } 364 | 365 | /// 366 | /// Tells code analysis engine if the parameter is completely handled 367 | /// when the invoked method is on stack. If the parameter is a delegate, 368 | /// indicates that delegate is executed while the method is executed. 369 | /// If the parameter is an enumerable, indicates that it is enumerated 370 | /// while the method is executed 371 | /// 372 | [AttributeUsage(AttributeTargets.Parameter, Inherited = true)] 373 | public sealed class InstantHandleAttribute : Attribute { } 374 | 375 | /// 376 | /// Indicates that a method does not make any observable state changes. 377 | /// The same as System.Diagnostics.Contracts.PureAttribute 378 | /// 379 | /// 380 | /// [Pure] private int Multiply(int x, int y) { return x * y; } 381 | /// public void Foo() { 382 | /// const int a = 2, b = 2; 383 | /// Multiply(a, b); // Waring: Return value of pure method is not used 384 | /// } 385 | /// 386 | [AttributeUsage(AttributeTargets.Method, Inherited = true)] 387 | public sealed class PureAttribute : Attribute { } 388 | 389 | /// 390 | /// Indicates that a parameter is a path to a file or a folder 391 | /// within a web project. Path can be relative or absolute, 392 | /// starting from web root (~) 393 | /// 394 | [AttributeUsage(AttributeTargets.Parameter)] 395 | public class PathReferenceAttribute : Attribute 396 | { 397 | public PathReferenceAttribute() { } 398 | public PathReferenceAttribute([PathReference] string basePath) 399 | { 400 | BasePath = basePath; 401 | } 402 | 403 | [NotNull] public string BasePath { get; private set; } 404 | } 405 | 406 | // ASP.NET MVC attributes 407 | 408 | [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] 409 | public sealed class AspMvcAreaMasterLocationFormatAttribute : Attribute 410 | { 411 | public AspMvcAreaMasterLocationFormatAttribute(string format) { } 412 | } 413 | 414 | [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] 415 | public sealed class AspMvcAreaPartialViewLocationFormatAttribute : Attribute 416 | { 417 | public AspMvcAreaPartialViewLocationFormatAttribute(string format) { } 418 | } 419 | 420 | [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] 421 | public sealed class AspMvcAreaViewLocationFormatAttribute : Attribute 422 | { 423 | public AspMvcAreaViewLocationFormatAttribute(string format) { } 424 | } 425 | 426 | [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] 427 | public sealed class AspMvcMasterLocationFormatAttribute : Attribute 428 | { 429 | public AspMvcMasterLocationFormatAttribute(string format) { } 430 | } 431 | 432 | [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] 433 | public sealed class AspMvcPartialViewLocationFormatAttribute : Attribute 434 | { 435 | public AspMvcPartialViewLocationFormatAttribute(string format) { } 436 | } 437 | 438 | [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] 439 | public sealed class AspMvcViewLocationFormatAttribute : Attribute 440 | { 441 | public AspMvcViewLocationFormatAttribute(string format) { } 442 | } 443 | 444 | /// 445 | /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter 446 | /// is an MVC action. If applied to a method, the MVC action name is calculated 447 | /// implicitly from the context. Use this attribute for custom wrappers similar to 448 | /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String) 449 | /// 450 | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] 451 | public sealed class AspMvcActionAttribute : Attribute 452 | { 453 | public AspMvcActionAttribute() { } 454 | public AspMvcActionAttribute([NotNull] string anonymousProperty) 455 | { 456 | AnonymousProperty = anonymousProperty; 457 | } 458 | 459 | [NotNull] public string AnonymousProperty { get; private set; } 460 | } 461 | 462 | /// 463 | /// ASP.NET MVC attribute. Indicates that a parameter is an MVC area. 464 | /// Use this attribute for custom wrappers similar to 465 | /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String) 466 | /// 467 | [AttributeUsage(AttributeTargets.Parameter)] 468 | public sealed class AspMvcAreaAttribute : PathReferenceAttribute 469 | { 470 | public AspMvcAreaAttribute() { } 471 | public AspMvcAreaAttribute([NotNull] string anonymousProperty) 472 | { 473 | AnonymousProperty = anonymousProperty; 474 | } 475 | 476 | [NotNull] public string AnonymousProperty { get; private set; } 477 | } 478 | 479 | /// 480 | /// ASP.NET MVC attribute. If applied to a parameter, indicates that 481 | /// the parameter is an MVC controller. If applied to a method, 482 | /// the MVC controller name is calculated implicitly from the context. 483 | /// Use this attribute for custom wrappers similar to 484 | /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String, String) 485 | /// 486 | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] 487 | public sealed class AspMvcControllerAttribute : Attribute 488 | { 489 | public AspMvcControllerAttribute() { } 490 | public AspMvcControllerAttribute([NotNull] string anonymousProperty) 491 | { 492 | AnonymousProperty = anonymousProperty; 493 | } 494 | 495 | [NotNull] public string AnonymousProperty { get; private set; } 496 | } 497 | 498 | /// 499 | /// ASP.NET MVC attribute. Indicates that a parameter is an MVC Master. 500 | /// Use this attribute for custom wrappers similar to 501 | /// System.Web.Mvc.Controller.View(String, String) 502 | /// 503 | [AttributeUsage(AttributeTargets.Parameter)] 504 | public sealed class AspMvcMasterAttribute : Attribute { } 505 | 506 | /// 507 | /// ASP.NET MVC attribute. Indicates that a parameter is an MVC model type. 508 | /// Use this attribute for custom wrappers similar to 509 | /// System.Web.Mvc.Controller.View(String, Object) 510 | /// 511 | [AttributeUsage(AttributeTargets.Parameter)] 512 | public sealed class AspMvcModelTypeAttribute : Attribute { } 513 | 514 | /// 515 | /// ASP.NET MVC attribute. If applied to a parameter, indicates that 516 | /// the parameter is an MVC partial view. If applied to a method, 517 | /// the MVC partial view name is calculated implicitly from the context. 518 | /// Use this attribute for custom wrappers similar to 519 | /// System.Web.Mvc.Html.RenderPartialExtensions.RenderPartial(HtmlHelper, String) 520 | /// 521 | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] 522 | public sealed class AspMvcPartialViewAttribute : PathReferenceAttribute { } 523 | 524 | /// 525 | /// ASP.NET MVC attribute. Allows disabling all inspections 526 | /// for MVC views within a class or a method. 527 | /// 528 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] 529 | public sealed class AspMvcSupressViewErrorAttribute : Attribute { } 530 | 531 | /// 532 | /// ASP.NET MVC attribute. Indicates that a parameter is an MVC display template. 533 | /// Use this attribute for custom wrappers similar to 534 | /// System.Web.Mvc.Html.DisplayExtensions.DisplayForModel(HtmlHelper, String) 535 | /// 536 | [AttributeUsage(AttributeTargets.Parameter)] 537 | public sealed class AspMvcDisplayTemplateAttribute : Attribute { } 538 | 539 | /// 540 | /// ASP.NET MVC attribute. Indicates that a parameter is an MVC editor template. 541 | /// Use this attribute for custom wrappers similar to 542 | /// System.Web.Mvc.Html.EditorExtensions.EditorForModel(HtmlHelper, String) 543 | /// 544 | [AttributeUsage(AttributeTargets.Parameter)] 545 | public sealed class AspMvcEditorTemplateAttribute : Attribute { } 546 | 547 | /// 548 | /// ASP.NET MVC attribute. Indicates that a parameter is an MVC template. 549 | /// Use this attribute for custom wrappers similar to 550 | /// System.ComponentModel.DataAnnotations.UIHintAttribute(System.String) 551 | /// 552 | [AttributeUsage(AttributeTargets.Parameter)] 553 | public sealed class AspMvcTemplateAttribute : Attribute { } 554 | 555 | /// 556 | /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter 557 | /// is an MVC view. If applied to a method, the MVC view name is calculated implicitly 558 | /// from the context. Use this attribute for custom wrappers similar to 559 | /// System.Web.Mvc.Controller.View(Object) 560 | /// 561 | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] 562 | public sealed class AspMvcViewAttribute : PathReferenceAttribute { } 563 | 564 | /// 565 | /// ASP.NET MVC attribute. When applied to a parameter of an attribute, 566 | /// indicates that this parameter is an MVC action name 567 | /// 568 | /// 569 | /// [ActionName("Foo")] 570 | /// public ActionResult Login(string returnUrl) { 571 | /// ViewBag.ReturnUrl = Url.Action("Foo"); // OK 572 | /// return RedirectToAction("Bar"); // Error: Cannot resolve action 573 | /// } 574 | /// 575 | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)] 576 | public sealed class AspMvcActionSelectorAttribute : Attribute { } 577 | 578 | [AttributeUsage( 579 | AttributeTargets.Parameter | AttributeTargets.Property | 580 | AttributeTargets.Field, Inherited = true)] 581 | public sealed class HtmlElementAttributesAttribute : Attribute 582 | { 583 | public HtmlElementAttributesAttribute() { } 584 | public HtmlElementAttributesAttribute([NotNull] string name) 585 | { 586 | Name = name; 587 | } 588 | 589 | [NotNull] public string Name { get; private set; } 590 | } 591 | 592 | [AttributeUsage( 593 | AttributeTargets.Parameter | AttributeTargets.Field | 594 | AttributeTargets.Property, Inherited = true)] 595 | public sealed class HtmlAttributeValueAttribute : Attribute 596 | { 597 | public HtmlAttributeValueAttribute([NotNull] string name) 598 | { 599 | Name = name; 600 | } 601 | 602 | [NotNull] public string Name { get; private set; } 603 | } 604 | 605 | // Razor attributes 606 | 607 | /// 608 | /// Razor attribute. Indicates that a parameter or a method is a Razor section. 609 | /// Use this attribute for custom wrappers similar to 610 | /// System.Web.WebPages.WebPageBase.RenderSection(String) 611 | /// 612 | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method, Inherited = true)] 613 | public sealed class RazorSectionAttribute : Attribute { } 614 | } -------------------------------------------------------------------------------- /LiteTreeViewTestApp/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("TreeAsListBox")] 11 | [assembly: AssemblyDescription("")] 12 | [assembly: AssemblyConfiguration("")] 13 | [assembly: AssemblyCompany("")] 14 | [assembly: AssemblyProduct("TreeAsListBox")] 15 | [assembly: AssemblyCopyright("Copyright © 2014")] 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 | -------------------------------------------------------------------------------- /LiteTreeViewTestApp/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.35317 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 TreeAsListBox.Properties { 12 | using System; 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 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("TreeAsListBox.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /LiteTreeViewTestApp/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 | -------------------------------------------------------------------------------- /LiteTreeViewTestApp/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.35317 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 TreeAsListBox.Properties { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "12.0.0.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 19 | 20 | public static Settings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /LiteTreeViewTestApp/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | WPFLiteTreeView 3 | =============== 4 | 5 | WPF Tree View doesn't scale very good, when you expand a tree item with thousands of items the performance is bad. This TreeView implementation uses a ListBox to simulate a tree 6 | 7 | 8 | Usage 9 | --------------- 10 | 1. WPFLiteTreeView doesnt support HierarchicalDataTemplate , Instead your model objects needs to implement the IHaveChildren interface 11 | ```csharp 12 | public interface IHaveChildren 13 | { 14 | IEnumerable Children { get; } 15 | } 16 | ``` 17 | 18 | 2. Instead of setting ItemsSource inside your xaml, you need to set MyItemsSource property 19 | 20 | ```xaml 21 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | ``` 30 | 31 | 3. LiteTreeViewControl uses an item container named LiteTreeViewItem. 32 | this how you can change its style 33 | 34 | ```xaml 35 | 37 | 38 | 44 | 45 | 46 | ``` 47 | 48 | > Written with [StackEdit](https://stackedit.io/). 49 | -------------------------------------------------------------------------------- /WPFLiteTree.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.30723.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LiteTreeView", "LiteTreeView\LiteTreeView.csproj", "{2034C20A-4BE1-479D-B1A5-223A6CFC4D7C}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LiteTreeViewTestApp", "LiteTreeViewTestApp\LiteTreeViewTestApp.csproj", "{3E2D8A4D-0402-4A9A-839D-A6E0E6027649}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {2034C20A-4BE1-479D-B1A5-223A6CFC4D7C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {2034C20A-4BE1-479D-B1A5-223A6CFC4D7C}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {2034C20A-4BE1-479D-B1A5-223A6CFC4D7C}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {2034C20A-4BE1-479D-B1A5-223A6CFC4D7C}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {3E2D8A4D-0402-4A9A-839D-A6E0E6027649}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {3E2D8A4D-0402-4A9A-839D-A6E0E6027649}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {3E2D8A4D-0402-4A9A-839D-A6E0E6027649}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {3E2D8A4D-0402-4A9A-839D-A6E0E6027649}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | --------------------------------------------------------------------------------