├── .gitignore ├── LICENSE ├── README.md └── source ├── CleanAll.bat ├── Components ├── BusinessLib │ ├── BusinessLib.csproj │ ├── Models │ │ ├── LocationType.cs │ │ └── MetaLocationModel.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Resources │ │ ├── lokasyon.sql.gz │ │ ├── lokasyon.zip │ │ └── readme.txt │ └── database.cs └── FilterTreeViewLib │ ├── Behaviors │ ├── HighlightTextBlockBehavior.cs │ ├── TextChangedCommand.cs │ └── TreeViewItemExpanded.cs │ ├── Converters │ ├── BoolToVisibilityConverter.cs │ ├── CountToBoolConverter.cs │ ├── InverseBooleanConverter.cs │ └── LocationTypeToImageConverter.cs │ ├── FilterTreeViewLib.csproj │ ├── Interfaces │ ├── IHasDummyChild.cs │ └── ISelectionRange.cs │ ├── Properties │ └── AssemblyInfo.cs │ ├── Resources │ ├── Locations │ │ ├── appbar.base.select.png │ │ ├── appbar.chess.rook.png │ │ └── appbar.city.sanfrancisco.png │ ├── ZoomIn.ico │ ├── ZoomIn_256x.png │ ├── ZoomIn_48x.png │ └── ZoomIn_64x.png │ ├── ViewModels │ ├── AppBaseViewModel.cs │ ├── Base │ │ ├── BaseViewModel.cs │ │ └── RelayCommand.cs │ └── Tree │ │ ├── MetaLocationRootViewModel.cs │ │ ├── MetaLocationViewModel.cs │ │ └── Search │ │ ├── Enums │ │ ├── MatchType.cs │ │ └── SearchMatch.cs │ │ ├── SearchParams.cs │ │ ├── SearchResult.cs │ │ ├── SelectionRange.cs │ │ └── StringMatchItem.cs │ ├── Views │ └── BindingProxy.cs │ └── packages.config ├── FilterTreeView.sln └── FilterTreeView ├── App.config ├── App.xaml ├── App.xaml.cs ├── FilterTreeView.csproj ├── FilterTreeView.csproj.user ├── MainWindow.xaml ├── MainWindow.xaml.cs ├── Properties ├── AssemblyInfo.cs ├── Resources.Designer.cs ├── Resources.resx ├── Settings.Designer.cs └── Settings.settings ├── Tasks ├── OneTaskLimitedScheduler.cs └── OneTaskProcessor.cs ├── ViewModels └── AppViewModel.cs └── packages.config /.gitignore: -------------------------------------------------------------------------------- 1 | source/packages/ 2 | packages/ 3 | 00_Release/ 4 | 01_Nuget/ 5 | debug/ 6 | release/ 7 | build/ 8 | bin/ 9 | obj/ 10 | cache/ 11 | log/ 12 | tmp/ 13 | /source/.vs/ 14 | .vs/ 15 | 16 | *~ 17 | *.lock 18 | *.DS_Store 19 | *.swp 20 | *.out 21 | *.sou 22 | *.suo 23 | *.sqlite 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build status](https://ci.appveyor.com/api/projects/status/u762r32aupstrsph?svg=true)](https://ci.appveyor.com/project/Dirkster99/filtertreeview) [![Release](https://img.shields.io/github/release/Dirkster99/FilterTreeView.svg)](https://github.com/Dirkster99/FilterTreeView/releases/latest) 2 | 3 | # FilterTreeView 4 | This is a WPF/MVVM Search and Filter TreeView Reference Application 5 | 6 | See Codeproject articles: 7 | * Advanced WPF TreeViews Part 3 of n 8 | * A Highlightable WPF/MVVM TextBlock 9 | 10 | for more details or watch a screen captured video on youtube. 11 | 12 | ## TreeLib 13 | See also TreeLib project for Tree Traversal methods used in this project. 14 | 15 | ## City Search (String is contained): 16 | 17 | 18 | * 781 Saint 19 | * 267 los 20 | *  48 Washington 21 | *  32 Berlin 22 | *  22 Holland 23 | *  29 Paris 24 | *  23 London 25 | *  15 Brighton 26 | *  11 Hamburg 27 | *  10 Bremen 28 | *  17 Kiel 29 | *  14 Francisco 30 | *   9 Madrid 31 | *   7 Liverpool 32 | *   7 Amsterdam 33 | *   7 Australia 34 | *   3 Muenster 35 | -------------------------------------------------------------------------------- /source/CleanAll.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | pushd "%~dp0" 3 | ECHO. 4 | ECHO. 5 | ECHO. 6 | ECHO This script deletes all temporary build files in the .vs folder and the 7 | ECHO BIN and OBJ folders contained in the following projects 8 | ECHO. 9 | ECHO Components\BusinessLib 10 | ECHO Components\FilterTreeViewLib 11 | ECHO FilterTreeView 12 | ECHO. 13 | REM Ask the user if hes really sure to continue beyond this point XXXXXXXX 14 | set /p choice=Are you sure to continue (Y/N)? 15 | if not '%choice%'=='Y' Goto EndOfBatch 16 | REM Script does not continue unless user types 'Y' in upper case letter 17 | ECHO. 18 | ECHO XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 19 | ECHO. 20 | ECHO XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 21 | ECHO. 22 | ECHO Removing vs settings folder with *.sou file 23 | ECHO. 24 | RMDIR /S /Q .vs 25 | 26 | ECHO. 27 | ECHO Deleting BIN and OBJ Folders in BusinessLib 28 | ECHO. 29 | RMDIR /S /Q "Components\BusinessLib\bin" 30 | RMDIR /S /Q "Components\BusinessLib\obj" 31 | 32 | ECHO. 33 | ECHO Deleting BIN and OBJ Folders in FilterTreeViewLib 34 | ECHO. 35 | RMDIR /S /Q "Components\FilterTreeViewLib\bin" 36 | RMDIR /S /Q "Components\FilterTreeViewLib\obj" 37 | 38 | ECHO. 39 | ECHO Deleting BIN and OBJ Folders in FilterTreeView 40 | ECHO. 41 | RMDIR /S /Q ".\FilterTreeView\bin" 42 | RMDIR /S /Q ".\FilterTreeView\obj" 43 | 44 | PAUSE 45 | 46 | :EndOfBatch 47 | -------------------------------------------------------------------------------- /source/Components/BusinessLib/BusinessLib.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {70CC82CC-9EFE-461F-A959-F64A06894621} 8 | Library 9 | Properties 10 | BusinessLib 11 | BusinessLib 12 | v4.5 13 | 512 14 | 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | false 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | false 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | Always 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /source/Components/BusinessLib/Models/LocationType.cs: -------------------------------------------------------------------------------- 1 | namespace BusinessLib.Models 2 | { 3 | /// 4 | /// Determines the type of location in a collection entry. 5 | /// 6 | public enum LocationType 7 | { 8 | Unknown = 1000, 9 | 10 | /// 11 | /// This location refers to a country on our little world called Earth. 12 | /// 13 | Country = 100, 14 | 15 | /// 16 | /// This location refers to a region that is part of a country. 17 | /// 18 | Region = 10, 19 | 20 | /// 21 | /// This location refers to a city (e.g. Berlin) located within 22 | /// a region. 23 | /// 24 | City = 0 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /source/Components/BusinessLib/Models/MetaLocationModel.cs: -------------------------------------------------------------------------------- 1 | namespace BusinessLib.Models 2 | { 3 | using System.Collections.Generic; 4 | using System.Collections.ObjectModel; 5 | using System.Xml.Serialization; 6 | 7 | public class MetaLocationModel 8 | { 9 | #region fields 10 | private readonly ObservableCollection _Children = null; 11 | #endregion fields 12 | 13 | #region constructors 14 | /// 15 | /// Parameterized Class Constructor 16 | /// 17 | public MetaLocationModel( 18 | MetaLocationModel parent 19 | , int id 20 | , string iso 21 | , string localName 22 | , LocationType type 23 | , long in_Location 24 | , double geo_lat 25 | , double geo_lng 26 | , string db_id 27 | ) 28 | : this() 29 | { 30 | Parent = parent; 31 | 32 | ID = id; 33 | ISO = iso; 34 | LocalName = localName; 35 | Type = type; 36 | In_Location = in_Location; 37 | Geo_lat = geo_lat; 38 | Geo_lng = geo_lng; 39 | DB_id = db_id; 40 | } 41 | 42 | /// 43 | /// Class Constructor 44 | /// 45 | public MetaLocationModel() 46 | { 47 | _Children = new ObservableCollection(); 48 | } 49 | #endregion constructors 50 | 51 | #region properties 52 | [XmlIgnore] 53 | public MetaLocationModel Parent { get; private set; } 54 | 55 | public int ID { get; set; } 56 | public string ISO { get; set; } 57 | public string LocalName { get; set; } 58 | public LocationType Type { get; set; } 59 | 60 | public long In_Location { get; set; } 61 | 62 | public double Geo_lat { get; set; } 63 | public double Geo_lng { get; set; } 64 | 65 | public string DB_id { get; set; } 66 | 67 | [XmlIgnore] 68 | public IEnumerable Children 69 | { 70 | get 71 | { 72 | return _Children; 73 | } 74 | } 75 | #endregion properties 76 | 77 | #region methods 78 | public int ChildrenCount => _Children.Count; 79 | 80 | public void ChildrenAdd(MetaLocationModel child) 81 | { 82 | _Children.Add(child); 83 | } 84 | 85 | public void ChildrenRemove(MetaLocationModel child) 86 | { 87 | _Children.Remove(child); 88 | } 89 | 90 | public void ChildrenClear() 91 | { 92 | _Children.Clear(); 93 | } 94 | 95 | public void SetParent(MetaLocationModel parent) 96 | { 97 | Parent = parent; 98 | } 99 | #endregion methods 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /source/Components/BusinessLib/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("BusinessLib")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("BusinessLib")] 13 | [assembly: AssemblyCopyright("Copyright © 2017")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("70cc82cc-9efe-461f-a959-f64a06894621")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /source/Components/BusinessLib/Resources/lokasyon.sql.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirkster99/FilterTreeView/71c690e00af040e64b4d6f0cb9270565e59789de/source/Components/BusinessLib/Resources/lokasyon.sql.gz -------------------------------------------------------------------------------- /source/Components/BusinessLib/Resources/lokasyon.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirkster99/FilterTreeView/71c690e00af040e64b4d6f0cb9270565e59789de/source/Components/BusinessLib/Resources/lokasyon.zip -------------------------------------------------------------------------------- /source/Components/BusinessLib/Resources/readme.txt: -------------------------------------------------------------------------------- 1 |  2 | The sample data in *lokasyon.sql.gz* directory was drawn from here: 3 | https://code.google.com/archive/p/worlddb/downloads 4 | 5 | This data was converted to XML using a custom sqlite converter program. 6 | The result of that conversion was: lokasyon.zip 7 | -------------------------------------------------------------------------------- /source/Components/BusinessLib/database.cs: -------------------------------------------------------------------------------- 1 | namespace BusinessLib 2 | { 3 | using BusinessLib.Models; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | using System.Xml.Serialization; 11 | 12 | /// 13 | /// A data source that provides raw data objects. In a real 14 | /// application this class could also make calls to a database. 15 | /// 16 | public class Database 17 | { 18 | /// 19 | /// Loads country, region, city data from an open source archiv: 20 | /// lokasyon.sql.gz dated back to 2012. 21 | /// Source: https://code.google.com/archive/p/worlddb/downloads 22 | /// 23 | /// The zipped XML format used here was converted from the above 24 | /// SQL data source. 25 | /// 26 | /// 27 | /// 28 | /// 29 | /// 30 | /// 31 | public static async Task> LoadData( 32 | string pathZipFile 33 | , string countriesFileName 34 | , string regionsFileName 35 | , string citiesFileName 36 | ) 37 | { 38 | return await Task.Run>(async () => 39 | { 40 | // Load all regions into a dicationary collection 41 | Dictionary isoDicRegions = await 42 | IsoDicFromZipXml(pathZipFile, regionsFileName); 43 | 44 | // Load cities (that belong to 1 region) into a dictionary collection 45 | List isoCities 46 | = await FromZipXml(pathZipFile, citiesFileName); 47 | 48 | // Insert each city into its region 49 | foreach (var cityItem in isoCities) 50 | { 51 | int isoCountryLength = cityItem.ISO.IndexOf('-'); 52 | int isoRegionLength = cityItem.ISO.IndexOf('-', isoCountryLength + 1); 53 | string isoRegion = cityItem.ISO.Substring(0, isoRegionLength); 54 | 55 | MetaLocationModel regionItem; 56 | isoDicRegions.TryGetValue(isoRegion, out regionItem); 57 | 58 | if (regionItem != null) 59 | { 60 | cityItem.SetParent(regionItem); 61 | regionItem.ChildrenAdd(cityItem); 62 | } 63 | } 64 | 65 | // Load all countries (about 220 world-wide) into a collection 66 | List isoCountries = null; 67 | isoCountries = await FromZipXml(pathZipFile, countriesFileName); 68 | 69 | // Insert all regions (and cities below them) into their countries 70 | foreach (var regionItem in isoDicRegions.Values) 71 | { 72 | int isoCountryLength = regionItem.ISO.IndexOf('-'); 73 | string isoCountry = regionItem.ISO.Substring(0, isoCountryLength); 74 | 75 | var countryItem = isoCountries.Where(x => x.ISO.Equals(isoCountry, StringComparison.InvariantCulture)).FirstOrDefault(); 76 | 77 | if (countryItem != null) 78 | { 79 | regionItem.SetParent(countryItem); 80 | countryItem.ChildrenAdd(regionItem); 81 | } 82 | } 83 | 84 | return isoCountries; 85 | }); 86 | } 87 | 88 | #region Xml Reader Methods 89 | /// 90 | /// Reads all Meta Location Models directly from a given Xml file. 91 | /// 92 | /// Path the Xml file eg.: @".\Resources\countries.xml" 93 | /// 94 | public static Task> ReadFromXml(string targetFileName) 95 | { 96 | return Task.Run(() => 97 | { 98 | List list = null; 99 | 100 | try 101 | { 102 | using (StreamReader sr = new StreamReader(targetFileName, Encoding.UTF8)) 103 | { 104 | using (TextReader reader = TextReader.Synchronized(sr)) 105 | { 106 | object ds = new XmlSerializer(typeof(List)).Deserialize(reader); 107 | 108 | list = ds as List; 109 | } 110 | } 111 | } 112 | catch (System.Exception) 113 | { 114 | throw; 115 | } 116 | 117 | return list; 118 | }); 119 | } 120 | 121 | /// 122 | /// Reads all Meta Location Models directly from a given Xml file. 123 | /// 124 | /// @".\Resources\lokasyon.zip" 125 | /// Path the Xml file eg.: "countries.xml" 126 | /// 127 | public static Task> FromZipXml( 128 | string zipFileFullPath 129 | , string targetFileName) 130 | { 131 | return Task.Run(() => 132 | { 133 | List list = null; 134 | 135 | try 136 | { 137 | var compressedFile = System.IO.Compression.ZipFile.OpenRead(zipFileFullPath) 138 | .Entries.Where(x => x.Name.Equals(targetFileName, StringComparison.InvariantCulture)) 139 | .FirstOrDefault().Open(); 140 | 141 | using (StreamReader sr = new StreamReader(compressedFile, Encoding.UTF8)) 142 | { 143 | using (TextReader reader = TextReader.Synchronized(sr)) 144 | { 145 | object ds = new XmlSerializer(typeof(List)).Deserialize(reader); 146 | 147 | list = ds as List; 148 | } 149 | } 150 | } 151 | catch (System.Exception) 152 | { 153 | throw; 154 | } 155 | 156 | return list; 157 | }); 158 | } 159 | 160 | /// 161 | /// Reads all Meta Location Models directly from a Zipped Xml file. 162 | /// 163 | /// 164 | /// 165 | /// 166 | public static async Task> IsoDicFromZipXml( 167 | string zipFileFullPath 168 | , string targetFileName) 169 | { 170 | List isoList = await FromZipXml(zipFileFullPath, targetFileName); 171 | 172 | Dictionary isoDictionary = new Dictionary(); 173 | 174 | foreach (var item in isoList) 175 | isoDictionary.Add(item.ISO, item); 176 | 177 | return isoDictionary; 178 | } 179 | #endregion Xml Reader Methods 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /source/Components/FilterTreeViewLib/Behaviors/HighlightTextBlockBehavior.cs: -------------------------------------------------------------------------------- 1 | namespace FilterTreeViewLib.Behaviors 2 | { 3 | using FilterTreeViewLib.Interfaces; 4 | using System.Windows; 5 | using System.Windows.Controls; 6 | using System.Windows.Documents; 7 | using System.Windows.Media; 8 | using System; 9 | 10 | /// 11 | /// Implements a behavior to highlight text as specified by 12 | /// a bound property that adheres to the interface. 13 | /// 14 | public static class HighlightTextBlockBehavior 15 | { 16 | #region fields 17 | /// 18 | /// Back Store of Attachable Dependency Property that indicates the range 19 | /// of text that should be highlighting (if any). 20 | /// 21 | private static readonly DependencyProperty RangeProperty = 22 | DependencyProperty.RegisterAttached("Range", 23 | typeof(ISelectionRange), 24 | typeof(HighlightTextBlockBehavior), new PropertyMetadata(null, OnRangeChanged)); 25 | #endregion fields 26 | 27 | #region methods 28 | /// 29 | /// Gets the current values of the Range dependency property. 30 | /// 31 | public static ISelectionRange GetRange(DependencyObject obj) 32 | { 33 | return (ISelectionRange)obj.GetValue(RangeProperty); 34 | } 35 | 36 | /// 37 | /// Gets the current values of the Range dependency property. 38 | /// 39 | public static void SetRange(DependencyObject obj, ISelectionRange value) 40 | { 41 | obj.SetValue(RangeProperty, value); 42 | } 43 | 44 | /// 45 | /// Method executes whenever the Range dependency property valua has changed 46 | /// (in the bound viewmodel). 47 | /// 48 | /// 49 | /// 50 | private static void OnRangeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 51 | { 52 | TextBlock txtblock = d as TextBlock; 53 | 54 | if (txtblock == null) 55 | return; 56 | 57 | var range = GetRange(d); // Get the bound Range value to do highlighting 58 | 59 | // Standard background is transparent 60 | SolidColorBrush normalBackGround = new SolidColorBrush(Color.FromArgb(00, 00, 00, 00)); 61 | if (range != null) 62 | { 63 | if (range.NormalBackground != default(Color)) 64 | normalBackGround = new SolidColorBrush(range.NormalBackground); 65 | } 66 | 67 | // Reset Highlighting - this must be done anyways since 68 | // multiple selection runs will overlay each other 69 | var txtrange = new TextRange(txtblock.ContentStart, txtblock.ContentEnd); 70 | txtrange.ApplyPropertyValue(TextElement.BackgroundProperty, normalBackGround); 71 | 72 | if (range == null) 73 | return; 74 | 75 | if (range.Start < 0 || range.End < 0) // Nothing to highlight here :-( 76 | return; 77 | 78 | try 79 | { 80 | // Standard selection background color on dark skin: 254, 252, 200 81 | // Standard selection background color on light skin: 208, 247, 255 82 | Color selColor = (range.DarkSkin ? Color.FromArgb(255, 254, 252, 200) : 83 | Color.FromArgb(255, 208, 247, 255)); 84 | 85 | Brush selectionBackground = new SolidColorBrush(selColor); 86 | if (range != null) 87 | { 88 | if (range.SelectionBackground != default(Color)) 89 | selectionBackground = new SolidColorBrush(range.SelectionBackground); 90 | } 91 | 92 | TextRange txtrangel = new TextRange( 93 | txtblock.ContentStart.GetPositionAtOffset(range.Start + 1) 94 | , txtblock.ContentStart.GetPositionAtOffset(range.End + 1)); 95 | 96 | txtrangel.ApplyPropertyValue(TextElement.BackgroundProperty, selectionBackground); 97 | } 98 | catch (Exception exc) 99 | { 100 | Console.WriteLine(exc.Message); 101 | Console.WriteLine(exc.StackTrace); 102 | } 103 | } 104 | #endregion methods 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /source/Components/FilterTreeViewLib/Behaviors/TextChangedCommand.cs: -------------------------------------------------------------------------------- 1 | namespace FilterTreeViewLib.Behaviors 2 | { 3 | using System.Windows; 4 | using System.Windows.Controls; 5 | using System.Windows.Input; 6 | 7 | /// 8 | /// Source: 9 | /// http://stackoverflow.com/questions/1034374/drag-and-drop-in-mvvm-with-scatterview 10 | /// http://social.msdn.microsoft.com/Forums/de-DE/wpf/thread/21bed380-c485-44fb-8741-f9245524d0ae 11 | /// 12 | /// Attached behaviour to implement the drop event via delegate command binding or routed commands. 13 | /// 14 | public static class TextChangedCommand 15 | { 16 | // Field of attached ICommand property 17 | private static readonly DependencyProperty ChangedCommandProperty = DependencyProperty.RegisterAttached( 18 | "ChangedCommand", 19 | typeof(ICommand), 20 | typeof(TextChangedCommand), 21 | new PropertyMetadata(null, OnTextChangedCommandChange)); 22 | 23 | /// 24 | /// Setter method of the attached DropCommand property 25 | /// 26 | /// 27 | /// 28 | public static void SetChangedCommand(DependencyObject source, ICommand value) 29 | { 30 | source.SetValue(ChangedCommandProperty, value); 31 | } 32 | 33 | /// 34 | /// Getter method of the attached DropCommand property 35 | /// 36 | /// 37 | /// 38 | public static ICommand GetChangedCommand(DependencyObject source) 39 | { 40 | return (ICommand)source.GetValue(ChangedCommandProperty); 41 | } 42 | 43 | /// 44 | /// This method is hooked in the definition of the . 45 | /// It is called whenever the attached property changes - in our case the event of binding 46 | /// and unbinding the property to a sink is what we are looking for. 47 | /// 48 | /// 49 | /// 50 | private static void OnTextChangedCommandChange(DependencyObject d, DependencyPropertyChangedEventArgs e) 51 | { 52 | TextBox uiElement = d as TextBox; // Remove the handler if it exist to avoid memory leaks 53 | 54 | if (uiElement != null) 55 | { 56 | uiElement.TextChanged -= OnText_Changed; 57 | 58 | var command = e.NewValue as ICommand; 59 | if (command != null) 60 | { 61 | // the property is attached so we attach the Drop event handler 62 | uiElement.TextChanged += OnText_Changed; 63 | } 64 | } 65 | } 66 | 67 | /// 68 | /// This method is called when the TextChanged event occurs. The sender should be the control 69 | /// on which this behaviour is attached - so we convert the sender into a 70 | /// and receive the Command through the GetDropCommand getter listed above. 71 | /// 72 | /// The parameter contains the standard data, 73 | /// which is unpacked and realesed upon the bound command. 74 | /// 75 | /// This implementation supports binding of delegate commands and routed commands. 76 | /// 77 | /// 78 | /// 79 | private static void OnText_Changed(object sender, TextChangedEventArgs e) 80 | { 81 | TextBox uiElement = sender as TextBox; 82 | 83 | 84 | // Sanity check just in case this was somehow send by something else 85 | if (uiElement == null) 86 | return; 87 | 88 | // Bugfix: A change during disabled state is likely to be caused by a bound property 89 | // in a viewmodel (a machine based edit rather than user input) 90 | // -> Lets break the message loop here to avoid unnecessary CPU processings... 91 | if (uiElement.IsEnabled == false) 92 | return; 93 | 94 | ICommand changedCommand = TextChangedCommand.GetChangedCommand(uiElement); 95 | 96 | // There may not be a command bound to this after all 97 | if (changedCommand == null) 98 | return; 99 | 100 | var item = uiElement.Text; 101 | 102 | // Check whether this attached behaviour is bound to a RoutedCommand 103 | if (changedCommand is RoutedCommand) 104 | { 105 | // Execute the routed command 106 | (changedCommand as RoutedCommand).Execute(item, uiElement); 107 | } 108 | else 109 | { 110 | // Execute the Command as bound delegate 111 | changedCommand.Execute(item); 112 | } 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /source/Components/FilterTreeViewLib/Behaviors/TreeViewItemExpanded.cs: -------------------------------------------------------------------------------- 1 | namespace FilterTreeViewLib.Behaviors 2 | { 3 | using FilterTreeViewLib.Interfaces; 4 | using System.Windows; 5 | using System.Windows.Controls; 6 | using System.Windows.Input; 7 | 8 | /// 9 | /// Class implements an attached behaviour to bring a selected TreeViewItem 10 | /// into view when selection is driven by the viewmodel (not the user). 11 | /// 12 | public static class TreeViewItemExpanded 13 | { 14 | public static ICommand GetCommand(DependencyObject obj) 15 | { 16 | return (ICommand)obj.GetValue(CommandProperty); 17 | } 18 | 19 | public static void SetCommand(DependencyObject obj, ICommand value) 20 | { 21 | obj.SetValue(CommandProperty, value); 22 | } 23 | 24 | public static readonly DependencyProperty CommandProperty = 25 | DependencyProperty.RegisterAttached("Command", 26 | typeof(ICommand), 27 | typeof(TreeViewItemExpanded), 28 | new PropertyMetadata(null, OnPropertyChanged)); 29 | #region methods 30 | private static void OnPropertyChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e) 31 | { 32 | TreeViewItem item = depObj as TreeViewItem; 33 | if (item == null) 34 | return; 35 | 36 | if (e.NewValue is ICommand == false) 37 | return; 38 | 39 | if ((ICommand)e.NewValue != null) 40 | { 41 | item.Expanded += Item_Expanded; 42 | } 43 | else 44 | { 45 | item.Expanded -= Item_Expanded; 46 | } 47 | } 48 | 49 | private static void Item_Expanded(object sender, RoutedEventArgs e) 50 | { 51 | var uiElement = sender as TreeViewItem; 52 | 53 | // Sanity check just in case this was somehow send by something else 54 | if (uiElement == null) 55 | return; 56 | 57 | IHasDummyChild f = null; 58 | 59 | if (uiElement.DataContext is IHasDummyChild) 60 | { 61 | f = uiElement.DataContext as IHasDummyChild; 62 | 63 | // Message Expand only for those who have 1 dummy folder below 64 | if (f.HasDummyChild == false) 65 | return; 66 | } 67 | 68 | ICommand changedCommand = TreeViewItemExpanded.GetCommand(uiElement); 69 | 70 | // There may not be a command bound to this after all 71 | if (changedCommand == null || f == null) 72 | return; 73 | 74 | // Check whether this attached behaviour is bound to a RoutedCommand 75 | if (changedCommand is RoutedCommand) 76 | { 77 | // Execute the routed command 78 | (changedCommand as RoutedCommand).Execute(f, uiElement); 79 | } 80 | else 81 | { 82 | // Execute the Command as bound delegate 83 | changedCommand.Execute(f); 84 | } 85 | } 86 | #endregion methods 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /source/Components/FilterTreeViewLib/Converters/BoolToVisibilityConverter.cs: -------------------------------------------------------------------------------- 1 | namespace FilterTreeViewLib.Converters 2 | { 3 | using System; 4 | using System.Globalization; 5 | using System.Windows; 6 | using System.Windows.Data; 7 | 8 | public class BoolToVisibilityConverter : IValueConverter 9 | { 10 | public BoolToVisibilityConverter() 11 | { 12 | this.True = Visibility.Visible; 13 | this.False = Visibility.Collapsed; 14 | } 15 | 16 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 17 | { 18 | if (value == null) 19 | return Binding.DoNothing; 20 | 21 | if (value is bool == false) 22 | return Binding.DoNothing; 23 | 24 | bool input = (bool)value; 25 | 26 | if (input == true) 27 | return True; 28 | 29 | return False; 30 | } 31 | 32 | public Visibility True { get; set; } 33 | 34 | public Visibility False { get; set; } 35 | 36 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 37 | { 38 | throw new NotImplementedException(); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /source/Components/FilterTreeViewLib/Converters/CountToBoolConverter.cs: -------------------------------------------------------------------------------- 1 | namespace FilterTreeViewLib.Converters 2 | { 3 | using System; 4 | using System.Globalization; 5 | using System.Windows.Data; 6 | 7 | public class CountToBoolConverter : IValueConverter 8 | { 9 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 10 | { 11 | if (value == null) 12 | return false; 13 | 14 | if (value is int == false) 15 | return false; 16 | 17 | int convertValue = (int)value; 18 | 19 | if (convertValue > 0) 20 | return true; 21 | 22 | return false; 23 | } 24 | 25 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 26 | { 27 | throw new NotImplementedException(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /source/Components/FilterTreeViewLib/Converters/InverseBooleanConverter.cs: -------------------------------------------------------------------------------- 1 | namespace FilterTreeViewLib.Converters 2 | { 3 | using System; 4 | using System.Windows.Data; 5 | 6 | [ValueConversion(typeof(bool), typeof(bool))] 7 | public class InverseBooleanConverter : IValueConverter 8 | { 9 | #region IValueConverter Members 10 | 11 | public object Convert(object value, Type targetType, object parameter, 12 | System.Globalization.CultureInfo culture) 13 | { 14 | if (targetType != typeof(bool)) 15 | throw new InvalidOperationException("The target must be a boolean"); 16 | 17 | return !(bool)value; 18 | } 19 | 20 | public object ConvertBack(object value, Type targetType, object parameter, 21 | System.Globalization.CultureInfo culture) 22 | { 23 | throw new NotSupportedException(); 24 | } 25 | 26 | #endregion 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /source/Components/FilterTreeViewLib/Converters/LocationTypeToImageConverter.cs: -------------------------------------------------------------------------------- 1 | namespace FilterTreeViewLib.Converters 2 | { 3 | using BusinessLib.Models; 4 | using System; 5 | using System.Globalization; 6 | using System.Windows; 7 | using System.Windows.Data; 8 | 9 | /// 10 | /// Converts an enum into its image resource. 11 | /// The corresponding image resource must be present in the applications's 12 | /// resource dictionary. 13 | /// 14 | [ValueConversion(typeof(LocationType), typeof(System.Windows.Media.Imaging.BitmapImage))] 15 | public class LocationTypeToImageConverter : IValueConverter 16 | { 17 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 18 | { 19 | if (value == null) 20 | return Binding.DoNothing; 21 | 22 | if (value is LocationType == false) 23 | return Binding.DoNothing; 24 | 25 | LocationType typeOfLocation = (LocationType)value; 26 | string locationIconName = string.Empty; 27 | 28 | try 29 | { 30 | switch (typeOfLocation) 31 | { 32 | case LocationType.Country: 33 | locationIconName = "CountryImage"; 34 | break; 35 | 36 | case LocationType.Region: 37 | locationIconName = "RegionImage"; 38 | break; 39 | 40 | case LocationType.City: 41 | locationIconName = "CityImage"; 42 | break; 43 | 44 | case LocationType.Unknown: 45 | return Binding.DoNothing; 46 | 47 | default: 48 | throw new ArgumentOutOfRangeException(typeOfLocation.ToString()); 49 | } 50 | 51 | return Application.Current.Resources[locationIconName]; 52 | } 53 | catch 54 | { 55 | } 56 | 57 | return Binding.DoNothing; 58 | } 59 | 60 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 61 | { 62 | throw new NotImplementedException(); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /source/Components/FilterTreeViewLib/FilterTreeViewLib.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {C5030463-0469-4AFA-92F4-F6B55A6B4C89} 8 | Library 9 | Properties 10 | FilterTreeViewLib 11 | FilterTreeViewLib 12 | v4.5 13 | 512 14 | 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | TRACE;DEBUG;TREELIB 22 | prompt 23 | 4 24 | false 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE;TREELIB 31 | prompt 32 | 4 33 | false 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | ..\..\packages\Dirkster.TreeLib.1.2.0\lib\net40\TreeLib.dll 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 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 | {70cc82cc-9efe-461f-a959-f64a06894621} 99 | BusinessLib 100 | 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /source/Components/FilterTreeViewLib/Interfaces/IHasDummyChild.cs: -------------------------------------------------------------------------------- 1 | namespace FilterTreeViewLib.Interfaces 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | public interface IHasDummyChild 10 | { 11 | 12 | /// 13 | /// Determines whether this item has a dummy child below or not. 14 | /// 15 | bool HasDummyChild { get; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /source/Components/FilterTreeViewLib/Interfaces/ISelectionRange.cs: -------------------------------------------------------------------------------- 1 | namespace FilterTreeViewLib.Interfaces 2 | { 3 | using System; 4 | using System.Windows.Media; 5 | 6 | /// 7 | /// Defines a range that can be used to indicate 8 | /// the start and end of a text selection or any other kind of range. 9 | /// 10 | public interface ISelectionRange : ICloneable 11 | { 12 | /// 13 | /// Gets the start of the indicated range. 14 | /// 15 | int Start { get; } 16 | 17 | /// 18 | /// Gets the end of the indicated range. 19 | /// 20 | int End { get; } 21 | 22 | /// 23 | /// Gets a bool value to determine whether DarkSkin default 24 | /// value for property should 25 | /// be applied or not. 26 | /// 27 | bool DarkSkin { get; } 28 | 29 | /// 30 | /// Gets the background color that is applied to the background brush, 31 | /// which should be applied when no match is indicated 32 | /// (this can be default(Color) in which case standard selection Brush 33 | /// is applied). 34 | /// 35 | /// Note: 36 | /// Standard selection background color on light skin: 208, 247, 255 37 | /// Standard selection background color on dark skin: 254, 252, 200 38 | /// 39 | Color SelectionBackground { get; } 40 | 41 | /// 42 | /// Gets the background color that is applied to the background brush. 43 | /// which should be applied when no match is indicated 44 | /// (this can be default(Color) in which case Transparent is applied). 45 | /// 46 | Color NormalBackground { get; } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /source/Components/FilterTreeViewLib/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("FilterTreeViewLib")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("FilterTreeViewLib")] 13 | [assembly: AssemblyCopyright("Copyright © 2017")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("c5030463-0469-4afa-92f4-f6b55a6b4c89")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /source/Components/FilterTreeViewLib/Resources/Locations/appbar.base.select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirkster99/FilterTreeView/71c690e00af040e64b4d6f0cb9270565e59789de/source/Components/FilterTreeViewLib/Resources/Locations/appbar.base.select.png -------------------------------------------------------------------------------- /source/Components/FilterTreeViewLib/Resources/Locations/appbar.chess.rook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirkster99/FilterTreeView/71c690e00af040e64b4d6f0cb9270565e59789de/source/Components/FilterTreeViewLib/Resources/Locations/appbar.chess.rook.png -------------------------------------------------------------------------------- /source/Components/FilterTreeViewLib/Resources/Locations/appbar.city.sanfrancisco.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirkster99/FilterTreeView/71c690e00af040e64b4d6f0cb9270565e59789de/source/Components/FilterTreeViewLib/Resources/Locations/appbar.city.sanfrancisco.png -------------------------------------------------------------------------------- /source/Components/FilterTreeViewLib/Resources/ZoomIn.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirkster99/FilterTreeView/71c690e00af040e64b4d6f0cb9270565e59789de/source/Components/FilterTreeViewLib/Resources/ZoomIn.ico -------------------------------------------------------------------------------- /source/Components/FilterTreeViewLib/Resources/ZoomIn_256x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirkster99/FilterTreeView/71c690e00af040e64b4d6f0cb9270565e59789de/source/Components/FilterTreeViewLib/Resources/ZoomIn_256x.png -------------------------------------------------------------------------------- /source/Components/FilterTreeViewLib/Resources/ZoomIn_48x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirkster99/FilterTreeView/71c690e00af040e64b4d6f0cb9270565e59789de/source/Components/FilterTreeViewLib/Resources/ZoomIn_48x.png -------------------------------------------------------------------------------- /source/Components/FilterTreeViewLib/Resources/ZoomIn_64x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirkster99/FilterTreeView/71c690e00af040e64b4d6f0cb9270565e59789de/source/Components/FilterTreeViewLib/Resources/ZoomIn_64x.png -------------------------------------------------------------------------------- /source/Components/FilterTreeViewLib/ViewModels/AppBaseViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace FilterTreeViewLib.ViewModels 2 | { 3 | using System.Threading.Tasks; 4 | 5 | public abstract class AppBaseViewModel : Base.BaseViewModel 6 | { 7 | #region fields 8 | private bool _IsStringContainedSearchOption; 9 | private int _CountSearchMatches; 10 | private bool _IsProcessing; 11 | private bool _IsLoading; 12 | 13 | private string _StatusStringResult; 14 | private string _SearchString; 15 | #endregion fields 16 | 17 | #region constructors 18 | /// 19 | /// Class constructor 20 | /// 21 | public AppBaseViewModel() 22 | { 23 | _SearchString = "Washington"; 24 | _IsProcessing = _IsLoading = false; 25 | _CountSearchMatches = 0; 26 | _IsStringContainedSearchOption = true; 27 | } 28 | #endregion constructors 29 | 30 | #region properties 31 | /// 32 | /// Gets a property to determine if application is currently processing 33 | /// data (loading or searching for matches in the tree view) or not. 34 | /// 35 | public bool IsProcessing 36 | { 37 | get { return _IsProcessing; } 38 | protected set 39 | { 40 | if (_IsProcessing != value) 41 | { 42 | _IsProcessing = value; 43 | NotifyPropertyChanged(() => IsProcessing); 44 | } 45 | } 46 | } 47 | 48 | /// 49 | /// Gets a property to determine if application is currently processing 50 | /// data (loading or searching for matches in the tree view) or not. 51 | /// 52 | public bool IsLoading 53 | { 54 | get { return _IsLoading; } 55 | protected set 56 | { 57 | if (_IsLoading != value) 58 | { 59 | _IsLoading = value; 60 | NotifyPropertyChanged(() => IsLoading); 61 | } 62 | } 63 | } 64 | 65 | /// 66 | /// Gets the input string from search textbox control. 67 | /// 68 | public string SearchString 69 | { 70 | get { return _SearchString; } 71 | set 72 | { 73 | if (_SearchString != value) 74 | { 75 | _SearchString = value; 76 | NotifyPropertyChanged(() => SearchString); 77 | } 78 | } 79 | } 80 | 81 | /// 82 | /// Gets the search string that is synchronized with the results from the search algorithm. 83 | /// 84 | public string StatusStringResult 85 | { 86 | get { return _StatusStringResult; } 87 | protected set 88 | { 89 | if (_StatusStringResult != value) 90 | { 91 | _StatusStringResult = value; 92 | NotifyPropertyChanged(() => StatusStringResult); 93 | } 94 | } 95 | } 96 | 97 | /// 98 | /// Determines whether the search is looking for: 99 | /// - strings that are contained in a given string or 100 | /// - strings that match the searched string with all letters. 101 | /// 102 | public bool IsStringContainedSearchOption 103 | { 104 | get { return _IsStringContainedSearchOption; } 105 | set 106 | { 107 | if (_IsStringContainedSearchOption != value) 108 | { 109 | _IsStringContainedSearchOption = value; 110 | NotifyPropertyChanged(() => IsStringContainedSearchOption); 111 | } 112 | } 113 | } 114 | 115 | /// 116 | /// Gets the number of matches that are found durring 117 | /// a search in the tree view nodes for a given string. 118 | /// 119 | public int CountSearchMatches 120 | { 121 | get { return _CountSearchMatches; } 122 | protected set 123 | { 124 | if (_CountSearchMatches != value) 125 | { 126 | _CountSearchMatches = value; 127 | NotifyPropertyChanged(() => CountSearchMatches); 128 | } 129 | } 130 | } 131 | #endregion properties 132 | 133 | #region methods 134 | /// 135 | /// Loads the initial sample data from XML file into memory 136 | /// 137 | /// 138 | public abstract Task LoadSampleDataAsync(); 139 | #endregion methods 140 | } 141 | } -------------------------------------------------------------------------------- /source/Components/FilterTreeViewLib/ViewModels/Base/BaseViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace FilterTreeViewLib.ViewModels.Base 2 | { 3 | using System; 4 | using System.ComponentModel; 5 | using System.Linq.Expressions; 6 | 7 | /// 8 | /// Every ViewModel class is required to implement the INotifyPropertyChanged 9 | /// interface in order to tell WPF when a property changed (for instance, when 10 | /// a method or setter is executed). 11 | /// 12 | /// Therefore, the PropertyChanged methode has to be called when data changes, 13 | /// because the relevant properties may or may not be bound to GUI elements, 14 | /// which in turn have to refresh their display. 15 | /// 16 | /// The PropertyChanged method is to be called by the members and properties of 17 | /// the class that derives from this class. Each call contains the name of the 18 | /// property that has to be refreshed. 19 | /// 20 | /// The BaseViewModel is derived from from System.Windows.DependencyObject to allow 21 | /// resulting ViewModels the implemantion of dependency properties. Dependency properties 22 | /// in turn are useful when working with IValueConverter and ConverterParameters. 23 | /// 24 | public class BaseViewModel : INotifyPropertyChanged 25 | { 26 | /// 27 | /// Standard event handler of the interface 28 | /// 29 | public event PropertyChangedEventHandler PropertyChanged; 30 | 31 | /// 32 | /// Tell bound controls (via WPF binding) to refresh their display. 33 | /// 34 | /// Sample call: this.NotifyPropertyChanged(() => this.IsSelected); 35 | /// where 'this' is derived from 36 | /// and IsSelected is a property. 37 | /// 38 | /// 39 | /// 40 | public void NotifyPropertyChanged(Expression> property) 41 | { 42 | var lambda = (LambdaExpression)property; 43 | MemberExpression memberExpression; 44 | 45 | if (lambda.Body is UnaryExpression) 46 | { 47 | var unaryExpression = (UnaryExpression)lambda.Body; 48 | memberExpression = (MemberExpression)unaryExpression.Operand; 49 | } 50 | else 51 | memberExpression = (MemberExpression)lambda.Body; 52 | 53 | this.OnPropertyChanged(memberExpression.Member.Name); 54 | } 55 | 56 | /// 57 | /// Tell bound controls (via WPF binding) to refresh their display. 58 | /// 59 | /// Sample call: this.OnPropertyChanged("IsSelected"); 60 | /// where 'this' is derived from 61 | /// and IsSelected is a property. 62 | /// 63 | /// Name of property to refresh 64 | public void OnPropertyChanged(string propertyName) 65 | { 66 | try 67 | { 68 | if (this.PropertyChanged != null) 69 | this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); 70 | } 71 | catch 72 | { 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /source/Components/FilterTreeViewLib/ViewModels/Base/RelayCommand.cs: -------------------------------------------------------------------------------- 1 | namespace FilterTreeViewLib.ViewModels.Base 2 | { 3 | using System; 4 | using System.Diagnostics; 5 | using System.Windows.Input; 6 | 7 | /// 8 | /// A class whose sole purpose is to relay its functionality to other 9 | /// objects by invoking delegates. 10 | /// 11 | /// The default return value for the CanExecute method is 'true'. 12 | /// 13 | /// Source: http://www.codeproject.com/Articles/31837/Creating-an-Internationalized-Wizard-in-WPF 14 | /// 15 | public class RelayCommand : ICommand 16 | { 17 | #region Fields 18 | private readonly Action mExecute = null; 19 | private readonly Predicate mCanExecute = null; 20 | #endregion // Fields 21 | 22 | #region Constructors 23 | /// 24 | /// Class constructor 25 | /// 26 | /// 27 | public RelayCommand(Action execute) 28 | : this(execute, null) 29 | { 30 | } 31 | 32 | /// 33 | /// Creates a new command. 34 | /// 35 | /// The execution logic. 36 | /// The execution status logic. 37 | public RelayCommand(Action execute, Predicate canExecute) 38 | { 39 | if (execute == null) 40 | throw new ArgumentNullException("execute"); 41 | 42 | this.mExecute = execute; 43 | this.mCanExecute = canExecute; 44 | } 45 | 46 | #endregion // Constructors 47 | 48 | #region events 49 | /// 50 | /// Eventhandler to re-evaluate whether this command can execute or not 51 | /// 52 | public event EventHandler CanExecuteChanged 53 | { 54 | add 55 | { 56 | if (this.mCanExecute != null) 57 | CommandManager.RequerySuggested += value; 58 | } 59 | 60 | remove 61 | { 62 | if (this.mCanExecute != null) 63 | CommandManager.RequerySuggested -= value; 64 | } 65 | } 66 | #endregion 67 | 68 | #region methods 69 | /// 70 | /// Determine whether this pre-requisites to execute this command are given or not. 71 | /// 72 | /// 73 | /// 74 | [DebuggerStepThrough] 75 | public bool CanExecute(object parameter) 76 | { 77 | return this.mCanExecute == null ? true : this.mCanExecute((T)parameter); 78 | } 79 | 80 | /// 81 | /// Execute the command method managed in this class. 82 | /// 83 | /// 84 | public void Execute(object parameter) 85 | { 86 | this.mExecute((T)parameter); 87 | } 88 | #endregion methods 89 | } 90 | } 91 | 92 | -------------------------------------------------------------------------------- /source/Components/FilterTreeViewLib/ViewModels/Tree/MetaLocationRootViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace FilterTreeViewLib.ViewModels 2 | { 3 | using FilterTreeViewLib.ViewModels.Base; 4 | using FilterTreeViewLib.ViewModelsSearch.SearchModels; 5 | using FilterTreeViewLib.ViewModelsSearch.SearchModels.Enums; 6 | using System.Collections.Generic; 7 | using System.Collections.ObjectModel; 8 | using System.Linq; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | using System.Windows.Data; 12 | using System.Windows.Input; 13 | 14 | /// 15 | /// Implements the root viewmodel of the tree structure to be presented in a treeview. 16 | /// 17 | public class MetaLocationRootViewModel : Base.BaseViewModel 18 | { 19 | #region fields 20 | protected readonly ObservableCollection _CountryRootItems = null; 21 | protected readonly IList _BackUpCountryRoots = null; 22 | 23 | private ICommand _ExpandCommand; 24 | 25 | private object _itemsLock = new object(); 26 | #endregion fields 27 | 28 | #region constructors 29 | /// 30 | /// Class constructor 31 | /// 32 | public MetaLocationRootViewModel() 33 | { 34 | _CountryRootItems = new ObservableCollection(); 35 | BindingOperations.EnableCollectionSynchronization(_CountryRootItems, _itemsLock); 36 | 37 | _BackUpCountryRoots = new List(400); 38 | } 39 | #endregion constructors 40 | 41 | #region properties 42 | /// 43 | /// Gets all bindable rootitems of the displayed treeview 44 | /// 45 | public ObservableCollection CountryRootItems 46 | { 47 | get 48 | { 49 | return _CountryRootItems; 50 | } 51 | } 52 | 53 | /// 54 | /// Gets the total count of (backup) rootitems in the tree collectiton. 55 | /// 56 | /// This number can be larger than the nummber of items in the 57 | /// collection if a filter is currently applied. 58 | /// 59 | public int BackUpCountryRootsCount 60 | { 61 | get 62 | { 63 | return _BackUpCountryRoots.Count(); 64 | } 65 | } 66 | 67 | /// 68 | /// Gets a command that expands the item given in the command parameter. 69 | /// 70 | public ICommand ExpandCommand 71 | { 72 | get 73 | { 74 | if (_ExpandCommand == null) 75 | { 76 | _ExpandCommand = new RelayCommand((p) => 77 | { 78 | var param = p as MetaLocationViewModel; 79 | 80 | if (param == null) 81 | return; 82 | 83 | param.LoadChildren(); 84 | }); 85 | } 86 | 87 | return _ExpandCommand; 88 | } 89 | } 90 | #endregion properties 91 | 92 | #region methods 93 | /// 94 | /// 95 | /// 96 | /// 97 | /// 98 | /// 99 | /// 100 | public async Task LoadData( 101 | string zipContainerFile 102 | , string countryXMLFile 103 | ,string regionsXMLFile 104 | ,string citiesXMLFile) 105 | { 106 | var isoCountries = await BusinessLib.Database.LoadData(zipContainerFile 107 | , countryXMLFile 108 | , regionsXMLFile 109 | , citiesXMLFile); 110 | 111 | foreach (var item in isoCountries) 112 | { 113 | lock (_itemsLock) 114 | { 115 | var vmItem = MetaLocationViewModel.GetViewModelFromModel(item); 116 | 117 | RootsAdd(vmItem, true); 118 | //Root.RootsAdd(vmItem, false); // Make all items initially visible 119 | } 120 | } 121 | } 122 | 123 | /// 124 | /// Implements the Async version of a PostOrder Search algorithm 125 | /// over a tree structure that can have n-root nodes. 126 | /// 127 | /// PostOrder Algorithm Source: 128 | /// https://blogs.msdn.microsoft.com/daveremy/2010/03/16/non-recursive-post-order-depth-first-traversal-in-c/ 129 | /// 130 | /// 131 | public async Task DoSearchAsync(SearchParams searchParams, CancellationToken token) 132 | { 133 | return await Task.Run(() => 134 | { 135 | return DoSearch(searchParams, token); 136 | }); 137 | } 138 | 139 | /// 140 | /// Implements the Async version of a PostOrder Search algorithm 141 | /// over a tree structure that can have n-root nodes. 142 | /// 143 | /// PostOrder Algorithm Source: 144 | /// https://blogs.msdn.microsoft.com/daveremy/2010/03/16/non-recursive-post-order-depth-first-traversal-in-c/ 145 | /// 146 | /// 147 | public int DoSearch(SearchParams searchParams, CancellationToken token) 148 | { 149 | IList backUpRoots = _BackUpCountryRoots; 150 | ObservableCollection root = _CountryRootItems; 151 | 152 | if (searchParams == null) 153 | searchParams = new SearchParams(); 154 | 155 | searchParams.SearchStringTrim(); 156 | searchParams.SearchStringToUpperCase(); 157 | 158 | lock (_itemsLock) 159 | { 160 | root.Clear(); 161 | } 162 | 163 | // Show all root items if string to search is empty 164 | if (searchParams.IsSearchStringEmpty == true || 165 | searchParams.MinimalSearchStringLength >= searchParams.SearchString.Length) 166 | { 167 | foreach (var rootItem in backUpRoots) 168 | { 169 | if (token.IsCancellationRequested == true) 170 | return 0; 171 | 172 | //PreOrderTraversal(rootItem); !!! Lazy Loading Children !!! 173 | rootItem.ChildrenClear(false); 174 | rootItem.SetExpand(false); 175 | 176 | lock (_itemsLock) 177 | { 178 | root.Add(rootItem); 179 | } 180 | } 181 | 182 | return 0; 183 | } 184 | 185 | int imatchCount = 0; 186 | 187 | // Go through all root items and process their children 188 | foreach (var rootItem in backUpRoots) 189 | { 190 | if (token.IsCancellationRequested == true) 191 | return 0; 192 | 193 | rootItem.SetMatch(MatchType.NoMatch); 194 | 195 | // Match children of this root item 196 | var nodeMatchCount = MatchNodes(rootItem, searchParams); 197 | 198 | imatchCount += nodeMatchCount; 199 | 200 | // Match this root item and find correct match type between 201 | // parent and children below 202 | int offset = -1; 203 | if ((offset = searchParams.MatchSearchString(rootItem.LocalName)) >= 0) 204 | { 205 | rootItem.SetMatch(MatchType.NodeMatch, offset, offset+searchParams.SearchString.Length); 206 | imatchCount++; 207 | } 208 | 209 | if (nodeMatchCount > 0) 210 | { 211 | if (rootItem.Match == MatchType.NodeMatch) 212 | rootItem.SetMatch(MatchType.Node_AND_SubNodeMatch, offset, offset + searchParams.SearchString.Length); 213 | else 214 | rootItem.SetMatch(MatchType.SubNodeMatch); 215 | } 216 | 217 | // Determine wether root item should be visible and expanded or not 218 | if (rootItem.Match != MatchType.NoMatch) 219 | { 220 | if ((rootItem.Match & (MatchType.SubNodeMatch | MatchType.Node_AND_SubNodeMatch)) != 0) 221 | rootItem.SetExpand(true); 222 | else 223 | rootItem.SetExpand(false); 224 | 225 | //Console.WriteLine("node: {0} match count: {1}", rootItem.LocalName, nodeMatchCount); 226 | lock (_itemsLock) 227 | { 228 | root.Add(rootItem); 229 | } 230 | 231 | } 232 | } 233 | 234 | return imatchCount; 235 | } 236 | 237 | /// 238 | /// Add a rootitem into the root items collection - 239 | /// always adds into the backup root items collection and 240 | /// only adds into the observable collection 241 | /// if is false. 242 | /// 243 | /// 244 | /// 245 | protected void RootsAdd(MetaLocationViewModel vmItem 246 | , bool addBackupItemOnly) 247 | { 248 | _BackUpCountryRoots.Add(vmItem); 249 | 250 | if (addBackupItemOnly == false) 251 | _CountryRootItems.Add(vmItem); 252 | } 253 | 254 | /// 255 | /// Implement a PostOrder matching algorithm with one root node 256 | /// and returns the number of matching children found. 257 | /// 258 | /// 259 | /// 260 | /// 261 | private Task MatchNodesAsync( 262 | MetaLocationViewModel root 263 | , SearchParams searchParams) 264 | { 265 | return Task.Run(() => { return MatchNodes(root, searchParams); }); 266 | } 267 | 268 | /// 269 | /// Implement a PostOrder matching algorithm with one root node 270 | /// and returns the number of matching children found. 271 | /// 272 | /// 273 | /// 274 | /// 275 | private int MatchNodes( 276 | MetaLocationViewModel root 277 | , SearchParams searchParams) 278 | { 279 | var toVisit = new Stack(); 280 | var visitedAncestors = new Stack(); 281 | int MatchCount = 0; 282 | 283 | toVisit.Push(root); 284 | while (toVisit.Count > 0) 285 | { 286 | var node = toVisit.Peek(); 287 | if (node.ChildrenCount > 0) 288 | { 289 | if (PeekOrDefault(visitedAncestors) != node) 290 | { 291 | visitedAncestors.Push(node); 292 | PushReverse(toVisit, node.BackUpNodes); 293 | continue; 294 | } 295 | 296 | visitedAncestors.Pop(); 297 | } 298 | 299 | // Process Node and count matches (if any) 300 | int matchStart = -1; 301 | MatchType match = MatchType.NoMatch; 302 | match = node.ProcessNodeMatch(searchParams, out matchStart); 303 | node.SetMatch(match, matchStart, matchStart+ searchParams.SearchString.Length); 304 | 305 | if (node.Match == MatchType.NodeMatch) 306 | MatchCount++; 307 | 308 | if (node.Match == MatchType.SubNodeMatch || 309 | node.Match == MatchType.Node_AND_SubNodeMatch) 310 | { 311 | node.SetExpand(true); 312 | } 313 | else 314 | node.SetExpand(false); 315 | 316 | toVisit.Pop(); 317 | } 318 | 319 | return MatchCount; 320 | } 321 | 322 | /// 323 | /// Return the top element of stack or null if the Stack is empty. 324 | /// 325 | /// 326 | /// 327 | private MetaLocationViewModel PeekOrDefault(Stack s) 328 | { 329 | return s.Count == 0 ? null : s.Peek(); 330 | } 331 | 332 | /// 333 | /// Push all children of a given node in reverse order into the 334 | /// . 335 | /// 336 | /// Use this to traverse the tree from left to right. 337 | /// 338 | /// 339 | /// 340 | private void PushReverse( 341 | Stack s 342 | , IEnumerable list) 343 | { 344 | foreach (var l in list.ToArray().Reverse()) 345 | { 346 | s.Push(l); 347 | } 348 | } 349 | /*** 350 | /// 351 | /// Traverse the tree from root to child and add child nodes from underneath 352 | /// each parent into the bound collection. Children are not really added but 353 | /// transferred from the BackUpNodes collection into the Children collection. 354 | /// 355 | /// http://urosv.blogspot.de/2011/04/iterative-binary-tree-traversal.html 356 | /// Preorder is the simplest iterative tree traversal. 357 | /// 358 | /// We start by pushing the tree root to the stack.Then, until the stack is empty, 359 | /// we repeat the following routine: 360 | /// - pop the next node and then push its children to the stack. 361 | /// 362 | /// 363 | private static void PreOrderTraversal(MetaLocationViewModel solutionRoot) 364 | { 365 | Stack stack = new Stack(); 366 | 367 | if (solutionRoot != null) 368 | stack.Push(solutionRoot); 369 | 370 | while (stack.Count() > 0) 371 | { 372 | MetaLocationViewModel current = stack.Pop(); 373 | 374 | //System.Console.WriteLine(string.Format("{0}", current.GetStackPath())); 375 | current.ChildrenClear(false); 376 | 377 | foreach (var item in current.BackUpNodes) 378 | { 379 | current.ChildrenAdd(item, false); 380 | stack.Push(item); 381 | } 382 | } 383 | } 384 | ***/ 385 | #endregion methods 386 | } 387 | } -------------------------------------------------------------------------------- /source/Components/FilterTreeViewLib/ViewModels/Tree/MetaLocationViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace FilterTreeViewLib.ViewModels 2 | { 3 | using BusinessLib.Models; 4 | using FilterTreeViewLib.Interfaces; 5 | using FilterTreeViewLib.ViewModels.Tree.Search; 6 | using FilterTreeViewLib.ViewModelsSearch.SearchModels; 7 | using FilterTreeViewLib.ViewModelsSearch.SearchModels.Enums; 8 | using System.Collections.Generic; 9 | using System.Collections.ObjectModel; 10 | using System.Linq; 11 | using System.Windows.Data; 12 | 13 | /// 14 | /// Implements an items viewmodel that should be bound to an items collection in a tree view. 15 | /// 16 | public class MetaLocationViewModel : Base.BaseViewModel, IHasDummyChild 17 | { 18 | #region fields 19 | private static readonly MetaLocationViewModel DummyChild = new MetaLocationViewModel(); 20 | 21 | private bool _IsItemExpanded; 22 | private List _BackUpNodes = null; 23 | 24 | private readonly ObservableCollection _Children = null; 25 | private MatchType _Match; 26 | private string _LocalName; 27 | private ISelectionRange _Range = null; 28 | 29 | private object _itemsLock = new object(); 30 | #endregion fields 31 | 32 | #region constructors 33 | /// 34 | /// Parameterized Class Constructor 35 | /// 36 | public MetaLocationViewModel( 37 | BusinessLib.Models.MetaLocationModel locationModel 38 | , MetaLocationViewModel parent 39 | ) 40 | : this() 41 | { 42 | Parent = parent; 43 | 44 | _LocalName = locationModel.LocalName; 45 | ID = locationModel.ID; 46 | Latitude = locationModel.Geo_lat; 47 | Longitude = locationModel.Geo_lng; 48 | TypeOfLocation = locationModel.Type; 49 | 50 | ChildrenClear(false); // Lazy Load Children !!! 51 | } 52 | 53 | /// 54 | /// Class Constructor 55 | /// 56 | protected MetaLocationViewModel() 57 | { 58 | _IsItemExpanded = false; 59 | 60 | _Children = new ObservableCollection(); 61 | BindingOperations.EnableCollectionSynchronization(_Children, _itemsLock); 62 | 63 | _BackUpNodes = new List(); 64 | 65 | _Match = MatchType.NoMatch; 66 | Parent = null; 67 | } 68 | #endregion constructors 69 | 70 | #region properties 71 | /// 72 | /// Gets the Parent (if any) of this item. 73 | /// 74 | public MetaLocationViewModel Parent { get; private set; } 75 | 76 | /// 77 | /// Gets the for this item. This field 78 | /// determines the classification of a match towards a current 79 | /// search criteria. 80 | /// 81 | public MatchType Match 82 | { 83 | get { return _Match; } 84 | 85 | private set 86 | { 87 | if (_Match != value) 88 | { 89 | _Match = value; 90 | NotifyPropertyChanged(() => Match); 91 | } 92 | } 93 | } 94 | 95 | /// 96 | /// Gets the range property that indicates the text range 97 | /// to be considered a match against another (search) string. 98 | /// 99 | public ISelectionRange Range 100 | { 101 | get 102 | { 103 | return _Range; 104 | } 105 | 106 | private set 107 | { 108 | if (_Range != null && value != null) 109 | { 110 | // Nothing changed - so we change nothing here :-) 111 | if (_Range.Start == value.Start && 112 | _Range.End == value.End && 113 | _Range.SelectionBackground == value.SelectionBackground && 114 | _Range.SelectionBackground == value.SelectionBackground) 115 | return; 116 | 117 | _Range = (ISelectionRange)value.Clone(); 118 | NotifyPropertyChanged(() => Range); 119 | } 120 | 121 | if (_Range == null && value != null || 122 | _Range != null && value == null) 123 | { 124 | _Range = (ISelectionRange)value.Clone(); 125 | NotifyPropertyChanged(() => Range); 126 | } 127 | } 128 | } 129 | 130 | /// 131 | /// Gets/sets whether this item is expanded in 132 | /// the tree view (items under this item are visible), or not. 133 | /// 134 | public bool IsItemExpanded 135 | { 136 | get { return _IsItemExpanded; } 137 | set 138 | { 139 | if (_IsItemExpanded != value) 140 | { 141 | _IsItemExpanded = value; 142 | NotifyPropertyChanged(() => IsItemExpanded); 143 | } 144 | } 145 | } 146 | 147 | /// 148 | /// Gets the name of this item (name of country, region, or city) 149 | /// 150 | public string LocalName 151 | { 152 | get 153 | { 154 | return _LocalName; 155 | } 156 | 157 | private set 158 | { 159 | if (_LocalName != value) 160 | { 161 | _LocalName = value; 162 | NotifyPropertyChanged(() => LocalName); 163 | } 164 | } 165 | } 166 | 167 | /// 168 | /// Gets the technical ID (for data reference) of this item. 169 | /// 170 | public int ID { get; } 171 | 172 | /// 173 | /// Gets the Geographical Latitude (if any) of this item. 174 | /// 175 | public double Latitude { get; } 176 | 177 | /// 178 | /// Gets the Geographical Longitude (if any) of this item. 179 | /// 180 | public double Longitude { get; } 181 | 182 | /// 183 | /// Gets the type of location of this object. 184 | /// 185 | public LocationType TypeOfLocation { get; } 186 | 187 | /// 188 | /// Gets the COMPLETE collection of child items under this item (complete sub-tree). 189 | /// This collection is always complete, maintained, and always available list of 190 | /// items under this item. 191 | /// 192 | /// The collection contains child items (sub-tree) matched 193 | /// to the current search criteria (if any). 194 | /// 195 | public IEnumerable BackUpNodes 196 | { 197 | get 198 | { 199 | return _BackUpNodes; 200 | } 201 | } 202 | 203 | /// 204 | /// Gets all child items (nodes) under this item. This collection (sub-tree) 205 | /// may hold no items, if this item is filtered and none of the items in the 206 | /// collection matched the current search criteria. 207 | /// 208 | /// The collection contains the complete, maintained, 209 | /// and always available list of items under this item. 210 | /// 211 | public IEnumerable Children 212 | { 213 | get 214 | { 215 | return _Children; 216 | } 217 | } 218 | 219 | /// 220 | /// Returns the total number of child items (nodes) under this item. 221 | /// 222 | public int ChildrenCount => _BackUpNodes.Count; 223 | 224 | /// 225 | /// Determines whether this item has a dummy child below or not. 226 | /// 227 | public virtual bool HasDummyChild 228 | { 229 | get 230 | { 231 | if (this.Children != null) 232 | { 233 | if (this._Children.Count == 1) 234 | { 235 | if (this._Children[0] == DummyChild) 236 | return true; 237 | } 238 | } 239 | 240 | return false; 241 | } 242 | } 243 | #endregion properties 244 | 245 | #region methods 246 | /// 247 | /// Returns the string path either: 248 | /// 1) for the item or 249 | /// 2) for this item (if optional parameter is not set). 250 | /// 251 | /// 252 | /// 253 | public string GetStackPath(MetaLocationViewModel current = null) 254 | { 255 | if (current == null) 256 | current = this; 257 | 258 | string result = string.Empty; 259 | 260 | // Traverse the list of parents backwards and 261 | // add each child to the path 262 | while (current != null) 263 | { 264 | result = "/" + LocalName + result; 265 | 266 | current = current.Parent; 267 | } 268 | 269 | return result; 270 | } 271 | 272 | /// 273 | /// The TREELIB (see FilterTreeViewLib (Project) > Properties > Conditional Compilation Symbol) 274 | /// switch determines whether the TreeLib LevelOrder traversal function is used or not. 275 | /// The point for training is here: 276 | /// 277 | /// Both functions are equivalent but using the TreeLib traversal function should simplify 278 | /// the problem of efficiently traversing any tree in the given order. 279 | /// 280 | #if TREELIB 281 | /// 282 | /// Convert a Model into a ViewModel using 283 | /// a LevelOrderTraversal Algorithm via TreeLib library. 284 | /// 285 | internal static MetaLocationViewModel GetViewModelFromModel(MetaLocationModel srcRoot) 286 | { 287 | if (srcRoot == null) 288 | return null; 289 | 290 | var srcItems = TreeLib.BreadthFirst.Traverse.LevelOrder(srcRoot, i => i.Children); 291 | var dstIdItems = new Dictionary(); 292 | MetaLocationViewModel dstRoot = null; 293 | 294 | foreach (var node in srcItems.Select(i => i.Node)) 295 | { 296 | if (node.Parent == null) 297 | { 298 | dstRoot = new MetaLocationViewModel(node, null); 299 | dstIdItems.Add(dstRoot.ID, dstRoot); 300 | } 301 | else 302 | { 303 | MetaLocationViewModel vmParentItem; // Find parent ViewModel for Model 304 | dstIdItems.TryGetValue(node.Parent.ID, out vmParentItem); 305 | 306 | var dstNode = new MetaLocationViewModel(node, vmParentItem); 307 | vmParentItem.ChildrenAdd(dstNode); // Insert converted ViewModel below ViewModel parent 308 | dstIdItems.Add(dstNode.ID, dstNode); 309 | } 310 | } 311 | 312 | dstIdItems.Clear(); // Destroy temp ID look-up structure 313 | 314 | return dstRoot; 315 | } 316 | #else 317 | /// 318 | /// Convert a Model into a ViewModel using 319 | /// a LevelOrderTraversal Algorithm 320 | /// 321 | /// 322 | internal static MetaLocationViewModel GetViewModelFromModel(MetaLocationModel srcRoot) 323 | { 324 | if (srcRoot == null) 325 | return null; 326 | 327 | MetaLocationViewModel dstRoot = new MetaLocationViewModel(srcRoot, null); 328 | 329 | Queue srcQueue = new Queue(); 330 | Queue dstQueue = new Queue(); 331 | 332 | srcQueue.Enqueue(srcRoot); 333 | dstQueue.Enqueue(dstRoot); 334 | 335 | while (srcQueue.Count() > 0) 336 | { 337 | MetaLocationModel srcCurrent = srcQueue.Dequeue(); 338 | MetaLocationViewModel dstCurrent = dstQueue.Dequeue(); 339 | 340 | ////Console.WriteLine(string.Format("{0,4} - {1}" 341 | //// , iLevel, current.GetPath())); 342 | 343 | foreach (var item in srcCurrent.Children) 344 | { 345 | var dstVM = new MetaLocationViewModel(item, dstCurrent); 346 | 347 | dstCurrent.ChildrenAddBackupNodes(dstVM); 348 | 349 | srcQueue.Enqueue(item); 350 | dstQueue.Enqueue(dstVM); 351 | } 352 | } 353 | 354 | return dstRoot; 355 | } 356 | #endif 357 | 358 | /// 359 | /// Re-load treeview items below the root item. 360 | /// 361 | /// Number of items found below the root item. 362 | internal int LoadChildren() 363 | { 364 | ChildrenClear(false, false); // Clear collection of children 365 | 366 | if (_BackUpNodes.Count() > 0) 367 | { 368 | foreach (var item in _BackUpNodes) 369 | this.ChildrenAdd(item, false); 370 | } 371 | 372 | return _Children.Count; 373 | } 374 | 375 | internal void ChildrenClear(bool bClearBackup = true 376 | , bool bAddDummyChild = true) 377 | { 378 | try 379 | { 380 | lock (_itemsLock) 381 | { 382 | _Children.Clear(); 383 | 384 | // Cities do not have children so we need no dummy child here 385 | if (bAddDummyChild == true && TypeOfLocation != LocationType.City) 386 | _Children.Add(DummyChild); 387 | } 388 | 389 | if (bClearBackup == true) 390 | _BackUpNodes.Clear(); 391 | } 392 | catch 393 | { 394 | } 395 | } 396 | 397 | /// 398 | /// Determines the of a node by evaluating the 399 | /// given search string parameter and the matchtype of its children. 400 | /// 401 | /// 402 | /// 403 | internal MatchType ProcessNodeMatch(SearchParams searchParams, out int MatchStart) 404 | { 405 | MatchStart = -1; 406 | MatchType matchThisNode = MatchType.NoMatch; 407 | 408 | // Determine whether this node is a match or not 409 | if ((MatchStart = searchParams.MatchSearchString(LocalName)) >= 0) 410 | matchThisNode = MatchType.NodeMatch; 411 | 412 | ChildrenClear(false); 413 | 414 | if (ChildrenCount > 0) 415 | { 416 | // Evaluate children by adding only thos children that contain no 'NoMatch' 417 | MatchType maxChildMatch = MatchType.NoMatch; 418 | foreach (var item in BackUpNodes) 419 | { 420 | if (item.Match != MatchType.NoMatch) 421 | { 422 | // Expand this item if it (or one of its children) contains a match 423 | if (item.Match == MatchType.SubNodeMatch || 424 | item.Match == MatchType.Node_AND_SubNodeMatch) 425 | { 426 | item.SetExpand(true); 427 | } 428 | else 429 | item.SetExpand(false); 430 | 431 | if (maxChildMatch < item.Match) 432 | maxChildMatch = item.Match; 433 | 434 | ChildrenAdd(item, false); 435 | } 436 | } 437 | 438 | if (matchThisNode == MatchType.NoMatch && maxChildMatch != MatchType.NoMatch) 439 | matchThisNode = MatchType.SubNodeMatch; 440 | 441 | if (matchThisNode == MatchType.NodeMatch && maxChildMatch != MatchType.NoMatch) 442 | matchThisNode = MatchType.Node_AND_SubNodeMatch; 443 | } 444 | 445 | return matchThisNode; 446 | } 447 | 448 | /// 449 | /// Sets the tree view item to the corresponding expanded state 450 | /// as indicated in the property. 451 | /// 452 | /// 453 | internal void SetExpand(bool isExpanded) 454 | { 455 | IsItemExpanded = isExpanded; 456 | } 457 | 458 | /// 459 | /// Sets the type of match detmerined for this item against a certain 460 | /// match criteria. 461 | /// 462 | /// We use this method instead of a setter to make this accessible for 463 | /// the root viewmodel but next to invisible for everyone else... 464 | /// 465 | /// 466 | /// 467 | internal MatchType SetMatch(MatchType match, 468 | int matchStart = -1, 469 | int matchEnd = -1) 470 | { 471 | this.Match = match; 472 | this.Range = new SelectionRange(matchStart, matchEnd); 473 | 474 | return match; 475 | } 476 | 477 | /// 478 | /// Add a child item including a reference to a backupnode 479 | /// (add to backupnode is determined by ). 480 | /// 481 | /// 482 | /// 483 | private void ChildrenAdd(MetaLocationViewModel child, bool bAddBackup = true) 484 | { 485 | try 486 | { 487 | if (HasDummyChild == true) 488 | { 489 | lock (_itemsLock) 490 | { 491 | _Children.Clear(); 492 | } 493 | } 494 | 495 | lock (_itemsLock) 496 | { 497 | _Children.Add(child); 498 | } 499 | 500 | if (bAddBackup == true) 501 | _BackUpNodes.Add(child); 502 | } 503 | catch 504 | { 505 | } 506 | } 507 | 508 | /// 509 | /// Add a child node to a backupnode only. 510 | /// 511 | /// 512 | private void ChildrenAddBackupNodes(MetaLocationViewModel child) 513 | { 514 | _BackUpNodes.Add(child); 515 | } 516 | 517 | private void ChildrenRemove(MetaLocationViewModel child, bool bRemoveBackup = true) 518 | { 519 | lock (_itemsLock) 520 | { 521 | _Children.Remove(child); 522 | } 523 | 524 | if (bRemoveBackup == true) 525 | _BackUpNodes.Remove(child); 526 | } 527 | #endregion methods 528 | } 529 | } 530 | -------------------------------------------------------------------------------- /source/Components/FilterTreeViewLib/ViewModels/Tree/Search/Enums/MatchType.cs: -------------------------------------------------------------------------------- 1 | namespace FilterTreeViewLib.ViewModelsSearch.SearchModels.Enums 2 | { 3 | /// 4 | /// Determines whether a givem node has a match with regard to search parameters 5 | /// - search string and 6 | /// - additional search options (eg.: string contained, exact match) 7 | /// 8 | /// This includes a reference to child items that might contain a 9 | /// match while the node itself may only be the parent of a child 10 | /// with an actual match. 11 | /// 12 | public enum MatchType 13 | { 14 | /// 15 | /// The node (and its children) contains no match. 16 | /// 17 | NoMatch = 0, 18 | 19 | /// 20 | /// The node was macth against the search parameters. 21 | /// 22 | NodeMatch = 1, 23 | 24 | /// 25 | /// A child node or children nodes of this node contain a match 26 | /// BUT this node does not contain a match. 27 | /// 28 | SubNodeMatch = 2, 29 | 30 | /// 31 | /// A child node or children nodes of this node contain a match 32 | /// AND this node does also contain a match. 33 | /// 34 | Node_AND_SubNodeMatch = 4 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /source/Components/FilterTreeViewLib/ViewModels/Tree/Search/Enums/SearchMatch.cs: -------------------------------------------------------------------------------- 1 | namespace FilterTreeViewLib.ViewModelsSearch.SearchModels.Enums 2 | { 3 | /// 4 | /// Determines the type of match that should be implemented to 5 | /// match a node to a search string during a search. 6 | /// 7 | public enum SearchMatch 8 | { 9 | /// 10 | /// The string searched is contained somewhere in within a nodes string. 11 | /// 12 | StringIsContained = 0, 13 | 14 | /// 15 | /// The string searched is an exact match within a nodes string 16 | /// (length of strings is identical and ALL letters are present 17 | /// in the given order - but case folding may still be applied). 18 | /// 19 | StringIsMatched = 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /source/Components/FilterTreeViewLib/ViewModels/Tree/Search/SearchParams.cs: -------------------------------------------------------------------------------- 1 | namespace FilterTreeViewLib.ViewModelsSearch.SearchModels 2 | { 3 | /// 4 | /// Implements a search object that contains the search string, 5 | /// related options, as well as methods to determine whether a 6 | /// given string is a match against the string or not. 7 | /// 8 | public class SearchParams 9 | { 10 | #region constructors 11 | /// 12 | /// Class constructor 13 | /// 14 | public SearchParams( 15 | string searchString 16 | , Enums.SearchMatch match) 17 | : this() 18 | { 19 | OriginalSearchString = SearchString = (searchString == null ? string.Empty : searchString); 20 | Match = match; 21 | } 22 | 23 | /// 24 | /// Class constructor 25 | /// 26 | public SearchParams() 27 | { 28 | SearchString = string.Empty; 29 | Match = Enums.SearchMatch.StringIsContained; 30 | MinimalSearchStringLength = 1; 31 | } 32 | #endregion constructors 33 | 34 | #region properties 35 | /// 36 | /// Gets the plain text string being searched or filtered. 37 | /// 38 | public string SearchString { get; private set; } 39 | 40 | /// 41 | /// Gets the plain text ORIGINAL string being searched or filtered. 42 | /// This string is the string that was set by the constructor and 43 | /// CANNOT be changed later on. 44 | /// 45 | public string OriginalSearchString { get; private set; } 46 | 47 | /// 48 | /// Gets the string being searched or filtered. 49 | /// 50 | public Enums.SearchMatch Match { get; private set; } 51 | 52 | /// 53 | /// Gets whether search string contains actual content or not. 54 | /// 55 | public bool IsSearchStringEmpty 56 | { 57 | get 58 | { 59 | return string.IsNullOrEmpty(SearchString); 60 | } 61 | } 62 | 63 | /// 64 | /// Gets the minimal search string length required. 65 | /// Any string shorter than this will not be searched at all. 66 | /// 67 | public int MinimalSearchStringLength { get; set; } 68 | #endregion properties 69 | 70 | #region methods 71 | /// 72 | /// Determines if a given string is considered a match in comparison 73 | /// to the search string and its options or not. 74 | /// 75 | /// 76 | /// true if is a match, otherwise false 77 | public int MatchSearchString(string stringToFind) 78 | { 79 | stringToFind = (stringToFind == null ? string.Empty : stringToFind); 80 | 81 | stringToFind = stringToFind.ToUpper(); 82 | 83 | switch (Match) 84 | { 85 | case Enums.SearchMatch.StringIsContained: 86 | return stringToFind.IndexOf(SearchString); 87 | 88 | case Enums.SearchMatch.StringIsMatched: 89 | if (SearchString == stringToFind) 90 | return 0; 91 | break; 92 | 93 | default: 94 | throw new System.ArgumentOutOfRangeException( 95 | string.Format("Internal Error: Search option '{0}' not implemented.", Match)); 96 | } 97 | 98 | return -1; 99 | } 100 | 101 | /// 102 | /// Can be called to trim the search string before matching takes place. 103 | /// 104 | public void SearchStringTrim() 105 | { 106 | SearchString = SearchString.Trim(); 107 | } 108 | 109 | /// 110 | /// Can be called to convert the search string 111 | /// to upper case before matching takes place. 112 | /// 113 | public void SearchStringToUpperCase() 114 | { 115 | SearchString = SearchString.ToUpper(); 116 | } 117 | #endregion methods 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /source/Components/FilterTreeViewLib/ViewModels/Tree/Search/SearchResult.cs: -------------------------------------------------------------------------------- 1 | namespace FilterTreeViewLib.ViewModelsSearch.SearchModels 2 | { 3 | /// 4 | /// Models the results of a search in terms of the actual string and options 5 | /// being searched and the results found. 6 | /// 7 | public class SearchResult 8 | { 9 | #region constructors 10 | /// 11 | /// Class constructor 12 | /// 13 | /// 14 | /// 15 | public SearchResult(SearchParams searchParams, int results) 16 | : this() 17 | { 18 | Options = searchParams; 19 | Results = results; 20 | } 21 | 22 | /// 23 | /// Hidden Class constructor 24 | /// 25 | protected SearchResult() 26 | { 27 | } 28 | #endregion constructors 29 | 30 | /// 31 | /// Gets the searchterm that was usedt o find the shown results. 32 | /// 33 | public string SearchTerm 34 | { 35 | get 36 | { 37 | if (Options != null) 38 | return Options.SearchString; 39 | 40 | return string.Empty; 41 | } 42 | } 43 | 44 | /// 45 | /// Gets the ORIGONAL searchterm that was used to find the shown results. 46 | /// 47 | public string OriginalSearchString 48 | { 49 | get 50 | { 51 | if (Options != null) 52 | return Options.OriginalSearchString; 53 | 54 | return string.Empty; 55 | } 56 | } 57 | 58 | /// 59 | /// Gets the search parameters and the search term for this result. 60 | /// 61 | public SearchParams Options { get; protected set; } 62 | 63 | /// 64 | /// Gets the number of matches found. 65 | /// 66 | public int Results { get; protected set; } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /source/Components/FilterTreeViewLib/ViewModels/Tree/Search/SelectionRange.cs: -------------------------------------------------------------------------------- 1 | namespace FilterTreeViewLib.ViewModels.Tree.Search 2 | { 3 | using System.Windows.Media; 4 | using FilterTreeViewLib.Interfaces; 5 | 6 | /// 7 | /// Implements a range object that can be used to indicate 8 | /// the start and end of a text selection or any other kind of range. 9 | /// 10 | public class SelectionRange : Base.BaseViewModel, ISelectionRange 11 | { 12 | #region fields 13 | private int _Start; 14 | private int _End; 15 | #endregion fields 16 | 17 | #region ctors 18 | /// 19 | /// Parameterized class constructor. 20 | /// 21 | /// 22 | /// 23 | public SelectionRange(int start, int end) 24 | : this() 25 | { 26 | Start = start; 27 | End = end; 28 | } 29 | 30 | /// 31 | /// Copy constructor 32 | /// 33 | /// 34 | public SelectionRange(SelectionRange copyThis) 35 | :this() 36 | { 37 | if (copyThis == null) 38 | return; 39 | 40 | Start = copyThis.Start; 41 | End = copyThis.End; 42 | SelectionBackground = copyThis.SelectionBackground; 43 | NormalBackground = copyThis.NormalBackground; 44 | DarkSkin = copyThis.DarkSkin; 45 | } 46 | 47 | /// 48 | /// Class constructor. 49 | /// 50 | public SelectionRange() 51 | { 52 | _Start = _End = 1; 53 | SelectionBackground = default(Color); 54 | NormalBackground = default(Color); 55 | DarkSkin = false; 56 | } 57 | #endregion ctors 58 | 59 | #region properties 60 | /// 61 | /// Gets the start of the indicated range. 62 | /// 63 | public int Start 64 | { 65 | get 66 | { 67 | return _Start; 68 | } 69 | 70 | private set 71 | { 72 | if (_Start != value) 73 | { 74 | _Start = value; 75 | NotifyPropertyChanged(() => Start); 76 | } 77 | } 78 | } 79 | 80 | /// 81 | /// Gets the end of the indicated range. 82 | /// 83 | public int End 84 | { 85 | get 86 | { 87 | return _End; 88 | } 89 | 90 | private set 91 | { 92 | if (_End != value) 93 | { 94 | _End = value; 95 | NotifyPropertyChanged(() => End); 96 | } 97 | } 98 | } 99 | 100 | /// 101 | /// Gets a bool value to determine whether DarkSkin default 102 | /// value for property should 103 | /// be applied or not. 104 | /// 105 | public bool DarkSkin { get; private set; } 106 | 107 | /// 108 | /// Gets the background color that is applied to the background brush, 109 | /// which should be applied when no match is indicated 110 | /// (this can be default(Color) in which case standard selection Brush 111 | /// is applied). 112 | /// 113 | public Color SelectionBackground { get; set; } 114 | 115 | /// 116 | /// Gets the background color that is applied to the background brush. 117 | /// which should be applied when no match is indicated 118 | /// (this can be default(Color) in which case Transparent is applied). 119 | /// 120 | public Color NormalBackground { get; set; } 121 | 122 | /// 123 | /// Gets a copy of this object. 124 | /// 125 | /// 126 | public object Clone() 127 | { 128 | return new SelectionRange(this); 129 | } 130 | #endregion properties 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /source/Components/FilterTreeViewLib/ViewModels/Tree/Search/StringMatchItem.cs: -------------------------------------------------------------------------------- 1 | namespace FilterTreeViewLib.ViewModels.Tree.Search 2 | { 3 | using FilterTreeViewLib.Interfaces; 4 | 5 | /// 6 | /// Implements a viewmodel that provides a string to display and can indicate 7 | /// a macthing range against another string via the property. 8 | /// 9 | public class StringMatchItem : Base.BaseViewModel 10 | { 11 | #region fields 12 | private ISelectionRange _Range; 13 | private string _DisplayString; 14 | #endregion fields 15 | 16 | #region ctors 17 | /// 18 | /// Parameterized class constructor. 19 | /// 20 | /// 21 | public StringMatchItem(string displayString) 22 | { 23 | DisplayString = displayString; 24 | } 25 | 26 | /// 27 | /// Class constructor. 28 | /// 29 | public StringMatchItem() 30 | { 31 | _Range = new SelectionRange(); 32 | _DisplayString = string.Empty; 33 | } 34 | #endregion ctors 35 | 36 | #region properties 37 | /// 38 | /// Gets the range property that indicates the text range 39 | /// to be considered a match against another (search) string. 40 | /// 41 | public ISelectionRange Range 42 | { 43 | get 44 | { 45 | return _Range; 46 | } 47 | 48 | private set 49 | { 50 | if (_Range != null && value != null) 51 | { 52 | // Nothing changed - so we change nothing here :-) 53 | if (_Range.Start == value.Start && 54 | _Range.End == value.End) 55 | return; 56 | 57 | _Range = value; 58 | NotifyPropertyChanged(() => Range); 59 | } 60 | 61 | if (_Range == null && value != null || 62 | _Range != null && value == null) 63 | { 64 | _Range = value; 65 | NotifyPropertyChanged(() => Range); 66 | } 67 | } 68 | } 69 | 70 | /// 71 | /// Gets te string that should be displayed (with or without hightlighting) 72 | /// 73 | public string DisplayString 74 | { 75 | get 76 | { 77 | return _DisplayString; 78 | } 79 | 80 | private set 81 | { 82 | if (_DisplayString != value) 83 | { 84 | _DisplayString = value; 85 | NotifyPropertyChanged(() => DisplayString); 86 | } 87 | } 88 | } 89 | #endregion properties 90 | 91 | #region methods 92 | /// 93 | /// Evaluates the given string against the 94 | /// property and sets the property to indicate the 95 | /// matched text range. 96 | /// 97 | /// 98 | /// 99 | public bool MatchString(string searchString) 100 | { 101 | if (string.IsNullOrEmpty(DisplayString) == true && 102 | string.IsNullOrEmpty(searchString) == true) 103 | { 104 | Range = new SelectionRange(0, 0); 105 | return true; 106 | } 107 | else 108 | { 109 | // There is no point in making this a match 110 | // if only one of the strings is not present 111 | if (string.IsNullOrEmpty(DisplayString) == true || 112 | string.IsNullOrEmpty(searchString) == true) 113 | { 114 | Range = new SelectionRange(-1, -1); 115 | return false; 116 | } 117 | } 118 | 119 | // Do we have a (sub)match or not ??? 120 | int start; 121 | if ((start = DisplayString.IndexOf(searchString)) >= 0) 122 | { 123 | Range = new SelectionRange(start, start + searchString.Length); 124 | return true; 125 | } 126 | else 127 | { 128 | Range = new SelectionRange(start, -1); 129 | return false; 130 | } 131 | } 132 | #endregion methods 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /source/Components/FilterTreeViewLib/Views/BindingProxy.cs: -------------------------------------------------------------------------------- 1 | namespace FilterTreeViewLib.Views 2 | { 3 | using System.Windows; 4 | 5 | /// 6 | /// Implements an XAML proxy which can be used to bind items (TreeViewItem, ListViewItem etc) 7 | /// with a viewmodel that manages the collecrions. 8 | /// 9 | /// Source: http://www.thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/ 10 | /// Issue: http://stackoverflow.com/questions/9994241/mvvm-binding-command-to-contextmenu-item 11 | /// 12 | public class BindingProxy : Freezable 13 | { 14 | /// 15 | /// Backing storage of the Data dependency property. 16 | /// 17 | /// Gets/sets the data object this class is forwarding to everyone 18 | /// who has a reference to this object. 19 | /// 20 | public static readonly DependencyProperty DataProperty = 21 | DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null)); 22 | 23 | /// 24 | /// Gets/sets the data object this class is forwarding to everyone 25 | /// who has a reference to this object. 26 | /// 27 | public object Data 28 | { 29 | get { return (object)GetValue(DataProperty); } 30 | set { SetValue(DataProperty, value); } 31 | } 32 | 33 | /// 34 | /// Overrides of Freezable 35 | /// 36 | /// 37 | protected override Freezable CreateInstanceCore() 38 | { 39 | return new BindingProxy(); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /source/Components/FilterTreeViewLib/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /source/FilterTreeView.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio 15 3 | VisualStudioVersion = 15.0.27004.2009 4 | MinimumVisualStudioVersion = 10.0.40219.1 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FilterTreeView", "FilterTreeView\FilterTreeView.csproj", "{9668A53D-608F-46B1-926C-14AC52CD35AE}" 6 | EndProject 7 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BusinessLib", "Components\BusinessLib\BusinessLib.csproj", "{70CC82CC-9EFE-461F-A959-F64A06894621}" 8 | EndProject 9 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FilterTreeViewLib", "Components\FilterTreeViewLib\FilterTreeViewLib.csproj", "{C5030463-0469-4AFA-92F4-F6B55A6B4C89}" 10 | EndProject 11 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Components", "Components", "{F0EEB4AD-6E8F-47AC-BE12-98A0A15ABD95}" 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 | {9668A53D-608F-46B1-926C-14AC52CD35AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 20 | {9668A53D-608F-46B1-926C-14AC52CD35AE}.Debug|Any CPU.Build.0 = Debug|Any CPU 21 | {9668A53D-608F-46B1-926C-14AC52CD35AE}.Release|Any CPU.ActiveCfg = Release|Any CPU 22 | {9668A53D-608F-46B1-926C-14AC52CD35AE}.Release|Any CPU.Build.0 = Release|Any CPU 23 | {70CC82CC-9EFE-461F-A959-F64A06894621}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {70CC82CC-9EFE-461F-A959-F64A06894621}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {70CC82CC-9EFE-461F-A959-F64A06894621}.Release|Any CPU.ActiveCfg = Release|Any CPU 26 | {70CC82CC-9EFE-461F-A959-F64A06894621}.Release|Any CPU.Build.0 = Release|Any CPU 27 | {C5030463-0469-4AFA-92F4-F6B55A6B4C89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 28 | {C5030463-0469-4AFA-92F4-F6B55A6B4C89}.Debug|Any CPU.Build.0 = Debug|Any CPU 29 | {C5030463-0469-4AFA-92F4-F6B55A6B4C89}.Release|Any CPU.ActiveCfg = Release|Any CPU 30 | {C5030463-0469-4AFA-92F4-F6B55A6B4C89}.Release|Any CPU.Build.0 = Release|Any CPU 31 | EndGlobalSection 32 | GlobalSection(SolutionProperties) = preSolution 33 | HideSolutionNode = FALSE 34 | EndGlobalSection 35 | GlobalSection(NestedProjects) = preSolution 36 | {70CC82CC-9EFE-461F-A959-F64A06894621} = {F0EEB4AD-6E8F-47AC-BE12-98A0A15ABD95} 37 | {C5030463-0469-4AFA-92F4-F6B55A6B4C89} = {F0EEB4AD-6E8F-47AC-BE12-98A0A15ABD95} 38 | EndGlobalSection 39 | GlobalSection(ExtensibilityGlobals) = postSolution 40 | SolutionGuid = {E5C2784B-651F-471A-84F7-7A11969323CE} 41 | EndGlobalSection 42 | EndGlobal 43 | -------------------------------------------------------------------------------- /source/FilterTreeView/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /source/FilterTreeView/App.xaml: -------------------------------------------------------------------------------- 1 |  5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /source/FilterTreeView/App.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace FilterTreeView 2 | { 3 | using System.Windows; 4 | 5 | /// 6 | /// Interaction logic for App.xaml 7 | /// 8 | public partial class App : Application 9 | { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /source/FilterTreeView/FilterTreeView.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {9668A53D-608F-46B1-926C-14AC52CD35AE} 8 | WinExe 9 | Properties 10 | FilterTreeView 11 | FilterTreeView 12 | v4.5.2 13 | 512 14 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 15 | 4 16 | true 17 | 18 | 19 | 20 | 21 | AnyCPU 22 | true 23 | full 24 | false 25 | bin\Debug\ 26 | TRACE;DEBUG;TREELIB 27 | prompt 28 | 4 29 | false 30 | 31 | 32 | AnyCPU 33 | pdbonly 34 | true 35 | bin\Release\ 36 | TRACE 37 | prompt 38 | 4 39 | false 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 4.0 57 | 58 | 59 | ..\packages\Dirkster.TreeLib.1.2.0\lib\net40\TreeLib.dll 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | MSBuild:Compile 68 | Designer 69 | 70 | 71 | 72 | 73 | 74 | MSBuild:Compile 75 | Designer 76 | 77 | 78 | App.xaml 79 | Code 80 | 81 | 82 | MainWindow.xaml 83 | Code 84 | 85 | 86 | 87 | 88 | Code 89 | 90 | 91 | True 92 | True 93 | Resources.resx 94 | 95 | 96 | True 97 | Settings.settings 98 | True 99 | 100 | 101 | ResXFileCodeGenerator 102 | Resources.Designer.cs 103 | 104 | 105 | 106 | SettingsSingleFileGenerator 107 | Settings.Designer.cs 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | {70cc82cc-9efe-461f-a959-f64a06894621} 117 | BusinessLib 118 | 119 | 120 | {c5030463-0469-4afa-92f4-f6b55a6b4c89} 121 | FilterTreeViewLib 122 | 123 | 124 | 125 | 132 | -------------------------------------------------------------------------------- /source/FilterTreeView/FilterTreeView.csproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | ProjectFiles 5 | 6 | -------------------------------------------------------------------------------- /source/FilterTreeView/MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  17 | 18 | 19 | 20 | 21 | 22 | 24 | 25 | 26 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 78 | 79 |